How to mark as outdated an ActiveRecord object attribute? - ruby-on-rails

I have a database trigger that modifies a field on INSERT. Then when I run object.my_attribute it returns nil instead of lets say 42.
If I do object.reload.my_attribute, this is fine. But I don't want to reload the whole object or part of it unless it is necessary. And I believe code shouldn't be concerned when and how an object was created. It should just be correct.
Is it possible to mark that particular attribute as outdated and any attempt to get its value to result in a query that fetches it from database?
For example:
after_save :forget_my_attribute
def forget_my_attribute
forget_field :my_attribute
end

I think it's better to make some service object where field is modified and call it when create the record. CreateModel.call(args) instead of Model.create(args). It will be more clear than database trigger I think
But you can do something like this
after_create_commit :fetch_my_attribute
def fetch_my_attribute
self[:my_attribute] = self.class.find_by(id: id)[:my_attribute]
end
Or more flexible fetch attribute you need dynamically
def fetch_attribute(atr)
self[atr] = self.class.find_by(id: id)[atr]
end
object.fetch_attribute(:my_attribute)

Related

How to assign to Rails ActiveRecord conditionally?

Hey I wasn't quite sure what to call this but here's the deal.
I'm trying to only assign things to my database value if
There isn't a value in the database already, and
The value I'm assigning isn't blank.
The rudimentary version of this code is:
venue.address = venue_json['address'] if venue.address.blank? && !venue_json['address'].blank?
where venue is my ActiveRecord result.
This is what I have now (a little better). With the init_value in the Venue.rb class.
Venue.init_value(venue.address, venue_json['address'])
def self.init_value(record, value)
if record.blank? && !value.blank?
record = value
end
end
I'd like to get to this point, but really have no idea how.
venue.address.init_value(venue_json['address'])
especially since I'd like it it work with any attribute of the ActiveRecord class not just the address value.
Separating it into a method sounds like a good idea, but in this case it makes more sense to use an instance method rather than a class method.
def init_attribute(attribute, value)
self.update(attribute => value) if self.send(attribute).blank? && value.present?
end
venue.init_attribute(:address, venue_json['address'])
Some quick comments on the snippet above:
Using direct assignment won't persist the database value. You could go with something else like update or update_column. Or you can use assignment and then call #save on the object.
Whenever you need something not to be blank, you can use the more readable Object#present? which is part of ActiveSupport.
You'll need to call the method with the same name as the attribute on the database object. For this you'll want to use Object#send from Ruby.

Active record where query for value inside of an array

Question: Is it possible to build a class method scope that can query objects based on values inside an array in a table? If yes, how can I do this?
In my example, I have a “wells” table that has an array field called “well_tags”. I want to build a query that returns all objects that have a specified value (such as “ceramic”) in the wells_tags array. The basic query would be something like this:
#well = Well.all
#query = #well.where(“well_tags contains ceramic”)
And then the class method scope would look something like this, with the “well_tag_search” param passed in from the controller:
class Well < ActiveRecord::Base
def self.well_tag_filter(well_tag_search)
if well_tag_search.present?
where(“well_tags contains ceramic")
else
Well.all
end
end
I found another post that asks a similar question (see link below), but I cannot get the answer to work for me...the result is always 'nil' when I know there should be at least 1 object. I am a beginner using sqlite (for now) as my database and rails 4.0.
Active Record Query where value in array field
Thanks!
UPDATE: some progress
I figured out how to create an array of all the objects I want using the ‘select’ method. But I still need to return the results as an Active Record object so I create a class method scope.
#well = Well.select
{ |well| if well.well_tags.present?
then well.well_tags.include? ‘ceramic' end }
#well.class #=> array
Not sure where Show is coming from.
Can you try doing Well.all instead of Show.all?

Update serialized attribute without callbacks in Rails

I am trying to update a serialized attribute with some data in an after_save callback on the same object.
I don't want any callbacks to be triggered, for various reasons (side-effects, infinite loop). The typical way to achieve this would be to use update_column, but unfortunately that doesn't work with serialized attributes.
I am aware that I could put conditionals on my callbacks to avoid them getting called again, but it feels that there should be a form of update_attribute which doesn't trigger callbacks, but still works with serialized attributes.
Any suggestions?
This is what I do
serialize :properties, Hash
def update_property(name, value)
self.properties[name] = value
update_column(:properties, properties)
end
Sharing an example below how you can update serialize attribute without callbacks.
Suppose you have a train object, and there is a serialize attribute in that table called: "running_weekdays", that store on which day that particular train runs.
train = Train.last
train.running_weekdays
=> {"Mon"=>"true", "Tues"=>"true", "Wedn"=>"true", "Thur"=>"true", "Frid"=>"true", "Sat"=>"true", "Sun"=>"true"}
Now suppose you want to set the value for all weekdays as false except 'Monday'
changed_weekdays = {"Mon"=>"true", "Tues"=>"false", "Wedn"=>"false", "Thur"=>"false", "Frid"=>"false", "Sat"=>"false", "Sun"=>"false"}
Now you can update this by using update_column as below:
train.update_column(:running_weekdays, train.class.serialized_attributes['running_weekdays'].dump(changed_weekdays))
Hope this will help.
You need to code the hash before updating the column:
update_column(:properties, self.class.serialized_attributes['properties'].dump(properties))

rails 3, how can a reference to fieldname pull data from another source if field is empty?

In my app, users have one "template" record (in Template table) that sets defaults for their data.
Then they create multiple records in (Userdata table).
For each Userdata record, if they enter data into a field, the app uses THAT data (of course). But if Userdata.foo is empty I'd like to use Template.foo instead, transparently. And if both are empty, then it's "empty".
I'm pretty sure the right answer is NOT to code every single place I use every field:
if Userdata.foo.blank?
Template.foo
endif
And I assume it's a matter of somehow defining my model to redefine the fieldname somehow?
And I'm hoping there's some way to not even have to code the model method field-by-field, to basically say "if the field in UserDayta is blank, use the one in Template instead..."
You can define a method in your User model like so:
def fetch_attribute(att)
if self.userdata[att].nil? and self.template[att].nil?
return nil
elsif self.userdata[att].nil?
return self.template[att]
else
return self.userdata[att]
end
end

How to change type of a record and run validations of the correct class in rails using the type column?

I have a model which uses Single table inheritance and has different types (school year of type either semester or quarter) and each type has its own validations. If a user tries to change the type of the record, he can select which academic year type it is from a drop down and make changes. however, if the user changes the type, i cannot figure out how to make new class validations run and not old validations. For instance, my code is as follows:
#school_year = SchoolYear.find(params[:id])
respond_to do |format|
if SchoolYear::SUBCLASSES.include? params[:school_year]['type']
#school_year[:type] = params[:school_year]['type']
else
raise "Invalid type"
end
if #school_year.update_attributes(params[:school_year])
# done
else
# validation problem?
end
now if the year's type was semester, and the user changes it to quarter, i expect the QuarterSchoolYear's validations to run and not of those of semester. How do i make changes to code to make it work?
I guess you should reload the object after you change the type. Just assigning a new value to the 'type' attribute will not change the ruby class of the object. Of course, when you save the object just after the type change, the old validations will be used.
You may try to update the type attribute in the database, and then load the object.
Something like:
[..]
if type_differs_and_is_acceptable_to_change
SchoolYear.update_all(
['type = ?', params[:shool_year][:type] ],
['id = ?',#school_year.id ]
)
#school_year = SchoolYear.find(#school_year.id)
end
if #school_year.update_attributes...
Be sure to have :type NOT in attr_accessible for this class.
Besides that, I find it a little disturbing to change the type of the object, but that may be just my personal fear. :)
This is old, but cleaning up and improving upon #Arsen7's suggestion, the best way to reload your object as its new type is to use the following ActiveRecord method (which is made for this very reason), and it doesn't require you to save the object after changing its type. This way, you're not hitting the database twice:
[..]
if type_differs_and_is_acceptable_to_change
#school_year.type = params[:school_year][:type]
#school_year = #school_year.becomes(#school_year.type.constantize)
end
if #school_year.update_attributes...
You need to instantiate with the right type of model to run the validations. I don't see a way to do this except to save with the new type and find it again:
new_type = params[:school_year]['type']
#school_year[:type] = new_type
#school_year.save!
#school_year = new_type.constantize.find(#school_year.id)
#school_year.update_attributes(params[:school_year])
You'd probably want to put this in a transaction so you could rollback the change of type if update_attributes fails.

Resources