Git Product home page Git Product logo

activerecord-associations-review's Introduction

Active Record Associations Review

Active Record associations are an iconic Rails feature. They allow developers to work with complex networks of related models without having to write a single line of SQL โ€“โ€“ as long as all of the names line up!

Objectives

After this lesson, you will be able to...

  1. Create the correct foreign keys for associations.
  2. Use the correct association macros to apply an Active Record association.
  3. List the methods that are added by the association macros.
  4. Use build and create correctly to instantiate associated data, e.g., post.build_author and author.posts.build.
  5. Add associated data to a collection association (push objects onto a has_many association, e.g., @category.posts << post).
  6. Identify the SQL generated by various association methods.
  7. Construct a join model and table.
  8. Use a has_many :through with a join model.
  9. Manipulate associated data from a has_many :through, including creating the association from the join model directly PostTag.new(:post => post, :tag => tag).

Foreign Keys

It all starts in the database. Foreign keys are columns that refer to the primary key of another table. Conventionally, foreign keys in Active Record are comprised of the name of the model you're referencing, and _id. So for example if the foreign key was for a posts table it would be post_id.

Like any other column, foreign keys are accessible through instance methods of the same name. For example, a migration that looks like this:

class AddAuthorIdToPosts < ActiveRecord::Migration
  def change
    change_table :posts do |t|
      t.integer :author_id
    end
  end
end

Would mean you could find a post's author with the following Active Record query:

Author.find(@post.author_id)

Which is equivalent to the SQL:

SELECT * FROM authors WHERE id = #{@post.author_id}

And you could lookup an author's posts like this:

Post.where("author_id = ?", @author.id)

Which is equivalent to the SQL:

SELECT * FROM posts WHERE author_id = #{@author.id}

This is all great, but Rails is always looking for ways to save us keystrokes.

Many-to-One Relationships

By using Active Record's macro-style association class methods, we can add some convenient instance methods to our models.

The most common relationship is many-to-one, and it is declared in Active Record with belongs_to and has_many.

belongs_to

Each Post is associated with one Author.

class Post < ActiveRecord::Base
  belongs_to :author
end

We now have access to some new instance methods, like author. This will return the actual Author object that is attached to that @post.

@post.author_id = 5
@post.author #=> #<Author id=5>

has_many

In the opposite direction, each Author might be associated with zero, one, or many Post objects. We haven't changed the schema of the authors table at all; Active Record is just going to use posts.author_id to do all of the lookups.

class Author < ActiveRecord::Base
  has_many :posts
end

Now we can look up an author's posts just as easily:

@author.posts #=> [#<Post id=3>, #<Post id=8>]

Remember, Active Record uses its Inflector to switch between the singular and plural forms of your models.

Name Data
Model Author
Table authors
Foreign Key author_id
belongs_to :author
has_many :authors

Like many other Active Record class methods, the symbol you pass determines the name of the instance method that will be defined. So belongs_to :author will give you @post.author, and has_many :posts will give you @author.posts.

Convenience Builders

Building a new item in a collection

If you want to add a new post for an author, you might start this way:

new_post = Post.new(author_id: @author.id, title: "Web Development for Cats")
new_post.save

But the association macros save the day again, allowing this instead:

new_post = @author.posts.build(title: "Web Development for Cats")
new_post.save

This will return a new Post object with the author_id already set for you! We use this one as much as possible because it's just easier. build works just like new. So the instance that is returned isn't quite saved to the database just yet. You'll need to #save the instance when you want it to be persisted to the database.

Setting a singular association

The setup process is a little bit less intuitive for singular associations. Remember, a post belongs_to an author. The verbose way of creating this association would be like so:

@post.author = Author.new(name: "Leeroy Jenkins") 

In the previous section, @author.posts always exists, even if it's an empty array. Here, @post.author is nil until the author is defined, so Active Record can't give us something like @post.author.build. Instead, it prepends the attribute with build_ or create_. The create_ option will persist to the database for you.

new_author = @post.build_author(name: "Leeroy Jenkins")

Remember, if you used the build_ option, you'll need to persist your new author with #save.

These methods are also documented in the Rails Associations Guide.

Collection Convenience

If you add an existing object to a collection association, Active Record will conveniently take care of setting the foreign key for you:

@author = Author.find_by(name: "Leeroy Jenkins")
@author.posts
#=> []
@post = Post.new(title: "Web Development for Cats")
@post.author
#=> nil
@author.posts << @post
@post.author
#=> #<Author @name="Leeroy Jenkins">

One-to-One Relationships

Profiles can get pretty complex, so in large applications it can be a good idea to give them their own model. In this case:

  • Every author would have one, and only one, profile.
  • Every profile would have one, and only one, author.

belongs_to makes another appearance in this relationship, but instead of has_many the other model is declared with has_one.

If you're not sure which model should be declared with which macro, it's usually a safe bet to put belongs_to on whichever model has the foreign key column in its database table.

Many-to-Many Relationships and Join Tables

Each author has many posts, each post has one author.

The universe is in balance. We're programmers, so this really disturbs us. Let's shake things up and think about tags.

  • One-to-One doesn't work because a post can have multiple tags.
  • Many-to-One doesn't work because a tag can appear on multiple posts.

Because there is no "owner" model in this relationship, there's also no right place to put the foreign key column.

Enter the join table:

tag_id post_id
1 1
2 1
1 5

This join table depicts two tags (1 and 2) and two posts (1 and 5). Post 1 has both tags, while Post 5 has only one.

Mercifully, Active Record has a migration method for doing exactly this.

create_join_table :posts, :tags

This will create a table called posts_tags.

has_many :through

To work with the join table, both our Post and Tag models will have a has_many association with the posts_tags table. We also still need to associate Post and Tag themselves. Ideally, we'd like to be able to call a @my_post.tags method, right? That's where has_many :through comes in.

To do this requires a bit of focus. But you can do it! First of all, let's add the has_many :posts_tags line to our Post and Tag models:

class Post
  has_many :posts_tags
end

class PostsTag
  belongs_to :post
  belongs_to :tag
end

class Tag
  has_many :posts_tags
end

So now we can run code like @post.posts_tags to get all the join entries. This is kinda sorta what we want. What we really want is to be able to call @post.tags, so we need one more has_many relationship to complete the link between tags and posts: has_many :through. Essentially, our Post model has many tags through the posts_tags table, and vice versa. Let's write that out:

class Post
  has_many :posts_tags
  has_many :tags, through: :posts_tags
end

class PostsTag
  belongs_to :post
  belongs_to :tag
end

class Tag
  has_many :posts_tags
  has_many :posts, through: :posts_tags
end

Great, we've unlocked our @post.tags and @tag.posts methods!

Summary

For every relationship, there is a foreign key somewhere. Foreign keys correspond to the belongs_to macro on the model.

One-to-one and many-to-one relationships only require a single foreign key, which is stored in the 'subordinate' or 'owned' model. The other model declares its relationship via a has_one or has_many statement, respectively.

Many-to-many relationships require a join table containing a foreign key for both models. The models are joined using has_many :through statements.

You can see the entire list of class methods in the Rails API docs.

View Active Record Associations Review on Learn.co and start learning to code for free.

activerecord-associations-review's People

Contributors

annjohn avatar aviflombaum avatar changemewtf avatar dakotalmartinez avatar febbraiod avatar jmburges avatar lawrend avatar peterbell avatar pletcher avatar preetness avatar smulligan85 avatar sophiedebenedetto avatar theericdevelops avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.