2

Let's suppose we have this model

class Account < ActiveRecord::Base
  after_initialize :set_name

  def set_name
    self.name = ‘My Account’
  end
end

Now I want run a query that returns only some attributes of the model but not all of them, in particular is not returning the "name" attribute that it is used in after_initialize callback

Account.group(:name).select("count(*), id").first

And then this execution raises the following error because the set_name callback uses an attribute that has not been "loaded" or selected into the records returned by the query.

ActiveModel::MissingAttributeError: missing attribute: name

Fortunately for some particular cases I can execute the same sql query without using the Account model at all to get the desired result

sql = Account.group(:name).select("count(*), id").to_sql
ActiveRecord::Base.connection.execute(sql).first
=> #<Mysql2::Result:0x00000106eddbc0>

But the point is, what if I want to get Account objects instead of a Mysql2::Result one? Should the .select method return "complete" objects with all their attributes (e.g. filling the missing columns with Nil's)? Or is just a very bad idea to use after_initialize callbacks for our ActiveRecord models? Of course we can also add some code in the callback to check if the property exists or not but, in my opinion, this is unnatural or sounds weird working in an OO language.

Rafa Paez
  • 4,820
  • 18
  • 35
  • Please, read carefully the question because is not so simple and I believe the answer is not so trivial. Let me know your thoughts. – Rafa Paez Feb 15 '14 at 03:52

2 Answers2

3

Most uses of after_initialize can be (and SHOULD be) replaced with defaults on the corresponding database columns. If you're setting the property to a constant value, you may want to look into this as an alternative.

EDIT: if the value isn't constant, a call to has_attribute?(:name) will guard against this error - ActiveModel::MissingAttributeError occurs after deploying and then goes away after a while

Community
  • 1
  • 1
Matt Jones
  • 544
  • 3
  • 5
  • Imagine that the callback method check a condition based on the name field instead of putting a default name. We still have the same problem `if self.name == .... ` will raise and exception. – Rafa Paez Feb 15 '14 at 03:39
0

No, it is not a bad idea, in fact I use it very often at work. The valid use case for this would be when you want code to run before you try and do anything with the object. Here is a breakdown of some of the filters offered.

# Before you intend to do anything with the object
after_initialize

# Before you intend to save the object
before_save

# After you've saved the object
after_save

# Before you save a new record
before_create

# After you create a new object
after_create
OneChillDude
  • 7,856
  • 10
  • 40
  • 79
  • 2
    Because of the "Before you intend to do anything with the object" if you run a query like I shown as example, it will raise the error. – Rafa Paez Feb 15 '14 at 03:48