Relationships in Ruby = 1, Soccer = nil

What is Object Orientated Programming?

One of the first concepts any junior developer at Flatiron School needs to understand is Object Orientated Programming (OOP). In Ruby, objects are the building blocks of code — they contain all the data and logic in order to complete a task. If objects are the building blocks; classes are the blueprints — they help us design, plan and construct them. OOP was designed to help the digital world mimic the real world as this makes the design process more intuitive and easy-to-understand.

While understanding these topics at first may seem like bashing your head, modeling them out makes it a breeze!

The world of football may no longer appear to be intuitive, easy-to-understand or even ‘mimic the real world’ but it is still an extremely relatable model to view how Object and Class Relationships in Ruby operate and facsimile real life.

Class of ’92

What is one thing that is pre-requisite in football? Over-priced tickets to subsidize extortionate wages? Yes, but even more so: players! Defining a player class in Ruby is as easy as kicking a ball.

Defining a class in Ruby is as easy as kicking a ball.
class Player  attr_reader :name
attr_accessor :age
@@all = []
def initialize(name, age)
@name = name
@age = age
@@all << self
end
end
messi = Player.new(“Messi”, 33) #=> <Player:0x00007f8b0e0181a0>
cr7 = Player.new(“CR7”, 35) #=> <Player:0x00007fbb97932358>
messi.name #=> Messy
messi.age = 34
messi.age #=> 34

This is a lot of code so let’s walk through it. To create a new player all we had to do is call the .new method to instantiate a new player instance and thus bring our player to life. As you can see, we can create multiple difference player instances from the same class. The initialize method is an instance method built into Ruby which is called the.new method that allows our Player to have attributes from the get-go. At the top we have our macros, these allow us to either read or overwrite the attributes of our player. Name is an attr_reader which means it cannot be changed, while age is an attr_accessor so can be changed whenever there is a birthday to be celebrated! The @all is an empty array which we then shovel all of our initiated instances as soon as they are created. This enables us to search through them once we define the all class method.

def self.all 
@@all
end

Mes Que Un Class

Players are great and all but what really inspires mass hooliganism is Clubs; let’s create a Club class so our players can sign lucrative contracts with them!

class Club
attr_reader :name
@@all = []
def initialize(name)
@name = name
@@all << self
end
end
trad_bricks = Club.new(“Trad Bricks”) #=> <Club:0x00007f1b0e0181a0>

Now we have our clubs and our players, let’s work on filling some squads. First, let’s plan out the relationship between them. This is the first thing you need to do whenever you start looking at classes in Ruby. There are a few main types of relationship and the first we’ll discuss is the Belongs-to.

A Bosman

Belongs-to relationship is the simplest and also the easiest for any football fan to understand. A player is contracted to a club and while contracted cannot play for any other, so you could say that they ‘belongs-to’ that club. This is essentially the exact-same principle in Ruby, you can only have one of another in a belongs-to relationship.

N.B. Please could somebody forward this paragraph to Paul Pogba.

We establish this relationship by creating a macro for the specific class they belong to, like so:

class Player
attr_reader :name
attr_accessor :age, :club

With the January transfer window coming up we better make sure it can be rewritten so it’s going down as an attr_accessor.

We also want to have our Player class initialize with a club. But what if they don’t have a club? Have no fear Jack Wilshire, we can set the default value to ‘Free Agent’ (more polite than ‘injury-prone rubbish’) right there in the argument.

def initialize(name, age, club = ‘Free Agent’)
@name = name
@age = age
@club = club

So a player belongs to a club, does this mean a club belongs to a player? Of course they don’t! A club needs to put out at least 11 players per game, if they only had one player in the pitch, football would be a lot slower — but there would probably be a lot more goals so maybe it’s worth trying…? Clubs have many players, some clubs like Frank Lampard’s Chelsea have more players than most but it is safe to say clubs have many players. So what do we call this relationship? Former FIFA President, Sepp Blatter called it “modern slavery” but we are Rubyists who know better, so we call it a Has-Many relationship.

Winning 11

You may have noticed that the Club class has not initialized with a player attribute. This is because of the notion of a ‘single-source-of-truth’. If our Messi player instance is keeping track that it’s playing for Barca and our Barca club class is keeping track that Messi is one of its many players — what would happen if Messi left and forgot to send the Burofax informing the club? Suddenly we would have two differing truths — Messi knows he’s gone to Canterbury City FC but Barca still think he’s playing for them! To avoid this issue, we have to choose one to relationship hold the truth, it makes more sense for the class in the belongs-to relationship to keep track of this because they only have one class they belong to — while the alternative has-many by definition! For the club class to access this information and keep track of it’s players, we can use a method to access them.

class Club   def players 
Player.all.select { |player| player.club == self}
end

This code is iterating through our club class and viewing all the players whose club attribute matches the Club class we are calling it on.

Now we have our club and our player relationships mapped out it’s time to create our final one: Has-many-to-many or Has-many-through.

Die Meister, Die Besten, Les grandes équipes

Most clubs only play in one league meaning the relationship would follow the same as Player/Club with Club now being the belongs-to side of the League class Having-many Clubs. But Flatiron FC have rocketed up the division and now play in their domestic and continental league, we know they can’t belong to more than one league and if they have a has-many relationship on both sides, how will we ever keep track of our single source of truth? This is why we must create a link between the two classes which can act as a ‘Join Table’ to contain the single source of truth. Naming convention labels this join table a portmanteau of the two classes but please feel free to list your football references as better names for it in the comments.

class LeagueClubs
attr_accessor :league, :club
@@all = []
def initialize(league, club)
@league = league
@club = club
@@all << self
end

We don’t need to put much into this class, it just needs to be connected to both a league and a club and it can keep track of all the information between them so we can maintain a single-source-of-truth!

Fergie Time

Hope this has helped you understand how relationships work in OOP and how easy it is to view them through a real-world lens — just as planned!