0

I'm following a teamtreehouse course. One of the things that are being re-factored is the ability to limit the user_id of a status that gets posted, to that of the current logged in user only. We already established a filter that they must be logged in to post already so the only thing we're trying to do here is prevent users posting a status as another user.

A user has_many :statuses

This is what the Statuses#create method looks like in the controller

 def create
    @status = Status.new(params[:status])

    respond_to do |format|
      if @status.save
        format.html { redirect_to @status, notice: 'Status was successfully created.' }
        format.json { render json: @status, status: :created, location: @status }
      else
        format.html { render action: "new" }
        format.json { render json: @status.errors, status: :unprocessable_entity }
      end
    end
  end

The thing we're changing is @status = Status.new(params[:status])

Now we're using Devise for our User model, so the current user is represented by current_user

The following change is created: @status = current_user.statuses.new(params[:status])

This statement CONFUSES the heck out of me

I tried to figure out what was going on here.

The following is my exploration of the process with the rails console:

irb(main):003:0> User.first.statuses
  User Load (0.5ms)  SELECT "users".* FROM "users" LIMIT 1
  Status Load (0.1ms)  SELECT "statuses".* FROM "statuses" WHERE "statuses"."user_id" = 2
=> [#<Status id: 2, content: "this is a test status", created_at: "2013-07-23 20:53:35", updated_at: "2013-07-23 20:53:35", user_id: 2>, #<Status id: 3, content: "test2", created_at: "2013-07-23 21:34:59", updated_at: "2013-07-23 21:34:59", user_id: 2>]
irb(main):004:0> User.first.statuses.class
  User Load (0.6ms)  SELECT "users".* FROM "users" LIMIT 1
  Status Load (0.3ms)  SELECT "statuses".* FROM "statuses" WHERE "statuses"."user_id" = 2
=> Array
irb(main):005:0> User.first.statuses.new.class
  User Load (0.5ms)  SELECT "users".* FROM "users" LIMIT 1
=> Status(id: integer, content: text, created_at: datetime, updated_at: datetime, user_id: integer)
irb(main):006:0> Array.new
=> []
irb(main):007:0> Array.new.class
=> Array
irb(main):008:0> 

Why when I found that the class of the object (as expected) returned from User.first.statuses was an array, but the class of .new being called on the array generated a new Status. How/why does this work? When I call Array.new, i get an array, are we not calling new on an array object?

Senjai
  • 1,811
  • 3
  • 20
  • 40
  • Check out Array.superclass vs User.first.statuses.superclass – Shawn Balestracci Jul 27 '13 at 02:07
  • Oh!, so the superclass of Array is object, while the superclass of the array returned by User.first.statuses is an ActiveRecord::Base. How did it do that? Did it make a separate array class that inherited from ActiveRecord::Base? – Senjai Jul 27 '13 at 02:14
  • http://stackoverflow.com/questions/1529606/how-do-rails-association-methods-work explains it well – Shawn Balestracci Jul 27 '13 at 02:25

1 Answers1

2

The trick is, current_user.statuses does not return an array. Sure it looks like an array, it even will tell you it's an array if you try current_user.statuses.class, but that's after it's been lazily evaluated. What's actually returned is an ActiveRecord::Relation object, and you can confirm this for yourself by trying current_user.statuses.foo in a rails console; you'll get a message like undefined method 'foo' for #<ActiveRecord::Relation:0x007fba7d603918>. ActiveRecord::Relation is a really helpful part of ActiveRecord, which manages the scope for you while you chain objects together, and doesn't actually perform a query against the database until you ask for something that's not another ActiveRecord::Relation.

Ryan Bates explains this fairly well in his Railscast, and here's a blog that discusses ActiveRecord::Relation vs. Arel which also discusses this point.

carpeliam
  • 6,691
  • 2
  • 38
  • 42