1

I am going though the tutorial (which I must say is a excellent resource) and I don't quite understand the following:

In section 6.3.1 we create a password_digest column in the db via the creating and running a migration script via :

rails generate migration add_password_digest_to_users password_digest:string
bundle exec rake db:migrate
bundle exec rake db:test:prepare
bundle exec rspec spec/

Then on the rails console I am able to instantiate a user model object and set password_digest on it :

irb(main):007:0> @user = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil,     password_digest: nil>
irb(main):008:0> @user.password_digest = "zzzz" => "zzzz"
irb(main):009:0> @user.password_digest => "zzzz"   

However I can not see a password_digest property on the User model class definition :

class User < ActiveRecord::Base
  attr_accessible :email, :name

  before_save { |user| user.email = email.downcase}

  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i

  validates :name, presence: true, length: {maximum: 50}
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX}, uniqueness: {case_sensitive: false}
end

I imagine Rails is doing some magic under the covers, would someone mind explaining exactly what it's doing?

Thanks!

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • That's strange - you can assign a value to the attribute being not white-listed. Usually you need to add some `devise` call or at least `attr_accessible :password_digest`... – shybovycha May 21 '13 at 22:43
  • @shybovycha Isn't attr_accessible for mass assignment, which this isn't? – Dave Newton May 21 '13 at 22:56
  • `However I can not see a password_digest property on the User model class definition` What are you confused about? Are you expecting a reference to the new column to show up in the model somewhere? BEcause that's not how it works. – Alex Wayne May 21 '13 at 22:56
  • You don't "see" DB properties in the AR class unless you explicitly need to do something with them (like make them accessible) or they're an association. Otherwise their methods (e.g., setter) are created dynamically. – Dave Newton May 21 '13 at 22:58
  • @DaveNewton `attr_accessible` defines getter and setter for the field. And yeah, it allows mass assignment as well. Proof: http://stackoverflow.com/a/3136500/330471 – shybovycha May 21 '13 at 23:07
  • I see, that is what I wasn't clear on I guess, that what's in the db is not automatically added to the model objects (btw..what does AR stand for?) unless I explicitly add them as I need to do something on them....thx! – Yashika Lamahewa May 21 '13 at 23:08
  • @shybovycha I know it allows mass assignment, that's what I said-but this is not an example of that.. It does not create accessors, which is not what the link says either, the link correctly states what it does. The accessors for DB properties are created by Rails. – Dave Newton May 21 '13 at 23:11
  • @YashikaLamahewa They *are* automatically added to the model-that doesn't mean they appear in the source code. – Dave Newton May 21 '13 at 23:12
  • @shybovycha For more info, [see the source](https://github.com/rails/rails/blob/3-2-stable/activemodel/lib/active_model/mass_assignment_security.rb) – Dave Newton May 21 '13 at 23:20
  • @YashikaLamahewa AR stands for ActiveRecord. – Dave Newton May 21 '13 at 23:20

2 Answers2

1

You are right - what's actually going on here is rails magic behind the scenes.

Whenever you have a descendant of ActiveRecord::Base ActiveRecord will look at the database table for that class and automatically create accessors for you - they won't show up in the class definition. This seems crazy if you're coming from a language like C# where you had to do this kind of stuff manually before.

What ActiveRecord is doing (this is a very watered down explanation, the actual thing it does much more complicated) is kind of sticking the following code in your class:

class User < ActiveRecord::Base
  def password_digest
    @password_digest
  end

  def password_digest=(val)
    @password_digest = val
  end

end

The other thing to note is that it doesn't just do the creation of an attribute getter and setter for you - it mixes in some type casting based on the type of the column. Check out this question for more info and some possible gotchas.

The net result of this is actually kind of a bonus, and one of the reasons I like rails: you define the column once in your database, and you get it put into your model class for free.

This pattern is common to Rails though, and you'll see it often. If you are still learning Ruby or the Rails framework and you aren't 100% sure where something comes from, don't be afraid to look more closely - so-called Rails 'magic' occurs frequently and it takes some time to not be surprised. I had this experience when I first moved to Rails from other languages.

Community
  • 1
  • 1
TreyE
  • 2,649
  • 22
  • 24
0

Here are a couple of ways that a class can have a member variable that you cannot see in the class definition:

class ActiveRecord
  def password_digest=(val)
    @x = val
  end
  def password_digest
    @x
  end
end


class User < ActiveRecord
end

me = User.new
me.password_digest = "hello"
puts me.password_digest   #=> "hello"

Created dynamically at run time:

class User
end

User.class_eval do 
  attr_accessor :password_digest
end

me = User.new
me.password_digest = "hello"
puts me.password_digest   #=> "hello"

The problem I found with the rails tutorial is that:

1) It is extremely boring.

2) Because all you do is copy code.

Congratulations on getting to Chapter 6!

7stud
  • 46,922
  • 14
  • 101
  • 127
  • Thanks but not sure if this clears my confusion.. 1) your describing inheritance ... but the password_digest property only became available on the model object after I added it to the db via db migrate script. 2) I don't do any adding of properties at runtime, is rails doing this for me on start up based on what's define in the db? – Yashika Lamahewa May 21 '13 at 23:04
  • @YashikaLamahewa Yes, it's adding methods to the model based on what's in the DB. – Dave Newton May 21 '13 at 23:16