How do I initialize attributes when I instantiate objects in Rails?

11,764

Solution 1

Generate a migration that adds invoices_number column to users table. Then in Invoice model write this:

class Invoice < ActiveRecord::Base
  belongs_to :user, :counter_cache => true
  ...
end

This will automatically increase invoices_count attribute for user once the invoice is created.

Solution 2

how about this:

class Invoice < ActiveRecord::Base
  ...
  def initialize(attributes = {})
    super
    self.number = self.client.invoices.size + 1 unless self.client.nil?
  end
end

Solution 3

first of all, you don't need to use the attributes collection, you can just do self.client_id. Better yet, as long as you have a belongs_to :client in your Invoice, you could just do self.client.last_invoice_number. Lastly, you almost always want to raise an exception if an update or create fails, so get used to using update_attributes!, which is a better default choice. (if you have any questions about those points, ask, and I'll go into more detail)

Now that that is out of the way, you ran into a bit of a gotcha with ActiveRecord, initializer methods are almost never the right choice. AR gives you a bunch of methods to hook into whatever point of the lifecycle you need to. These are

after_create
after_destroy
after_save
after_update
after_validation
after_validation_on_create
after_validation_on_update
before_create
before_destroy
before_save
before_update
before_validation
before_validation_on_create
before_validation_on_update

What you probably want is to hook into before_create. Something like this

def before_create
  self.number ||= self.client.last_invoice_number + 1 unless self.client
end

What that will do is it will hit up the database for your client, get the last invoice number, increment it by one, and set it as its new number, but only if you haven't already set a number (||= will assign, but only if the left side is nil), and only if you have set a client (or client_id) before the save.

Solution 4

Here is some useful discussion on after_initialize per Jonathan R. Wallace's comment above: http://blog.dalethatcher.com/2008/03/rails-dont-override-initialize-on.html

Share:
11,764
Charles Enrick Cajan
Author by

Charles Enrick Cajan

Updated on June 05, 2022

Comments

  • Charles Enrick Cajan
    Charles Enrick Cajan almost 2 years

    Clients have many Invoices. Invoices have a number attribute that I want to initialize by incrementing the client's previous invoice number.

    For example:

    @client = Client.find(1)
    @client.last_invoice_number
    > 14
    @invoice = @client.invoices.build
    @invoice.number
    > 15
    

    I want to get this functionality into my Invoice model, but I'm not sure how to. Here's what I'm imagining the code to be like:

    class Invoice < ActiveRecord::Base
      ...
      def initialize(attributes = {})
        client = Client.find(attributes[:client_id])
        attributes[:number] = client.last_invoice_number + 1
        client.update_attributes(:last_invoice_number => client.last_invoice_number + 1)
      end
    end
    

    However, attributes[:client_id] isn't set when I call @client.invoices.build.

    How and when is the invoice's client_id initialized, and when can I use it to initialize the invoice's number? Can I get this logic into the model, or will I have to put it in the controller?