DEV Community

mzakzook
mzakzook

Posted on

5 1

Intro to OOP: Ruby 'One to Many' vs 'Many to Many'

As a Ruby beginner, I started off learning the basics - hashes, arrays and methods. These building blocks will continue to show up again and again. But how do these 'building blocks' come together?

When faced with the challenge of modeling real-world paradigms, Classes opened new doors to the possibilities of code. In Ruby, the use of Classes to create relationships is known as Object Oriented Programming (OOP). An individual instance of a Class is known as an Object or Instance.

When designing the relationships among Classes I find it helpful to first distinguish: am I dealing with a 'one to many' relationship or a 'many to many' relationship?

An example of a 'one to many' relationship is: a sports team and its players. A player only has one team (let's disregard outliers like Bo Jackson) and a team has many players. To connect classes that fall into this category, I would add an attribute, intended to store an entire object of the 'one' class, to the 'many' class. For our team example this means adding a 'team' attribute, intended to store a team object, to the Player class.

class Player
    attr_accessor :team
    attr_reader :name, :position
    @@all = []

    def initialize(name, position)
        @name = name 
        @position = position
        @@all << self
    end
end

class Team
    attr_reader :name, :city
    @@all = []

    def initialize(name, city)
        @name = name
        @city = city
        @@all << self
    end

    def players
        Player.all.select { |player| player.team == self }
    end
end

With the above 'players' method inside the Team class I am able to find a team's roster (all of its players), and this is possible because of the 'team' attribute inside the Player class.

An example of a 'many to many' relationship is: a social media app and its users. A person uses many social media apps and a social media app has many users. This scenario calls for a Join class. Let's run through how it looks...

class User
    attr_reader :name, :email
    @@all = []

    def initialize(name, email)
        @name = name
        @email = email
        @@all << self
    end
end

class App
    attr_reader :name, :url
    @@all = []

    def initialize(name, url)
        @name = name
        @url = url
        @@all << self
    end
end

class Account
    attr_reader :user, :app
    attr_accessor :username, :password
    @@all = []

    def initialize(user, app, username, password)
        @user = user
        @app = app
        @username = username
        @password = password
        @@all << self
    end
end

In the above example, I created three classes: a User class, an App class and an Account class. The User class only captures information about a person: her/his name & email. The App class only captures information about a social media app: its name and url. And the Account class captures all User and App information (by initializing an Account object with a User object and an App object), as well as information that is specific to an account: its username and password. A user does not inherently know about the app(s) she/he uses and an app does not inherently know about its users, but they are connected through accounts.

With an 'Accounts' join class in place, I am now able to keep track of all user/app relationships, and call on methods belonging to either class. For example, if I wanted to see all of the apps for a specific user I could write...

class User  
    ...
    def apps
        my_accts = Account.all.select {|acct| acct.user == self}
        my_accts.map {|acct| acct.app}
    end
end

Designing your Class relationships from the start will allow you to hit the ground running when you're ready to write your code. And realizing when you're dealing with a 'One to Many' vs a 'Many to Many' relationship should simplify your design process.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (1)

Collapse
 
kwstannard profile image
Kelly Stannard

Thank you for taking the time to write this. There are a few problems with this approach though if you don't mind.

  • User objects know about the Account class, Account.all, and Account.all.select, causing three couplings.
  • Exposed class variables are effectively global variables and global variables cause a lot of tricky errors when you least expect it. Any globally accessible thing should be constant at runtime.
  • It seems like a re-implementation of an object-relational mapping but without the relational database. ORMs are more of a necessary evil than a good pattern. OOP prefers direct references between objects rather than filtering through global lists.

Hopefully that is helpful. I would recommend, if you haven't already, Sandi Metz for learning more about OOP as her writing is very accessible and informative.

Regards,
kwstannard

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay