rails 3: difference between write_attribute and update_attribute - ruby-on-rails-3

I did not know about write_attribute until today...
it seems like update_attribute, although not calling validation is still calling the :before_save callbacks, whereas write_attribute doesn't.
Is that the difference between these two methods?

update_attribute actually makes a physical call to the DB. You get a full execution of an UPDATE statement. It's like update_attributes but just for a single column.
While write_attribute writes the attribute for assignment to the model for AR based columns. If you were to overwrite a DB based attribute.
def first_name=(val)
write_attribute :first_name, val
end
# some_model.first_name => 'whatever val is'
def first_name=(val)
#first_name = val
end
# some_model.first_name => nil
I have not looked into write_attribute extensively, but I gather Activerecord based models handle assignments to db based columns slightly differently than your run of the mill accessor.

write_attribute is used when you want to overwrite the default accessors for a method. It is essentially syntactic sugar for self[:attribute]=(value).
Have a look at the ActiveRecord::Base documentationunder the heading "Overwriting default accessors".
If you tried to rewrite the example in the documentation using update_attribute, I'd imagine it would end up in a loop.

Related

How to prevent using a named_scope from creating n+1 queries

What is the best way to prevent ActiveRecord from making an unnecessary query when referencing a list of models that already been loaded?
class Checklist
has_many :checklist_items
scope :active, where(active: true)
end
class ChecklistItem
belongs_to :checklist
scope :active, where(active: true)
end
#checklists = Checklist.active.includes(:checklist_items).where(checklist_items: {active: true})
# Works fine, does NOT make extra SQL query
#checklists[0].checklist_items
# Makes extra query. How do I prevent this?
#checklists[0].checklist_items.active
What is the best work around for preventing this extra query when using a named_scope?
As soon as you use where, you'll trigger another query. If you think you have all your records in memory, treat the resulting active record relation as an array, not SQL. Use array methods like select instead of where. Perhaps:
#checklists[0].checklist_items.select {|ci| ci.active == true}

How to validate Rails Model attribute uniqueness over a virtual attribute scope

