Virtual attributes in rails model - ruby-on-rails

In my controller I'm calling #hour.shopper.add_product within a for loop.
My model looks like:
class Shopper < ActiveRecord::Base
attr_accessor :quantity
def add_product
if self.quantity.nil? || \
self.quantity == 0
self.quantity = 1
else
self.quantity += 1
end
self.save
end
end
When I print #hour.shopper.quantity it always says 'nil'. It seems like it's not saving the quantity attribute in the #hour.shopper object.
Thanks in advance!

Well, yes, instance variables aren't saved to the database (how could they be? There's no column for them).
Since the title of the question is "Virtual attributes", I'm going to assume that you don't have a quantity column in your database table (if you do, just remove the attr_accessor bit), however you still need to store the quantity somewhere if you want it to persist.
Usually virtual attributes are used when some attribute is not stored directly in the DB, but can be converted from and to an attribute that is. In this case it doesn't look like that is the case, so I can only recommend that you add a quantity column to your database table.

Related

Automatically setting a column to created_at during model instance creation

I have the following model (sort_timestamp is a datetime):
class Post < ActiveRecord::Base
[snip attr_accessible]
acts_as_nested_set
after_create :set_sort_timestamp
private
def set_sort_timestamp
self.sort_timestamp = self.created_at
end
end
I'm using https://github.com/collectiveidea/awesome_nested_set . This code doesn't set sort_timestamp. What am I doing wrong?
Unless I'm missing the point of what you're doing here, you're probably looking for before_create if you'd like it to save when the row is created. Otherwise you'll have to add self.save to the method, but that will cause extra database calls, so before_create might be the better option.
(Basically, the flow of what you were doing before was that the model would be created, saved to the database, and then the object would modify its attribute sort_timestamp to be created_at; this is after your database commit, and only performed in memory (so not persisted, unless you were persisting it in another way later in the code).
EDIT: Actually, this probably won't work because created_at probably won't be set before the record is created. A few options:
1) Add self.save to end of your method with after_create
2) Use Time.now if the times sort_timestamp and created_at don't have to be exactly the same.
or, 3) Try adding default value to migration: How to use created_at value as default in Rails

Conditional model updating -- Rails 3.1

I have two tables:
stores
raw_stores_data
The raw_stores_data is received from a third party daily.
I'd update certain fields of the stores model if those fields have been modified for that record in raw_stores_data.
Currently I have a bunch of conditional statements that check each of those fields. Is there any better way to code this?
new_data = raw_stores_data.all.select do |item|
item.store_id.present?
end
new_data.each do |item|
if item.field1 != item.stores.field1
...
...
...
# update record with hash of fields to update created above
end
You could add an association and special mutators to the 'raw' model that know how manipulate the 'stores' object. This serves to keep the model code in the model. Thin controller, comprehensive models, etc.
class Store < ActiveRecord::Base
has_one :raw_stores_data
end
class RawStoresData < ActiveRecord::Base
belongs_to :store
def field1=(value)
store.field1 = value
store.save!
field1 = value
end
end
I'm hand waving at some of the details, and you might want to reverse the direction of the association or make it go both directions.
EDIT:
You would use this as such:
raw_data = RawStoreData.find(param[:id]) # or new or however you get this object
raw_data.field1 = param[:field1]
The act of assigning will use the 'field1=' method, and make the change to the associated store object. If you're worried about saving unnecessarily, you could conditionalize in that method to only save if the value changed.
I hope this is clearer.

How to set default value of column to current time with ActiveRecord?

I got a field in MYSQL called "date_added". I want this to have the value Time.now.to_i as its default value (in other words, the time the record was added). I know a similar column is added for free in ActiveRecord, but ignoring that... how can I add this default value?
:default => ???
You can use before_create:
class SomeModel < ActiveRecord::Base
before_create :set_date_added
protected
def set_date_added
self.date_added = Time.now.to_i
end
end
This gets called right before an object is saved for the first time, exactly the conditions you want.
Alternatively, you can use a hack to rename the default activerecord created_at to date_added by following the answers here: Renaming the created_at, updated_at columns of ActiveRecord/Rails
See http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html for more information on callbacks.

virtual attribute in a query

User model has all_scores attribute and i created the method below
models/user.rb
def score
ActiveSupport::JSON.decode(self.all_scores)["local"]
end
What i'm trying to do this using this virtual attribute score to filter users. For example:
I need the users whose score is over 50. The problem is i can't use virtual attribute as a regular attribute in a query.
User.where("score > 50") # i can't do this.
Any help will be appreciated.
Well, the "easiest" solution would probably be User.all.select{|user| user.score > 50}. Obviously that's very inefficient, pulling every User record out of the database.
If you want to do a query involving the score, why don't you add a score column to the users table? You could update it whenever all_scores is changed.
class User < AR::Base
before_save :set_score, :if => :all_scores_changed?
protected
def set_score
self.score = ActiveSupport::JSON.decode(self.all_scores)["local"] rescue nil
end
end
This will also avoid constantly deserializing JSON data whenever you access user#score.

How to protect a Rails model attribute?

My Invoice model has an address_id attribute, and I don't want this address_id to change FOREVER. So I don't want this to happen outside the class:
invoice.address_id = 1
invoice.address = some_address
Rails automatically adds this address_id attribute to the model from the invoice table, so how can I declare this attribute private/protected? Calling
attr_protected :address_id
is most likely not the solution since based on the documentation it only prevents mass assignments.
Thanks!
You want attr_readonly.
Not as pretty as a one liner, but code below should work (and you could always do some metaprogramming to write an 'immutable' method)
def address_id=(id)
if new_record?
write_attribute(:address_id, id)
else
raise 'address is immutable!'
end
end

Resources