Ruby on Rails – ODIN Project – 5

odinAdvanced Forms and Active Record

Now it’s starting to get fun! Learn how to do more than just find and show your users. You’ll learn how to use relationships between models to greatly expand your abilities and how to build web forms with sufficient firepower to achieve your most ambitious goals.


 Step 1: Active Record Queries

Learn how to take some of those advanced querying concepts you used in SQL and have Rails do them for you.

Introduction

Up until now, we’ve covered the bread and butter you need to build basic queries using Active Record. After building a handful of projects and working through the tutorial, you should be comfortable making these queries.

Active Record is much more powerful than just simple CRUD actions on individual records. It gives you a Ruby-ish interface to do almost anything you can do with bare-metal SQL statements. You can cherry-pick individual groups of records based on specific criteria and order them however you want. You can join tables manually or query using associations set up by Rails. You can return lists of records or perform basic math like counts and averages.

All this is done at the database level, which is much faster than loading up a whole table of stuff into Ruby objects before parsing and chopping and calculating with it.

In this lesson, we’ll get into the more interesting and useful areas of Active Record queries. You’ll learn what Active Record actually returns and how to manipulate the returned values at will. You’ll also learn how to make your queries more efficient along the way.

There’s a lot of material to read and cover, but it basically follows the idea “anything you can do in SQL, you can do in Active Record”. They mostly use the same terminology as well. Active Record just extends that functionality by giving you a suite of versatile methods (and concepts like Relations) to make it much more user-friendly along the way.

You Should Understand

  1. What is an ActiveRecord::Relation?
  2. What does Lazy Evaluation mean?
  3. How do you make a relation evaluate into an array?
  4. How do you check whether a database already contains a record?
  5. Why is #find_by useful and how is it used?
  6. What’s the difference between what’s returned using a #where query and a #find query?
  7. How do you join tables together in Rails?
  8. When can you use symbols / hashes and when do you need to use explicit strings for query parameters?
  9. What are Scopes and why are they useful?
  10. What needs to happen for a class method to act like a scope?

 Relations and Lazy Evaluation

Using User.find(1) will return an unambiguous object — it’s going to find the user with ID = 1 and give it to you as a Ruby object. But this behavior is actually unusual. Most queries don’t actually return a Ruby object, they just fake it. For example:

    User.where(:id => 1)

