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
Related
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
I learned that is this how to access a model from other controller,
var book = Alloy.Models.instance('book');
And this is how to access a property of a model,
var name = book.get('name');
However in the console,the name logs [INFO] : { } , meaning this doesn't get its property value, and ofcourse the model has already a data saved on it. Thanks for your help!
You may have to fetch the collection first:
var books = Alloy.Collections.book;
books.fetch();
This will load all the models from the collection so you can use them.
although the above works, there are a few addtional points here.
the call is asynchronous in most cases so you should be getting the model in a callback which is not presented in the code above.
I dont know if fetching the collection everytime you want a model is the correct approach either? If the collection already exists you just need to get the model from the collection just using the id.
depending on the exact use case, you might just want to pass the model as a parameter from one controller to the next
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.
I'm just starting to learn ActiveRecord, and I am just trying out little things to figure out how everything works. I just tried the following code on the following sqlite3 database.
Ruby:
class Balances < ActiveRecord::Base
def initialize
#balance = 50
update_attribute(:balance, #balance)
end
def withdraw amount
update_attribute(:balance, #balance-amount)
end
end
SQL:
CREATE TABLE balance(
balance 50
);
When I write:
balance = Balances.new
I Get:
NoMethodError: undefined method `delete' for nil:NilClass
from /Users/Solomon/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.3/lib/active_record/attribute_methods/write.rb:28:in `write_attribute'
from /Users/Solomon/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.3/lib/active_record/attribute_methods/dirty.rb:67:in `write_attribute'
from /Users/Solomon/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.3/lib/active_record/attribute_methods/write.rb:14:in `balance='
from /Users/Solomon/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.3/lib/active_record/persistence.rb:180:in `update_attribute'
Why is this? Am I doing something wrong?
I notice several things:
The class name should be Balance (capitalized, singular). The table name in the database will be lower case, plural. Eg, balances
Don't define an initialize method for an ActiveRecord model. Instead use after_initialize callback. A post. Also, the Rails docs.
Added Also, the file name for the model should be balance.rb (lower case, singular)
Added some more You probably don't want to change the balance of the record back to 50 every time an instance of the record is initialized. -- That's what your example is currently doing. If you want to set the opening balance of new records in the database to be 50, then use the "before_create" callback.
Remember that ActiveRecord model classes are associated with, but different from, the records in the underlying database. For example, you can create an instance of an ActiveRecord model, and then not create a matching record in the database. -- The database record will only be created when and if you call the save or create methods.
I'm following a Rails 3.0 tutorial by lynda.com.
What's the difference between these two lines?
first_page = Page.new(:name => "First page")
first_page = Page.create(:name => "First page")
By the way, this is great tutorial; I recommend it for any other newbies like me.
Basically the new method creates an object instance and the create method additionally tries to save it to the database if it is possible.
Check the ActiveRecord::Base documentation:
create method
Creates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not.
new method
New objects can be instantiated as either empty (pass no construction parameter) or pre-set with attributes but not yet saved (pass a hash with key names matching the associated table column names).