Querying another model from inside a model created by FactoryGirl - ruby-on-rails-3

Just starting with FactoryGirl. I have a Model named Subscription. It has a method 'set_price` which apparently does some calculations. In order to do so, it has to ask another model for some values:
def set_price
base_price = Option.find_by_key(:base_price).value.to_f
# […] some calculations
end
When running my specs I get:
NoMethodError:
undefined method `value' for nil:NilClass
Which is quite logical since I didn't (yet?) create any Options.
Is FactoryGirl suited for this? Do I have to create Option fixtures in this case? Or just mock it?

This will fail because there are no Options in the database. You can either create the option factory before calling set_price in the test (you'll need to make sure find_by_key(:base_price) will return your factory created option in this case), or you can as you say use a mock:
option = mock_model('Option', :value => 1)
Option.stub(:find_by_key).and_return(option)
The mock has the advantage that it will not touch the database, but it's potentially more brittle.

Related

In a Rails ActiveRecord model, is using after_initialize callbacks a very bad idea?

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.
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
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

RAILS 3 - Transactions in controllers

I have an example Action in a Controller.
def some_action
product = Product.new
product.name = "namepro"
if product.save
client.update_attribute(:product_id,product.id)
end
end
How to add transactions for this code? I try with this example code:
def some_action
**transaction do**
product = Product.new
product.name = "namepro"
if product.save
client.update_attribute(:product_create,Time.now)
end
**end**
end
But it produces this error:
undefined method `transaction'
I read about using transactions in Controllers is a bad practice but I don't know why is the reason (http://markdaggett.com/blog/2011/12/01/transactions-in-rails/)
In the example, if product has been created and saved and the client update fail... Rails must not do nothing.
thanks.
You can use a transaction in a controller if you really want to. As you noted, it's bad practice, but if you want to do it, just call Product.transaction do instead of transaction do. transaction is a class method on ActiveRecord::Base, so you need to call it on an ActiveRecord-derived class. Any model class in your application will do (nit-picking caveat: if you are connecting to different databases for different models, that may not be true...but you're probably not doing that).
The reason this is a bad practice is that it doesn't properly separate concerns according to the MVC paradigm. Your controller shouldn't be so concerned with your data persistence implementation. A better approach would be to add a method to Product. Maybe something like this:
def save_and_update_create_time
transaction do
if save
client.update_attribute(:product_create, Time.now)
end
end
end
Then instead of calling product.save in your controller, call product.save_and_update_client_create_time. You may need to pass client to that method too; it's unclear from your code where client comes from. If it's an attribute on product, then the method above should work.
There are better, more Railsy ways to do this, too, especially if a product knows about its client without needing any controller data. Then you can just use an after_save callback, like this (add to Product class):
after_save :update_client
private
def update_client(product)
product.client.update_attribute(:product_create, Time.now)
end
Then every time a Product is saved, the field on the associated client will be updated. You'll possibly have to introduce some code to check for the existence of a client first.
The benefit to using callbacks, besides cleaner code, is that the entire callback chain runs in a single transaction along with the save; you don't need to create the transaction manually. You can read more about callbacks in the Rails documentation.

Scope staticly reference another model resulting table not found

I use in-memory database for testing. The schema is reloaded in every test.
Recently my rspec complains that a table is not found. The reason is that a scope is referencing another model at load time.
class Item
scope :public, where(:store_id => Store.public_store_ids())
class Store
def self.public_store_ids
self.public.pluck(:id)
The problem is that, during the initializing when item model is loaded in the memory, the schema for store table has not been loaded yet, but my scope will try to query the public store ids, which results in the "table not found" error.
How can I make my item scope to evaluate dynamically at runtime? I didn't want to use join because it can slow down my query, but would it be my only way?
I realized that I can just make it a class method so it is evaluated at run time
def self.public
store_ids = BeautyStreet::Store.public_store_ids()
where(:store_id => store_ids)
end

How to pass in a block rather than a variable to a method in ruby that wants a variable

Using Rails3, the awesome FactoryGirl gem has a method create_list that takes a strategy, a number of times and then a hash of values to pass into the strategy. (copied from https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md)
twenty_year_olds = FactoryGirl.create_list(:user, 25, date_of_birth: 20.years.ago)
Suppose that instead of passing "20.years.ago", I want to pass a lambda (or Proc) that gets executed once for each time that that the variable gets read. Is this possible in Ruby or FactoryGirl, or would FactoryGirl have to check each param to see if it's a lambda.
dob_dynamic = -> { random_dob some_param }
twenty_year_olds = FactoryGirl.create_list(:user, 25, date_of_birth: dob_dynamic )
When I did something like this, I got:
raise ActiveRecord::AssociationTypeMismatch, message, Object expected, got Proc
I worked around this issue by doing something like this:
# pass i, b/c times always passes index to proc
# dob_dynamic.() invokes dob_dynamic
create_user = -> i { create :user, date_of_birth: dob_dynamic.() }
25.times &create_user
Any better way to do this?
From what I understand of you want, you should be able to create a new factory (probably using factory inheritance or a trait) and put the randomising code in that factory. If you use a block in a factory it will be reevaluated each time, so you can easily use create_list.
I posted the feature request for the create_list to support taking a proc. For the time being the hook after(:create) is great for code like this, as original posted:
# pass i, b/c times always passes index to proc
# dob_dynamic.() invokes dob_dynamic
create_user = -> i { create :user, date_of_birth: dob_dynamic.() }
25.times &create_user

Rspec test if an attribute was updated

My model has a method that will update several attributes in a model from a remote resource. I want to test that with Rspec, but cannot find how to test whether a field was created or updated.
Some pseudocode to explain the question
def update_from_remote
attributes = {}
fields.each_key do |field_name|
attributes[field_name] = scrape_from_remote field_name
end
update_attributes(attributes)
end
In reality this code is a lot more complex, but that will only clutter my question. A create_from_remote is rather similar, only that it does not call update_attributes but simply sets them and then saves the object.
I would like to test this. What I want, is a spec that tests whether the fields were updated or filled:
it 'should fill or set all local attributes on a new profile' do
#how to test if a list of fields were updated in the Database?
end
it 'should update all local attributes on an existing profile' do
#how to test if a list of fields were created in the Database?
end
I am using mongoId, but AFAIK that should not make much difference.
I should have dug deeper in the MongoID documentation. There is a chapter on Dirty tracking which can be used:
it "should be updated"
#something.field.should be_changed
end