Might look like it returns an array that contains a serialized User object, like:

    [#<User id: 1, email: "foo@bar.com">]

But try running User.where(:id => 1).class and you’ll see that it isn’t an Array, it’s actually an instance of ActiveRecord::Relation. Relations are actually just really good at looking like arrays but they’ve got more going on.

Active Record queries return relations to be lazy. There’s basically no reason to actually tell the database to execute a query until the very last possible minute. What if you never actually needed to use that query at all? What if you want to make it more complex before executing it? Relations give you that flexibility and make much more efficient use of your database’s valuable time.

Relations only get executed when it becomes absolutely necessary to know what’s inside them. So if your controller grabs 5 blog posts using @posts = Post.limit(5), that is really passing your view a relation. It’s only when the code in the view actually calls a method on @posts (like @posts.first.title) that the query will be run and the relation will get stored as a real Ruby object in memory.

This behavior can be a bit tricky to observe if you use something like the Rails Console ($ rails console) to test them out, because the queries will actually be run right away in the console since it implicitly runs something like the .inspect method on the relation, which requires the query to be run. But try playing with building a query like we did above and checking out its #class… you’ll usually get back ActiveRecord::Relation.


 Chaining Queries

Relations aren’t just built for speed… they’re also built for flexibility. Let’s say you want to grab the first 5 posts listed in ascending order (Post.limit(5).order(:created_at => :desc)). Because #limit returns a Relation, #order takes that relation and adds its own criteria to it. You can chain together a dozen methods this way, and, when it’s finally time to execute, ActiveRecord and SQL (if that’s what you’re using for the DB) will figure out the optimal way to structure the query to achieve the desired result.

This is the sort of behavior that you just sort of expect to work, and Relations are what enables it to do so.


 Why Care?

You should care that ActiveRecord queries usually return Relations because you’ll run into them often when coding and debugging. The knowledge should make you comfortable chaining query methods together to construct elaborate queries.

If you end up working with a Relation when you really want it to act like an Array, you can sometimes run #to_a on it to force it to evaluate the query.

A couple of common methods which do NOT return Relations and can force evaluation of a relation, are #all (returns an array of objects) and #find (returns a single object).


 Beyond Basic Querying

You should be confident now with simple queries like finding objects. The reading you do for this section will cover the basics and then dive in a bit further than before. There are a couple of new concepts worth mentioning.


 Checking for Existence

The simplest new concept is how to check whether an object actually exists yet or not, which you may want to do before running a method which depends on the object actually having been saved already.

#exists? will return true/false. #any? will be true if any records match the specified criteria and #many? will be true if multiple records match the specified criteria. You can run each of these either on a model directly, a Relation, an association, or a scope (which we’ll cover later). Basically, anywhere you might think of using them, they’re likely to work:

    # From the Guide:
    # via a model
    Post.any?
    Post.many?

    # via a relation
    Post.where(:published => true).any?
    Post.where(:published => true).many?

    # via an association
    Post.first.categories.any?
    Post.first.categories.many?

 Arguments

There are multiple ways to submit arguments for most Rails query methods. You can typically use either symbols or strings or both. I prefer to stick with symbols and hashes wherever possible. You can also use ? parameters like in normal SQL. When it’s not ambiguous (ie. if you aren’t working with multiple tables) you can also choose to specify the table name or not (see #5 below). All of the following are the same:

  1. User.where(:email => "foo@bar.com")
  2. User.where("email" => "foo@bar.com")
  3. User.where("email = 'foo@bar.com'")
  4. User.where("email = ?", "foo@bar.com")
  5. User.where("users.email" => "foo@bar.com")

 More Assorted Querying Knowledge

Very large queries can actually be batched into lots of subqueries so they don’t eat up tons of performance resources. #find_each does the trick. The basic principle is that it chunks the query into pieces, loading up the first piece and evaluating it before moving onto the next one. This will be helpful for you when optimizing queries but isn’t really something to worry too much about up front.

#where queries give you a fair bit of flexibility. They let you specify an exact value to find, a range of values to find, or several values to find. If you know what type of query you’re looking for, you can almost guess the proper syntax for executing it.

The key thing to note is that #find returns the actual record while #where returns an ActiveRecord::Relation which basically acts like an array. So if you’re using #where to find a single record, you still need to remember to go into that “array” and grab the first record, e.g. User.where(:email => "foo@bar.com")[0] or User.where(:email => "foo@bar.com").first.

#find_by is a really neat method that basically lets you build your own finder method. It’s an alternative to using #where (to which you’d have to add another method like #take or #first to pull the result out of the returned array). If you want to find by a user’s email, write User.find_by(:email => 'foo@bar.com').

#select should be pretty obvious to a SQL ninja like you — it lets you choose which columns to select from the table(s), just like in SQL. To select just the ID column for all users, it’s as simple as User.select(:id). You can also use aliases like in SQL but should use quotes instead of symbols, e.g. @users = User.select("users.id AS user_id") will create a new attribute called user_id, e.g. allowing you to access @users.first.user_id.


 Aggregations

Just like with SQL, you often want to group fields together (or “roll up” the values under one header). For example, grouping blog posts written on a certain date. This is most useful when you also apply mathematical operations to them like #count or #max. An example (a bit more complex because it involves joining two tables) is if I want to get a count of all the blog posts categorized by each tag. I might write something like:

Post.joins(:tags).group("tags.name").count
# => {"tag1" => 4, "tag2" => 2, "tag3" => 5}

#having is sort of like a #where clause for grouped queries.


 Joins

When working with multiple tables, you’ll often want to join them together. Rails associations often do the heavy lifting of setting up the joins for you if you’re working with instances of an object, so you may not need to explicitly use a #join right away.

But if you’re running queries like in the Post-Tag-count grouping example used above, you’ll need to use joins to bring together the appropriate tables. You need to be more careful with how you select data when using joins — if you are looking for the :id column, which table’s ID are we asking for? You’ll find yourself using more explicit strings when joining, e.g. in the example above (copied below) where we specify the name attribute of the tags table:

Post.joins(:tags).group("tags.name").count
# => {"tag1" => 4, "tag2" => 2, "tag3" => 5}

 Your Tasks

1. Real Rails Guides Active Record Query Interface (http://guides.rubyonrails.org/active_record_querying.html). Read the first 5 sections for a more basic overview of query functions. Don’t worry too much about batching and #find_each.

2. Real Rails Guides Active Record Query Interface (http://guides.rubyonrails.org/active_record_querying.html). Read section 18  or a brief look at using exists?any? and many?.

3. Real Rails Guides Active Record Query Interface (http://guides.rubyonrails.org/active_record_querying.html). Read sections 6, 7 and 19 for an understanding of aggregate functions and the calculations you can run on them.

4. Real Rails Guides Active Record Query Interface (http://guides.rubyonrails.org/active_record_querying.html). Skim sections 8 to 11

5. Real Rails Guides Active Record Query Interface (http://guides.rubyonrails.org/active_record_querying.html). Read section 12 to see how Rails lets you play with joining tables together.

6. Real Rails Guides Active Record Query Interface (http://guides.rubyonrails.org/active_record_querying.html). Read section 16 for a quick look at the helpful find_or_create_by methods.


  N + 1 Queries and Eager Loading

 If you want your application to run with any kind of efficiency at all, you should strive to reduce the number of queries that are run on your database as much as possible. That means figuring out ahead of time exactly what you’re looking for and then building the correct query to grab that thing one time onlIt’s okay to grab the SAME information multiple times. Rails caches the first result anyway so it doesn’t result in a performance hit. But there are situations where you force the ActiveRecord::Relation that is returned by a query to execute itself immediately and then you try to run queries on each member of the collection. That’s a whole lot of queries and can quickly slow your application down to a snail’s pace.

The N + 1 query problem is the classic case of this.  You grab all the records for your users (User.all) then loop through each user and call an association it has, like the city the user lives in (user.city). For this example we’re assuming an association exists between User and City, where User belongs_to a City. This might look like:

    User.all.each do |user|
      puts user.city
    end

This is going to result in one query to get all the users, then another query for each user to find its city through the association… so N additional queries, where N is the total number of users. Hence “N+1” problems. Note that it’s totally fine to just grab a regular attribute of User like user.name. It’s because you’re reaching through the association with City that we’ve got to run another full query.

If the best way to make an application run faster is to reduce database calls, we’ve just messed up badly by causing a potentially huge number of them.

Rails is well aware of your distress and has provided a simple solution — “eager loading”. When you first grab the list of all users, you can tell Rails to also grab the cities at the same time (with just one additional query) and store them in memory until you’d like to call upon them. Then user.city gets treated the same way as user.name. It doesn’t run another query. The trick is the #includes method.

#includes basically takes the name of one or more associations that you’d like to load at the same time as your original object and brings them into memory. You can chain it onto other methods like #where or #order clauses.

Note: One thing which can be a bit annoying from a development standpoint is that I haven’t found an easy way to “see” your eager-loaded fields by looking at the output from your Rails server. So don’t be alarmed if they don’t show up in the server output.

Almost as useful is the #pluck method, which is covered in the Rails Guide. #pluck lets you skip several steps in the process of pulling up a bunch of records, storing them in memory, then grabbing a specific column and placing it into an array. #pluck just gives you the resulting array right away:

    User.pluck(:name)
    # => ["Foo", "Bar", "Baz", "Jimmy-Bob"]

This is another way to help speed up your application if you’ve found pain points. Start by getting rid of N+1 queries, though.


Scopes

Scopes are underappreciated and awesome and very simple. A scope is basically a custom chain of ActiveRecord methods that you can slap onto an existing Relation by calling its name like a normal method. It’s easiest to see in an example.

Let’s say you let your user choose to filter your blog posts only for those marked “important”:

    # app/models/post.rb
    ...
    scope :important, -> { where(:is_important => true) }
    ...

    # app/controllers/posts_controller.rb
    ...
    def index
      if params[:important] == true
        @posts = Post.important.all
      else
        @posts = Post.all
      end
    end

This is a pretty simple example. Instead of always having to rewrite that chain of ActiveRecord methods when you want them, you can create nicely named scopes to contain all the component logic. You reduce repetition and make your code more readable. The best part is that scopes return Relations… so you can chain as many of them as you want.

You might be thinking, Why use a scope when you can write a class method to do the same thing? You can, as long as your class method returns a Relation (which can take some additional thought for edge cases). In fact, using a class method is often best if your logic chains are quite complicated. The example above could be solved using the following class method as well:

    # app/models/post.rb
    ...
    def self.important
      self.where(:is_important => true)
    end
    ...

See the Additional Resources section for links to some posts that dig a bit deeper into the use cases for these two.

How much do you need to understand or care about scopes? In the early going, you probably won’t run into them or see why to use them. Keep them in the back of your mind for when you start working on some slightly more complicated projects that might need them.


Bare-Metal SQL

Sometimes, you just can’t get ActiveRecord to do what you want it to. In that case, it gives you an interface to the bare metal SQL so you can just type in your query as desired. This should really be a last resort. It’s basically hard-coding your application code. Use the #find_by_sql method for this.


Your Tasks

1. Read Rails Guides Active Record Query Interface Chapter 14  (http://guides.rubyonrails.org/active_record_querying.html) for a look at Scopes. Again, you don’t necessarily need to memorize all the details of scopes, but understand the concept and when it might be useful.

2. Read Rails Guides Active Record Query Interface Chapter 17  (http://guides.rubyonrails.org/active_record_querying.html)or a look at using SQL directly.


Conclusion

At the most basic level you can do pretty much anything you can in SQL by using Active Record query methods. You’ll get a chance to use some of these new found query methods in future projects and others will come up when you’re building things on your own.


Additional Resources


Step 2: Active Record Associations

Dive into some of the more interesting features of associations like special methods and polymorphism.

Advanced Active Record Associations

Introduction

You’ve already had some familiarity with associations, especially the basic has_one, has_many, and belongs_to variety. Thus far, you’ve probably mostly used these associations to grab collections of objects like a user’s posts (user.posts). There are a lot of other handy things that Rails lets you do with associations too. This brief section will highlight some of the more useful methods that come along with associations.

Points to Ponder

Look through these now and then use them to test yourself after doing the tasks

  • How does Rails normally know which table and foreign key to use when you have an association (e.g. User.first.posts)?
  • When would you need to specify the :class_name option in an association?
  • What about the :foreign_key?
  • What about the :source?
  • What is a polymorphic association and when would you use one?
  • What are two ways to use the association to create a new object instead of just calling YourObject.new? Why is this useful? Which method is preferred?
  • How do you automatically destroy all a User’s Post objects if that user is deleted?
  • How do you set up a self-association, like with Users following Users?

Basic Stuff

This section is meant to just bring up some of the basic stuff you may not yet have been exposed to.

Foreign Keys and Class Names

When you create an association, Rails makes two major assumptions — first, that the class of the model your association points to is based directly off of the name of the association, and, second, that the foreign key in any belongs_to relationship will be called yourassociationname_id. Any time you go away from these defaults, you just need to let Rails know what kind of class to look for and which foreign key to use.

A very simple case would be a User who can create many Posts for a blog:

    # app/models/user.rb
    class User < ActiveRecord::Base
      has_many :posts
    end

    # app/models/post.rb
    class Post < ActiveRecord::Base
      belongs_to :user
    end

So you could ask the first user for all her posts with User.first.posts or the first post for its author user with Post.first.user. Rails knows to look for a foreign key called user_id in the Posts table. If you ask for Post.first.user, Rails will look in the Users table for the User with the ID corresponding to the user_id column in the Posts table. All is well in the world when your association names correspond directly to the names of your models and tables.

But what if you want to have two types of users that the post belongs to — the “author” and the “editor”? In this case, you’ll need two separate foreign keys in your Posts table, presumably one called author_id and another called editor_id. How do you tell Rails that each of these foreign keys actually point to a User (so it knows to look in the Users table for them)? Just specify the class your association should point to using the aptly named :class_name option:

    # app/models/post.rb
    class Post < ActiveRecord::Base
      belongs_to :author, :class_name => "User"
      belongs_to :editor, :class_name => "User"
    end

In this case, Rails will automatically look for the foreign key named after the association, e.g. author_id or editor_id, in the Posts table.

If you called the association something which didn’t correspond exactly to what you’d named the foreign key in your table, you need to tell Rails that as well. This should be obvious if you think of this relationship from the side of the User. The User will have some posts for which she is an author and others for which she is an editor. You’ll need to rename the association on the User’s side as well to keep things crystal clear, for instance splitting up authored_posts from edited_posts.

But now Rails doesn’t have the foggiest idea where to look and what to look for. By default, if you ask for User.first.authored_posts it will go looking in the authored_posts table for a foreign key called user_id (neither of which exist). To get it pointing at the right table, we again need to specify the :class_name and to get it using the correct foreign key, we need to specify the right :foreign_key. For instance:

    # app/models/user.rb
    class User < ActiveRecord::Base
      has_many :authored_posts, :foreign_key => "author_id", :class_name => "Post"
      has_many :edited_posts, :foreign_key => "editor_id", :class_name => "Post"
    end

The basic gist of this is simple — assume that Rails is looking for the foreign key named after the association in the table also named after the association. If any of these is incorrect because of a creatively named association or foreign key, you’ll need to specify. This is quite common to make your associations more legible.


Source

Now that it’s clear you need to let Rails know when you’ve creatively named your associations or foreign keys, I should point out that there’s one additional step required if you’re using a creatively named has_many :through association. Recall that has-many-through associations are where you create a “through table” to act as a go-between for two models that have a many-to-many relationship.

For example, perhaps we change the example above so a Post actually can have multiple Authors (but still only one editor). We’ll need to create a new table, which we’ll call post_authorings. post_authorings joins these two models together and contains columns for authored_post_id and post_author_id. You can probably see where this is going — we’ve named our foreign keys something more descriptive and helpful than just simply post_id and user_id but it will require us to inform Rails of the change. Our models look like:

    # app/models/post.rb
    class Post < ActiveRecord::Base
      has_many :post_authorings, :foreign_key => :authored_post_id
      has_many :authors, :through => :post_authorings, :source => :post_author
      belongs_to :editor, :class_name => "User"
    end

    # app/models/user.rb
    class User < ActiveRecord::Base
      has_many :post_authorings, :foreign_key => :post_author_id
      has_many :authored_posts, :through => :post_authorings
      has_many :edited_posts, :foreign_key => :editor_id, :class_name => "Post"
    end

    # app/models/post_authoring.rb
    class PostAuthoring < ActiveRecord::Base
      belongs_to :post_author, :class_name => "User"
      belongs_to :authored_post, :class_name => "Post"
    end

The major thing to note here is that with has-many-through associations, Rails uses the name of the association in the through table to determine which foreign key and table name to reach out to. If it’s named anything irregular, you’ll use the :source option to specify which association actually points where we’d like to go. You can think of :source as being just like :class_name but for the associations in the “through table”.

It may be helpful to illustrate what Rails is doing. In the example above, if you ask for Post.first.authors, Rails sort of “thinks” like this:

  1. You just called for the “authors” association on the Post model
  2. To get “authors”, we first need to go through the post_authorings association to get there.
  3. Once we’re at the PostAuthoring model, to get to the author, we’ll need to use the :belongs_to association and it’ll be called post_author. We know this because we used the :source option. If we hadn’t used the :source option in the original has-many-through association, we would have been looking for belongs_to :author instead.
  4. Now we’ve got all the information we need to structure our SQL joins and grab the list of authors for the post.

It sounds a bit wonky but it’s just the same logic as before — if Rails can’t tell based on its assumptions which associations or class names or foreign keys you’re supposed to use, you need to specify them yourself using :source or :foreign_key or :class_name. It takes some practice but you’ll get it. Usually you know something’s up if you get error messages of the flavor ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column.

Once you do get things figured out, it can still be helpful to look in your Rails server output to see which joins are being done to build the SQL query. That’s a great window into what your associations are doing behind the scenes (because in the end, it’s all about figuring out the correct SQL query to run).

Polymorphic Associations

We’ll cover polymorphism here but, if your head is really spinning from the other concepts, feel free to just skim it. Consider this an “additional topic” instead of a core topic like foreign keys, class names and source.

Polymorphic associations can be a bit of a head scratcher at first and aren’t terribly common, but are well suited for their use case. They use a big word to describe a pretty straightforward concept — what if you have a single model that can belong to a bunch of different types of models? For example, let’s say you’re building a service like Facebook where users can comment on any of the different types of things posted by other users (like text, pictures, images). How do you make it okay to comment on all these different types of objects using just a single Comment model?

In a plain vanilla situation, the comment would belongs_to a Post or a Picture or a Video (or whatever you’re commenting on). You would have a foreign key called something like post_id in your Comments table. Now if we want to be able to comment on multiple types of things, we need to figure out a different way of dealing with the foreign key because a single foreign key could be referencing a post, an image, a video etc and we don’t know which one… it’s ambiguous. You could just make a different column for each one, e.g. post_id, image_id, video_id, but that is terribly inelegant and hardcoded (imagine if there were 100 different types of posts we want to be able to comment on!). We need to stick with a single foreign key column.

We solve this by storing not just the foreign key id, but also a reference to which type of model it corresponds to. That way, whenever you want to retrieve a comment, by specifying which type of thing it belongs to it is no longer ambiguous what you’re asking for. Note that Rails does this for you in the background as long as it knows you’re working with a polymorphic association.

We have to call our foreign key something a bit different from the normal case since it’s ambiguous which model it’s referencing and you can’t just use post_id or picture_id. A convention is to come up with an abstract term for what type of action you’re doing and use that to name the association. So in this case we’re commenting on things and can thus call the foreign key "commentable". You’ll see the *able convention used a fair bit. So the migration for that model might look like:

    class CreateComments < ActiveRecord::Migration
      def change
        create_table :comments do |t|
          t.string :title
          t.text :content
          t.integer :commentable_id
          t.string :commentable_type
          t.timestamps
        end
      end
    end

“Commentable” will be used to refer to the associations as well. You’ll need to tell your Comment model that it is actually polymorphic so Rails knows to also check for a commentable_type column when using it. This is done simply:

    # app/models/comments.rb
    class Comment < ActiveRecord::Base
      belongs_to :commentable, :polymorphic => true
    end

On the other side of the association, you just treat your comment like any other association (which happens to have a nonstandard name). You just need to specify the association on every model that has_many of it. The only wrinkle is that, because it’s using the “commentable” name, you need to specify it in an alias just like you would if any other association had a nonstandard name:

    # app/models/post.rb
    class Post < ActiveRecord::Base
      has_many :comments, :as => :commentable
    end

    # app/models/picture.rb
    class Picture < ActiveRecord::Base
      has_many :comments, :as => :commentable
    end

Rails does the rest of the work for you. Any time you ask a Picture for all its comments (Picture.first.comments), Rails will return just the comments that belong to that picture without you having to worry about anything else.

Self Joins

Often times you have relationships between the same type of model, for instance users who can follow other users. In this case, you need to specify both associations in your User model but name them differently. You will need to specify in your has_many association what the name of the foreign_key will be:

    class Employee < ActiveRecord::Base
      has_many :subordinates, class_name: "Employee",
                              foreign_key: "manager_id"

      belongs_to :manager, class_name: "Employee"
    end 

Handy Methods

As mentioned in the intro, associations give you access to some nifty tricks that you might not think of.

Creating Association Objects

There’s a couple of shortcuts for creating new association objects. The first is to call #new or #create on the association to automatically populate the foreign key. For instance, if a User has_many Posts and Post belongs_to a User:

# Long version:
> user = User.first
> post = Post.create(:title => "sample", :user_id => user.id)

# Shorter version:
> user = User.first
> user.posts.create(:title => "sample")

Another nifty trick is to create a new object and then use the shovel operator to add it to the association. This is just one of the ways that associations sometimes act like arrays:

> user = User.create(:name => "foobar")
> post = Post.new(:title => "sample")
> user.posts << post

This will save the post to the database with that User’s foreign key. It’s roughly equivalent to calling:

> post.user_id = user.id
> post.save

If you really want to, you can actually replace the entire association with a new collection by setting it equal to the new collection:

> user = User.first
> post1 = Post.find(1)
> post2 = Post.find(2)
> user.posts = [post1, post2]  # posts added to that user's collection

Destroying Dependents

If your user has created a bunch of posts and then decides to delete her account, how do you delete all the associated posts? Specify the :dependent => :destroy option when first declaring the association:

    # app/models/user.rb
    class User < ActiveRecord::Base
      has_many :posts, :dependent => :destroy
    end

This is just the most common among several options to specify for :dependent. It will run the destroy method on all objects that belong to that user when the user is destroyed.

Your Assignment

  1. Read the Rails Guide on Associations. Start by skimming sections 1 to 2.7 (which you should have already done).
  2. Read from 2.8 to the end of chapter 3.
  3. Skim chapter 4. It contains all the methods that you gain by using various associations. You certainly don’t need to memorize the whole list, but poke through it. You’ll end up using most of them.

Conclusion

In this lesson we covered some of the more advanced associations material. Associations are all over the place in Rails and incredibly useful because of all the new methods they give you access to. As long as you pause and think about what Rails is assuming when you set them up, you should be able to modify them to your liking without too much trouble. Practice makes perfect, though, so keep building projects with associations in them and it’ll eventually stick.

Additional Resources

This section contains helpful links to other content. It isn’t required, so consider it supplemental for if you need to dive deeper into something


 

Project:

Associations

Build a system to manage signups for you and your friends’ special events.

Projects: Active Record Associations

Don’t forget to use Git to save your projects!

In these projects, you’ll finally really get to dive into ActiveRecord’s associations, one of the best features it offers. The tutorial chapter will have you creating microposts for your users and the other project will give you a chance to add some more interesting associations to your previous work.

Warmup: Thinking Data First

Lay out the data architecture you’d need to implement to build the following scenarios:

  1. A site for pet-sitting (watching someone’s pet while they’re gone). People can babysit for multiple pets and pets can have multiple petsitters.
  2. You like hosting people for dinner so you want to build a dinner party invitation site. A user can create parties, invite people to a party, and accept an invitation to someone else’s party.
  3. Extra credit (tricky): You and your friends just love posting things and following each other. How would you set up the models so a user can follow another user?

Project 1: Ruby on Rails Tutorial

This chapter of the tutorial begins the conceptual heavy lifting. For beginners trying to plough through the tutorial, this is usually the beginning of the “I’m doing it but not understanding it” phase. Luckily, you’ve already learned everything that will be covered in the tutorial and this should be a great chance to see it in action as part of a real website.

The basic gist of it is that, to have a microposting site like Twitter, you’ve got to actually have microposts. Users create microposts, so you can surely expect that a User will has_many microposts. With the association done, the rest is really just about setting up the correct views to display the microposts.

As with the previous chapter, just make sure you’re writing the test specs in the correct files and stick with it. There’s a lot of ground to cover but, again, the conceptual hurdles should be well within reach at this point.

Your Task

Project 2: Private Events

You want to build a site similar to a private Eventbrite which allows users to create events and then manage user signups. Users can create events and send invitations and parties (sound familiar?). Events take place at a specific date and at a location (which you can just store as a string, like “Andy’s House”).

A user can create events. A user can attend many events. An event can be attended by many users. This will require you to model many-to-many relationships and also to be very conscious about your foreign keys and class names (hint: you won’t be able to just rely on Rails’ defaults like you have before).

Your Task

We’ve gotten quite far here, so these tasks will only lay out the high level overview of what you’re meant to build. You’ll need to implement the details yourself. As usual, it’s not meant to look pretty, just work. Design is all extra credit.

Setup and Sign In

  1. Model the data for your application, including the necessary tables.
  2. Create a new Rails application and Git repo called private-events.
  3. Update your README to be descriptive and link to this project.
  4. Build and migrate your User model. Don’t worry about validations.
  5. Create a simple Users controller and corresponding routes for #new, #create, and #show actions. You’ll need to make a form where you can sign up a new user and a simple #show page. You should be getting better and faster at this type of vanilla controller/form/view building.
  6. Create a simple sign in function that doesn’t require a password — just enter the ID or name of the user you’d like to “sign in” as and click Okay. You can then save the ID of the “signed in” user in either the session hash or the cookies hash and retrieve it when necessary. It may be helpful to always display the name of the “signed in” user at the top.

Basic Events

  1. Build and migrate your Event model without any foreign keys. Don’t worry about validations. Include the event’s date in your model but don’t worry about doing anything special with it yet.
  2. Add the association between the event creator (a User) and the event. Call this user the “creator”. Add the foreign key to the Events model as necessary. You’ll need to specify your association properties carefully (e.g. :foreign_key, :class_name, and :source).
  3. Modify your User’s Show page to list all the events a user has created.
  4. Create an EventsController and corresponding routes to allow you to create an event (don’t worry about editing or deleting events), show a single event, and list all events.
  5. The form for creating an event should just contain a :description field.
  6. The #create action should use the #build method of the association to create the new event with the user’s ID prepopulated. You could easily also just use Event’s ::new method and manually enter the ID but… don’t.
  7. The event’s Show page should just display the creator of the event for now.
  8. Create the Event Index page to display all events.

Event Attendance

  1. Now add the association between the event attendee (also a User) and the event. Call this user the “attendee”. Call the event the “attended_event”. You’ll again need to juggle specially named foreign keys and classes and sources.
  2. Create and migrate all necessary tables and foreign keys. This will require a “through” table since an Event can have many Attendees and a single User (Attendee) can attend many Events… many-to-many.
  3. Now make an Event’s Show page display a list of attendees.
  4. Make a User’s Show page display a list of events he is attending.
  5. Modify the User’s Show page to separate those events which have occurred in the past (“Previously attended events”) from those which are occuring in the future (“Upcoming events”). You could do this by putting logic in your view. Don’t. Have your controller call separate model methods to retrieve each, e.g. @upcoming_events = current_user.upcoming_events and @prev_events = current_user.previous_events. You’ll get some practice with working with dates as well as building some queries.
  6. Modify the Event Index page to list all events, separated into Past and Upcoming categories. Use a class method on Event (e.g. Event.past).
  7. Refactor the “upcoming” and “past” methods into simple scopes (remember scopes??).
  8. Put navigation links across the top to help you jump around.
  9. Extra Credit: Allow users to invite other users to events. Only allow invited users to attend an event.
  10. Push to Github.

Student Solutions

Send us your solution so we can show others! Submit a link to the Github repo with your files in it here using any of the methods listed on the contributing page. Please include your partner’s github handle somewhere in the description if they would like attribution.

Additional Resources

This section contains helpful links to other content. It isn’t required, so consider it supplemental for if you need to dive deeper into something


 

Step 3:

Active Record Callbacks

A brief look at the life-cycle of an Active Record object, from birth to destruction, and how you can hook into that for profit.

Active Record Callbacks

Introduction

Callbacks are a common way for you to execute code at specific times in the life cycle of an Active Record object, for instance just before it is created, after it is saved, or after it is destroyed. These can be very useful if you’ve got something to execute whenever an object hits one of those lifecycle points, like modifying the user’s email to be lowercase when creating her account. Callbacks are a way of saying something like “Hey Active Record, when you’ve finished creating a new User object, give me a call so I can run this method before anything else happens.”

This is a brief section on a useful topic. The Rails Guide reading provides good coverage of it, so my summary will be necessarily brief.

Points to Ponder

Look through these now and then use them to test yourself after doing the assignment

  • What is a callback used for?
  • What are the major lifecycle stages of an Active Record object?
  • How do you build an “around” callback?
  • How do you specify a particular action to run a callback for?

The Life Cycle of an Active Record Object

Callbacks provide hooks into specific points (either before, after, or sometimes “around”) in the life cycle of an object. Those life cycle moments are:

  • Initialization — When the object is first built OR whenever it is reloaded from the database and into memory (so any time you find it in a query).
  • Validation — whenever Rails checks if the object is valid. That could be when you’re trying to save it or if you’ve manually run the #valid? method.
  • Saving — The actual act of saving an already-built object to the database. This is triggered any time the object is saved, not just the first time it is created.
  • Creating — The creation and saving of a new object.
  • Updating — The updating of an existing object.
  • Finding — When you’ve searched for the object. Often gets triggered by Rails working with objects behind the scenes (e.g. when )

You often get three choices for callbacks. Not all object lifecycle steps support all callbacks, but the basic three are (using create as an example):

  1. before_create — Runs the method before the stated action
  2. after_create — Runs the method after the stated action
  3. around_create — A bit trickier. In this one, you will write a method which actually yield‘s at some point to the original action. That way you can have code before it and after it and YOU decide at which point the original action gets done. Not entirely common.

Using Callbacks

To use a callback, you need to “register” it at the top of your Model by using the appropriate method (e.g. before_create). You pass that method either a symbol which corresponds to a method name or you could just write the callback as a block then and there. Rails will hang onto that method and call it at the appropriate time. For example:

    # app/models/user.rb
    class User < ActiveRecord::Base
      before_create do |user|
        puts "about to create #{user.name}"
      end
      after_create :just_created

      protected
      def just_created
        puts "just created a user"
      end
    end

Specifying Callback Characteristics

Callbacks give you several options for narrowing down or selecting specifically when you want them to run. If you only want to run a callback when a particular controller action calls it, use the :on option, which takes either a single symbol or a full array, e.g. before_create :run_code, :on => [:create, :update].

You can also use conditional logic options :if and :unless to try a method before running callbacks, for instance:

    ...
    before_create :run_code, :unless => :method_is_true

    protected
    def method_is_true
      true
    end
    ...

Transaction Callbacks

Sometimes your Rails app will need to interact with an external application (which is inherently imperfect) as a part of the save process. Other times your save will involve juggling several balls at once and, if one fails, they all need to be rolled back. Typically these cases will involve wrapping your database save operation in a “transaction”, which means that either all the steps work or they all fail and are rolled back.

The committing of a transaction and its potential rollback if it fails are both lifecycle events that you can latch onto with callbacks, e.g. after_commit and before_rollback. This is uncommon, so consider it another one of those “just remember that it’s an option” type things.

Your Assignment

  1. Read through the Rails Guide on Callbacks
  2. Read this post from Samuel Mullen on guidelines for using callbacks.

Conclusion

Callbacks are useful and many, like :after_create and :before_destroy are pretty common. There’s no rocket science here, just a helpful concept.

Additional Resources

This section contains helpful links to other content. It isn’t required, so consider it supplemental for if you need to dive deeper into something


 

Project:

Advanced Associations

Exercise those association muscles to finish up the tutorial like a pro.

Project: Advanced Active Record Associations

Don’t forget to use Git to save your projects!

This final chapter of the RoR Tutorial will require you to have users following users.

Project: Ruby on Rails Tutorial

You’re on the last chapter! This one will require you to implement the final piece of functionality that makes microblogging what it is — allowing users to follow each other. With your now excellent knowledge of associations, you should be able to see that you’ll run into some naming issues (you won’t get too far with a User who has_many :users). It’s a many-to-many relationship that uses the SAME model for both sides (“self-referential”)!

As an added bonus, you’ll get a chance to touch a bit of AJAX when making it so the buttons for following don’t actually refresh the page with each push. Hooray learning!

Your Task

  1. You’ve got a User model. Write down how you would implement (and name) the associations necessary to make one user follow another. You’ll get a lot more out of the tutorial chapter if you think it through first.
  2. Do the Ruby on Rails Tutorial Chapter 12, “Following users”.

Additional Resources

This section contains helpful links to other content. It isn’t required, so consider it supplemental for if you need to dive deeper into something


 

Step 4:

Advanced Forms

Take what you know about forms and put rocket boosters on it. Don’t be afraid to make a form for anything.

Advanced Forms

Introduction

You learned about the basics of building forms in HTML and Rails in previous lessons and you can do a whole lot with that knowledge. But there are also cases when crafting a good user experience demands building a form that handles multiple things (e.g. model objects) at once. Users only like clicking the submission button once so you’d better be able to give them the simple experience they demand.

In this section, we’ll take a look at some of the options you have to make a form handle multiple model objects at once. You’ll also learn how to prepopulate a dropdown menu with objects.

Points to Ponder

Look through these now and then use them to test yourself after doing the assignment

  • How do you prepopulate a dropdown menu with data?
  • What is the difference between the #select_tag helper and the #select helper?
  • What format does the array you input to #options_for_select need to be in?
  • Why would you need to use a nested form?
  • What do you need to change in the model to allow nested forms to create new objects properly?
  • How do you whitelist the nested parameters properly in your controller?
  • Why can’t you just delete something by leaving a form field (e.g. a checkbox) blank (unchecked)?

Prepopulating select Tags with Collections

Rails provides you with a few handy ways of making dropdown menus which already contain data when the form is loaded (otherwise they’re not that useful).

Let’s say you want to build a New Post form for your blog but you want to be able to select who the author is from among your list of users. You will need to make a dropdown which submits the user’s ID as a part of your params hash. So you might populate @users in your posts controller:

    # app/controllers/posts_controller.rb
    ...
    def new
      @users = User.all
      @post = Post.new
    end
    ...

The bare HTML way is to build a bunch of <option> tags (inside your <select> tag). You could easily create these in your ERB code by just iterating over some collection, for instance if you’d like to select a post to view from a list of them.

    # app/views/posts/new.html.erb
    ...
    <select name="user_id">
      <%= @users.each do |user| %>
        <option value="<%= user.id %>"><%= user.name %></option>
      <% end %>
    </select>
    ...

This creates a dropdown list with each user’s name as an option. Your #create action will receive an attribute called user_id and you can use it to match an author to that post.

But Rails provides some less verbose ways of doing the same thing, namely using the #select_tag helper in conjunction with the #options_for_select helper. The #select_tag will create the surrounding tag while the #options_for_select gives #select_tag the array of options it needs.

#options_for_select expects a very specific input — an array of arrays which provide the text for the dropdown option and the value it represents. So options_for_select([["choice1",1],["choice2",2]]) creates a couple of option tags, one for each choice. This is great, because that’s exactly what #select_tag expects for its second argument. The only wrinkle here is that you need to convert your @users collection, which has full User objects, into a simple array with just name and value. That’s easy using #map:

    # app/controllers/posts_controller.rb
    ...
    def new
      @user_options = User.all.map{|u| [ u.name, u.id ] }
      @post = Post.new
    end
    ...

    # app/views/posts/new.html.erb
    ...
    <%= select_tag(:author_id, options_for_select(@user_options)) %>
    ...

So just pass #select_tag the name it should use for your chosen value and the collection and it will output the exact same thing!

If you want to avoid the whole options_for_select thing altogether and your form is designed to build a model instance (e.g. a Post object), just use the more generic #select helper in your view:

    # app/views/posts/new.html.erb
    ...
    <%= select(:post, :author_id, @user_options) %>
    ...

You still need to pass it the :post parameter (which indicates that your form is building a Post object) so the select tag can get the name right… in this case, it will name the tag <select name="post[author_id]" id="post_author_id">. That means (remember!) that the author_id attribute will show up in your params nested under the post hash.

The :author_id input to the #select helper above represents not just what the chosen value should be called (as in the #select_tag) but also which column name it represents in your original (in this case Post) model. This may be a bit annoying at first since you can’t just name your choice whatever you want.

If you have a #form_for form scoped under the f variable, you don’t need to pass the :post symbol above (it gets it from f), so could instead use:

    # app/views/posts/new.html.erb
    ...
      <%= f.select(:author_id, @user_options) %>
    ...

It took a bit of time for us to get here, but hopefully you can now see how straightforward this method is for generating a potentially large dropdown.

You’ll use dropdowns a lot to populate the association of a model, e.g. the author of a Post. In the next section, we’ll talk about how to actually create both model objects from within a single form.

Note: If you’ve used the simple_form gem, it has its own way of handling collections of objects which is a bit different (supposedly simpler) than this.

Nested Forms

You’ve got a form for creating one of your User objects (say for your Amazon.com clone application) but you also want to make that form create one or more ShippingAddress objects (which a User can have many of). How do you get that one form to create both so your user doesn’t get stuck clicking a bunch of form submits?

This is a multi-part process. It involves your controller, view, models and routes… the whole MVC team! The gist of it is that your form will submit the main object (e.g. the User) as normal but it will sneak in a bunch of attributes for the other object(s) you want to create (e.g. ShippingAddress object(s)). Your model will have to be ready for this. It will create not just the original User object but also the nested objects at the same time.

As you can imagine, it’s important to get the names and parameters properly listed so all this magic can happen behind the scenes.

We’ll do a broad overview of the process here:

  1. You will need to prepare the User model so that it knows to create one or more ShippingAddress objects if it receives their attributes when creating a normal User. This is done by adding a method to your User model called #accepts_nested_attributes_for which accepts the name of an association, e.g:
        # app/models/user.rb
        class User < ActiveRecord::Base
          has_many :shipping_addresses
          accepts_nested_attributes_for :shipping_addresses
        end
    
  2. Make sure you’ve allowed your params to include the nested attributes by appropriately including them in your Strong Parameters controller method. See the reading for examples of how to do this.
  3. Build the form in the view. Use the #fields_for method to effectively create a #form_for inside your existing #form_for form.

There are a couple new aspects to this process. You saw #fields_for in the Basic Forms lesson but it probably has new meaning to you now. It’s basically how you create a form within a form (which should make sense since it’s actually used behind the scenes by #form_for). In this example, we might create three “sub-forms” for ShippingAddress objects by using our association, e.g.

    <%= form_for @user do |f| %>
      ...
      <% 3.times do %>
        <%= f.fields_for @user.shipping_address.build do |addy_form| %>
          ...
          <%= addy_form.text_field :zip_code %>
          ...
        <% end %>
      <% end %>
      <%= f.submit %>
    <% end %>

Note that we could (and should) also have built the new shipping_address objects in the controller instead of the view; it’s just for demonstration purposes here.

The #accepts_nested_attributes_for method is fairly straightforward and the docs should be helpful.

The reading will cover more about whitelisting the nested parameters.

Deleting Nested Form Objects

You can also have your form destroy nested forms by first setting the :allow_destroy option to true for the #accepts_nested_attributes_for method, e.g. accepts_nested_attributes_for :shipping_addresses, :allow_destroy => true. Now, any time you want to destroy a ShippingAddress object from a User’s form, just include the key _destroy => 1 in the submitted parameters for that ShippingAddress.

Many-to-Many Relationships

If you’ve got a has_many :through relationship, you’ll likely need to go one additional step further by specifying that each side of your relationship is the inverse of the other. It’s detailed in this blog post from ThoughtBot.

Designing Your Own Forms

Sometimes, despite all the nice helpers Rails gives you, you just want to do something that’s not standard. You should first wonder whether this is the easiest and most straightforward way to do things. If it passes the smell test, then go ahead and build your form.

It’s often easiest (and good practice while you’re learning) to start with the most basic of HTML forms. If you don’t understand what’s going on in the basic HTML (and remember to include your CSRF token), then you’ll be hopeless trying to use helpers. Once you’ve got a good handle on things, gradually bring in the Rails helpers like #form_tag and #form_for.

Don’t get discouraged if you get some real head-scratcher moments when building nonstandard forms. It just takes some experience to feel comfortable. And if things are too out of hand, you may need to re-evaluate your approach (what exactly are you hoping to accomplish with your complex form?) and start again.

Simple Form

simple_form is a gem by Platformatec which can really make your life easier (if you aren’t doing anything too crazy). It provides lots of user-friendly features for building forms and is in wide use today. We won’t cover it explicitly here (though it’s used in the tutorial we’re following).

It’s up to you to check out the documentation and start using it in your own applications as desired.

Miscellania: Blank Submissions That Mean Delete

Sometimes, for a record that already exists, you want to either deselect a dropdown or check none of your checkboxes but you want this to indicate that the associated fields should actually be set to nil. Usually, though, if you submit the form it will include none of the fields and your back end won’t know that you actually wanted to remove those fields so nothing will happen. How do you get around it?

Try making a hidden field in your form (or nested form) that has the same name as your checkboxes or dropdown but only contains the value "". Now you’ll get that attribute to show up in your params hash no matter what and you can handle deleting the records however you’d like appropriate.

Sometimes Rails helper methods will do it for you, but make sure you know what your form is actually submitting (if anything) if you deselect all options!

Your Assignment

  1. Read the Rails Guide on Forms sections 3.3, which covers populating a form with a collection of objects.
  2. Read the Same Rails Guide on Forms section 9, which covers accepting nested form data.
  3. Read the Same Rails Guide on Forms section 7, which covers the parameter conventions for nested forms.
  4. Read this blog post from Peter Rhoades on working with nested forms. The example covers a lot of the things we’ve gone over so far, so follow along. Also note how he does the whitelisting of nested attributes in Rails 4.

Conclusion

We’ve covered two of the more common use cases for complex forms — pre-populating a form with objects and creating multiple objects with a single form. At this point, even if you’re uncomfortable, you should have all the tools you need to work through creating a form. We’ll get your hands dirty in the project, have no fear.

The best part? This is more or less the most complicated conceptual stuff with learning Rails. Actually, it’s not even really Rails-specific… once you’re comfortable with the HTML that forms require and how the parameters get submitted to your controller, mapping that to the correct Rails conventions or helpers is the easy part. So everything you’ve learned may just be transferrable to every form you’ll ever make.

Additional Resources

This section contains helpful links to other content. It isn’t required, so consider it supplemental for if you need to dive deeper into something


Project:

Building Advanced Forms

Build an airline flight signup system, which is a nest of interesting complexities

Project: Advanced Forms

Don’t forget to use Git to save your projects!

Project: Flight Booker

In this project, you’ll get a chance to tackle some more advanced forms. This is the kind of thing you’ll have to work with when handling user orders for anything more complicated than an e-book. In this case, we’ll build the first three steps of a typical checkout process for booking a one-way flight:

A typical airline booking flow:

  1. Enter desired dates / airports and click “Search”
  2. Choose from among a list of eligible flights
  3. Enter passenger information for all passengers
  4. Enter billing information

Step 4 would be done via integration of something like Paypal, via a gem or an SDK or Stripe.

Your Task

We’ll be creating a one-way flight booker. You’ll get lots of practice populating and building dropdown menus, radio buttons, and nested submissions. Let the fun begin!

This project will require you to seed your database, so use your db/seeds.rb file to write the code necessary to populate Airports and Flights. One trick for toy apps like this (don’t do it for production!) is to make your seeds file ::delete_all items from each table in your database and then completely repopulate them. That way, when you create a new model or change how you want your sample data set up, you can just update the seeds.rb file and rerun $ rake db:seed.

Setup

  1. Think through the data architecture required to bring this to life.
  2. Create a new Rails app, e.g. odin-flight-booker, and a new Git repo
  3. Modify the README to explain what you’re doing and link back to the project here.

Screen 1: Search

For the first screen, you’ll need a dropdown that lists a possible “From” airport and a possible “To” airport. just assume all flights fly between SFO and NYC. You’ll need to set up a dropdown menu that contains all eligible flights.

This requires relatively vanilla forms that happen to be prepopulated with collections of data. Working with dates will cover a bit of new ground for you.

  1. Create an Airport model (which basically just needs an airport code like “SFO” or “NYC”) and use the db/seeds.rb file to create several of them.
  2. Create a Flight model (with the start and finish airport ids, start datetime, and flight duration).
  3. Set up associations so you can ask Flight.first.from_airport, Flight.first.to_airport and get back Airport objects. Same for Airport.first.departing_flights and Airport.first.arriving_flights, which should return a list of flight objects.
  4. Seed your database with flights.
  5. You will search and view results in the same page. Set up your FlightsController and routes to make the Index page (/flights) the root route.
  6. Create your search form on the /flights index page to submit using a GET (not POST) request back to the same URL.
  7. The home page should contain four dropdown menus — a list of FROM and TO airports, number of passengers (1-4), and a DATE dropdown for the flight date. The DATE dropdown should only include dates that have existing flights. Don’t worry about restricting the contents of the FROM/TO dropdowns — you’d normally use Javascript — so just allow the user to select any airport. See this SO post on formatting date in dropdown lists, this quickie SO post on selecting months/years, and the Rails DateHelper API Docs for some help with creating Date selects.

Screen 2: Pick a Flight

Once search results are returned, the user just needs to choose from among the eligible flights.

  1. Once the user has submitted the form, your controller should detect the additional query parameters (e.g. from /flights?from_code=SFO&to_code=NYC&date=20131215&num_tickets=2) and should pull the flights which match that criteria from your database through smart querying model methods.
  2. Send those flights back to your app/views/flights/index.html.erb view. Make it so that the view will display the results below (if present).
  3. Don’t get rid of the search dropdowns — keep the search form active at the top so the user can try running a new search.
  4. The search results will need to be in their own form — this time for selecting which of the flights the user would like to choose. Each returned flight should render with a radio button next to it. The user will select which flight to submit and move onto the next step. The form should submit to the #new action of the BookingsController you’re about to create using another GET method. You’ll need to include a hidden field that contains the number of passengers.

Screen 3: Passenger Information

Once the user has submitted their chosen flight, it’s time to take their booking information. You’ll need a form to submit the Booking and also create a Passenger object for each passenger who needs a ticket. Don’t worry about creating a separate “Ticket” object, we’ll assume the airline will issue those once the booking is verified.

  1. Create and migrate the Booking model
  2. Create and migrate the Passenger model (just keep the information simple — name and email only)
  3. Set up associations between Bookings, Passengers, and Flights.
  4. Create a BookingsController and appropriate routes.
  5. Set up your #new action, which should have received the flight ID and passenger number parameters, and use it to help render a form for a new booking which displays the currently chosen date, airports, flight ID and a set of fields to enter personal information for each passenger. You’ll want to create a new blank Passenger object in your controller for each passenger, and then use #fields_for in the view to set up the sub-forms.
  6. Try submitting the form and check out the parameters in your server.
  7. You’ll need your #create action to create a new Booking (linking it to the appropriate Flight) but it will also need to accept the nested attributes for each of the Passenger objects and create a new Passenger from them. Be mindful of whitelisting the nested parameters as well.
  8. Go to the Booking model and implement the #accepts_nested_attributes_for method. See the Rails API for examples and this SO post for another.
  9. Once your form is successfully submitted, render the booking’s #show page which displays the booking information (flight and passenger information).
  10. Make sure your ticketing flow is working properly. Good work!

Student Solutions

Send us your solution so we can show others! Submit a link to the Github repo with your files in it here using any of the methods listed on the contributing page. Please include your partner’s github handle somewhere in the description if they would like attribution.

Additional Resources

This section contains helpful links to other content. It isn’t required, so consider it supplemental for if you need to dive deeper into something