How can I validate uniqueness of an attribute with a custom or virtual scope? I thought of using a virtual attribute, but it keeps trying to query audit_year in the database. I would rather not create another database column just for the purpose of this uniqueness constraint.
Each location can only have one audit scheduled per year, so I need to extract the year from the scheduled attribute and validate uniqueness over that scope.
class Audit
attr_accessible :location_name, :scheduled_date, :completion_date ...
validates :location_name, :presence => true, :uniqueness => { :scope => :audit_year }
...
def audit_year
scheduled_date.year
end
end
I may not even be on the correct path with my virtual attribute attempts. What would be the "right" way to do this in rails?
I know this is a bit late, but I figured I'd pitch in. I'm doing this from memory so you may need to screw with this a bit.
My first thought is in your audit_year method, you could query the database for all years. Something like:
def audit_year
!self.class.select('selected_date').map{ |a| a.selected_date.year }.include?(selected_date.year)
# or using Rails 3.2 and Ruby 1.9.3:
# !self.class.pluck('created_at').map(&:year).include?(selected_date.year)
end
My assumption of the unique method is if it returns false, it will fail validation. This code selects just that one column from your class (I used self.class instead of Audit so it's more reusable), then maps out the years to an array. If it's included (true), return the opposite (!). You could probably optimize the query with uniq, but it depends on how large the table is whether it's necessary or not.
The other option would be to roll your own validator for the attribute, which really wouldn't be that difficult. The only difference is you'd add a line that conditionally checks for selected_date.present? in addition to the above. A great resource is the Rails Guides for callbacks and errors if you don't know how: http://guides.rubyonrails.org/active_record_validations_callbacks.html
Hope that helps.

Rails 3, Model Methods / Calculated Attributes

I am dealing with numerous calculations to bring various values within a model to a simple TRUE or FALSE. Problem is, these calculations are pretty intense and not something I want to create a long, hard to follow SQL statement for. I'd rather just have the entire calculation within a method that the model could check for when returning records.
I've tried numerous ways to accomplish this, and when looking up other similar feats, others push newbs like me to SQL which might serve most purposes but will not serve mine as the calculations being done are somewhat external to the model.
Model:
class Quality < ActiveRecord::Base
...
def passed_inspection
[code that calculates based on values in model]
end
Controller:
#records = Quality.where('passed_inspection = true')
View:
Did pass inspection?: <%= record.passed_inspection %>
It sounds like the solution to your problem would be to use a Scope with a Class Method to help clean up your model. Essentially you would set up your model like this:
class Quality < ActiveRecord::Base
def self.passed_inspection
# Code that does your calculations
end
scope :passed, passed_inspection() # This needs to be below the function above
end
Then you could get this data by calling it like this
#records = Quality.passed
There is a rails cast about this problem if you need any more information: RailsCast #215 Advanced Queries
Edit: Fixed some terrible grammar

named_scope and .first?

I can return a collection of objects, with only one (:limit => 1) but is there a way to return the .first() object only, like not within a collection?
named_scope :profile, :conditions => {:association => 'owner', :resource_type => 'Profile'}, :limit => 1 # => collection of 1 profile but I want the profile only NOT in a collection or array
the workaround is simply to apply .first() to the results, but I'd just like to clean up the code and make it less error prone.
You'll probably need to create a class method instead:
def self.profile
where(:association => 'owner', :resource_type => 'Profile').first
end
Note that with Rails 3 you should be using the where(...) syntax, and that when doing .first, you don't need to specify the limit.
First off, if you're using Rails 3 you should be using scope instead of named_scope. Same thing, different, err, name (named_scope will still work, but it is deprecated). Now that that is out of the way…
A scope (or named scope) takes two arguments (a symbol and either a lambda or a hash) and defines a class method on that model that returns an ActiveRecord::Relation, which is why you're able to chain methods on it.
first, like find or all, returns an actual result from the database. For this reason it won't work in a scope.
All that said, you can define your own class method on your model that gives the behavior you're wanting (as 2 people already answered while I was typing this). This is actually recommended over using scopes by many well-respected devs in the Rails community. Since using the scope class macro just defines class methods itself anyways, there isn't really a downside to this, and it has the benefit of flexibility (like in your case here).
Define a class method to do this:
def profile
where(:association => "owner", :resource_type => 'Profile').first
end
The first already does an implicit limit 1 on the query, AND will order it by the primary key of the table so you'll always get the first.

Track dirty for not-persisted attribute in an ActiveRecord object in rails

I have an object that inherits from ActiveRecord, yet it has an attribute that is not persisted in the DB, like:
class Foo < ActiveRecord::Base
attr_accessor :bar
end
I would like to be able to track changes to 'bar', with methods like 'bar_changed?', as provided by ActiveModel Dirty. The problem is that when I try to implement Dirty on this object, as described in the docs, I'm getting an error as both ActiveRecord and ActiveModel have defined define_attribute_methods, but with different number of parameters, so I'm getting an error when trying to invoke define_attribute_methods [:bar].
I have tried aliasing define_attribute_methods before including ActiveModel::Dirty, but with no luck: I get a not defined method error.
Any ideas on how to deal with this? Of course I could write the required methods manually, but I was wondering if it was possible to do using Rails modules, by extending ActiveModel functionality to attributes not handled by ActiveRecord.
I'm using the attribute_will_change! method and things seem to be working fine.
It's a private method defined in active_model/dirty.rb, but ActiveRecord mixes it in all models.
This is what I ended up implementing in my model class:
def bar
#bar ||= init_bar
end
def bar=(value)
attribute_will_change!('bar') if bar != value
#bar = value
end
def bar_changed?
changed.include?('bar')
end
The init_bar method is just used to initialise the attribute. You may or may not need it.
I didn't need to specify any other method (such as define_attribute_methods) or include any modules.
You do have to reimplement some of the methods yourself, but at least the behaviour will be mostly consistent with ActiveModel.
I admit I haven't tested it thoroughly yet, but so far I've encountered no issues.
ActiveRecord has the #attribute method (source) which once invoked from your class will let ActiveModel::Dirty to create methods such as bar_was, bar_changed?, and many others.
Thus you would have to call attribute :bar within any class that extends from ActiveRecord (or ApplicationRecord for most recent versions of Rails) in order to create those helper methods upon bar.
Edit: Note that this approach should not be mixed with attr_accessor :bar
Edit 2: Another note is that unpersisted attributes defined with attribute (eg attribute :bar, :string) will be blown away on save. If you need attrs to hang around after save (as I did), you actually can (carefully) mix with attr_reader, like so:
attr_reader :bar
attribute :bar, :string
def bar=(val)
super
#bar = val
end
I figured out a solution that worked for me...
Save this file as lib/active_record/nonpersisted_attribute_methods.rb: https://gist.github.com/4600209
Then you can do something like this:
require 'active_record/nonpersisted_attribute_methods'
class Foo < ActiveRecord::Base
include ActiveRecord::NonPersistedAttributeMethods
define_nonpersisted_attribute_methods [:bar]
end
foo = Foo.new
foo.bar = 3
foo.bar_changed? # => true
foo.bar_was # => nil
foo.bar_change # => [nil, 3]
foo.changes[:bar] # => [nil, 3]
However, it looks like we get a warning when we do it this way:
DEPRECATION WARNING: You're trying to create an attribute `bar'. Writing arbitrary attributes on a model is deprecated. Please just use `attr_writer` etc.
So I don't know if this approach will break or be harder in Rails 4...
Write the bar= method yourself and use an instance variable to track changes.
def bar=(value)
#bar_changed = true
#bar = value
end
def bar_changed?
if #bar_changed
#bar_changed = false
return true
else
return false
end
end