jbk
jbk

Reputation: 2200

How should I set initialise instance variables on an ApplicationRecord object?

My app creates a QuoteRequest object and upon save of that object to the db I want it to create a Quote.new object (QuoteRequest has_one Quote). In a new Quote instance I want to set some instance variables, which various methods related to a Watir crawler will use, then the result of that Watir crawl & scrape will #save thus persist the Quote object to the db.

I would like to set the Quote instance variables in an initialize method as soon as the object is created, but this is creating issues with the fact that it inherits from ApplicationRecord this there's an initialization conflict.

What is the correct way to instantiate an ApplicationRecord object with variables, without conflicting with the Rails library initialization code?

Upvotes: 4

Views: 1922

Answers (2)

m. simon borg
m. simon borg

Reputation: 2575

If you just want to set one instance variable without any extra logic at initialize then Vijay's answer is a good one. However you can have more flexibility than just being restricted to attr methods.

As with all Ruby, you can always take advantage of super if you're maintaining the right interface to the super method. ActiveRecord::Base#initialize only takes one optional argument, a hash of attributes and values. It also yields itself to a block if a block is given. https://github.com/rails/rails/blob/master/activerecord/lib/active_record/core.rb#L313

So if you wanted to set an instance variable at initialization without also defining any public access methods, you can:

class Quote < ApplicationRecord
  def initialize(attributes = nil, &block)
    @my_var = attributes.delete(:my_var) if attributes
    super
  end
end

quote = Quote.new(my_var: 'value')
quote.instance_variable_get(:@my_var) # => "value"

You can also perform more complex operations. Assume you have User has_many :posts and Post belongs_to :user, User has a :name and Post has a :title.

class User < ApplicationRecord
  def initialize(attributes = nil, &block)
    title = attributes.delete(:first_post_title) if attributes
    super
    posts.build(title: "#{name}'s first post: #{title}") if title
  end
end

user = User.new(first_post_title: 'Hello, world!', name: 'Matz')
user.posts.first # => #<Post:xxxxxxxxxxx title: "Matz's first post: Hello, world!">

Hopefully that demonstrates how flexible you can be even with ActiveRecord objects, and sometimes callbacks are a little too complicated in implementation and restrictive in use when super can work just fine and even better when used appropriately.

Upvotes: 4

Vijay Agrawal
Vijay Agrawal

Reputation: 1683

For the properties of Quote you don't want to save in DB, create attr_reader (or attr_accessor). Suppose your Watir method needs var1 and var2 objects, then your Quote should look like this:

class Quote < ApplicationRecord
  attr_reader :var1, :var2
end

And then in after_create callback of QuoteRequest you should simply pass these variables as key-value pair like any other property.

obj1 = Var1.new
obj2 = Var2.new
quote = Quote.new(var1: obj1, var2: obj2, ....)`

Now you can access these by calling quote.var1 and quote.var2. Calling save on quote would persist all the properties which have corresponding columns in db except var1, var2.

Upvotes: 1

Related Questions