I came across a scenario where in other languages I would detach the model object from the transaction then I can alter it all I want without worry of an automatic-update to the record.
Question
Does rails not support attaching\detaching a model object?
What is the alternative, just duplicate the object?
EDIT
Scenario
We are reading models out of the database and we want to make changes to them that will not be persisted to the database at the end of the transaction. In Hibernate\JPA etc you detach the model (Entity) and no changes will be persisted.
Now you may ask why not use Model.dup? The answer is that we still need the id of the model but as soon as you assign the id, rails believes this instance is now the model and updates the record at the end of the transaction.
Thanks
You can totally change Rails model instance attributes without persisting the changes.
There are a couple of methods to change model attributes, some of which do automatically persist the changes to the DB and others just change the attribute values of the in-memory instance.
You may want to try using #assign_attributes or #<attribute>= from the above list.
Only after explicitly calling #save afterwards, the changes will be saved to the database.
As stated in a previous answer, you can update instance attributes without persisting them (by avoiding self.save for example). But if you want to be sure, consider the following that uses a validation to check for a flag attribute being nil (or blank):
attr_accessor :prevent_save
validates :prevent_save, absence: true
def prevent_save!
self.prevent_save = true
end
def do_something_safely
prevent_save!
self.other_attr = 'abc'
end
def accidentally_save
# if prevent_save! has been previously called,
# validations will fail, and save! will raise an exception
self.save!
end
Related
I am not sure if I understand totally active record validation role.
Of course, if a user inputs data (like an email or a country), I can and should validate its existence, its uniqueness or its inclusion in a list of countries
But for example, if I have methods in the backend that change an attribute page_clicked or click_date or even the column update_at, that I "control" i.e 'is not generated by a user's input', should I use active record validations ?
I'm asking this because on a very 'hot database' (need speed for millions of frequent updates), I wonder if checking on each update that updated_at is a datetime, and that if a clicked column is true/false and nothing esle is really necessary as the user is not the one inputting/controlling these data but I am through Rails custom methods I wrote
Thanks
I don't think there is a general satisfying answer to your question. It's up to you to enforce validation or not.
Remember that you don't have to use ActiveRecord for validation, you can also use your DBMS to ensure that:
a value will never be NULL (one of the most annoying errors)
a value has the correct TYPE
a FOREIGN KEY always points to an existing row in another table
and depending on your DBMS, a lot more is possible
If you need high INSERT speed and want to go with raw SQL INSERTS, putting some validation in your database can prevent nasty application errors later.
Validations should guard your database and its job should be to stop saving the records that are considered invalid by your application.
There is no hard rule on what is valid record you have to decide it your self by adding the validations. If the record wont pass the validation step it is simply not going to be saved to the database.
From Active Record Callbacks:
3.1 Creating an Object
before_validation
after_validation
before_save
around_save
before_create
around_create
after_create
after_save
after_commit/after_rollback
3.2 Updating an Object
before_validation
after_validation
before_save
around_save
before_update
around_update
after_update
after_save
after_commit/after_rollback
You can see that validation hooks run at the beginning of the object life cycle.
So in your case instead of asking your self a question:
Should I use active record validations if the record is not generated by a user's input.
You should ask your self:
Is this record invalid without page_clicked or click_date(aka them being nil)
UPDATE
If you consider record to be invalid but worrying about speed problems with running validations I would do the validations to make sure that all the records in the database are valid and try to find the way to optimise the speed somewhere else. Plus not 100% sure but time spend on saving invalid records and filtering them later on will be probably much longer then validating in the first place.
When performance is really a priority and that I am sure that we developers / the server are the only ones who can manipulate specific attributes of a Model, I will
Make sure that I create a separate method / wrapper method for this specific action.
In this specific method, I call .save (validate: false) instead of the usual .save
I still write validations for the said attributes for developers' reference to prevent future development errors, and in case a new developer comes in and accidentally save an invalid record, precisely just because there's no validation to safeguard it.
Or, I will use .update_column instead of .save (validate: false) to perform a direct DB call, skipping Model validations and callbacks (If you also do not want callbacks to be called).
Note that .update_column is different from .update.
Well, not a good title but here is the problem.
[Question updated]
I have two models, Word and Definition. When the user looks up a word the definitions are enlisted and there should be a form below the definitions so that the user can contribute by adding up another definition. So far no problem. But if the search returns no result, I will ask the user to create Word along with its first definition.
I do not know how to deal with the form and logic of the problem. It is more than a nested form. Because something like form_for [#word, #word.definitions.build] do |form| would not work since there is no #word object to which the/a new definition can be referred.
Addendum:
I seem to find a way here. It just works but not so clean to me. If you think there is a better solution please share it anyway.
My approach would be to implement a form object (RailsCast). I'd use a transaction so I'm not left with any orphan database records.
In the submit method of your form object:
def submit
if dictionary_item.present?
# just save the entry
else
# start a transaction so both operations will either succeed or fail
ActiveRecord::Base.transaction do
# save the new dictionary_item
# save the entry
end
end
# return true if the objects are valid and persisted, false otherwise
end
Make sure to call save! or create! inside the transaction. The bangs are important, because an error has to be raised for the transaction to trigger a rollback.
In my Rails application I'm trying to update a model's attribute using update_attribute in an after_create callback. I can successfully update the attribute, but for some reason all the model's other attributes are also updated when I do so. So, even though the model's name attribute (for example) has not changed it is set (to it's current value) in the database update query.
Is this the expected behaviour in Rails (2.3.8), or am I doing something wrong?
Yes I believe this is consistent behaviour because that instance of your model that was just created has not been reloaded. Therefore the 'changed' attributes have not been reset.
Sorry if that's not a very clear explanation. To demonstrate, run the debugger in your after_create method. E.g.
def my_after_save_callback
require 'ruby-debug'; debugger
update_attribute(:foo, "bar")
end
Then when the debugger starts run:
p self.changed
An array of all the attributes that have been modified for this object will be returned. ActiveRecord will update all these attributes the next time the object is saved.
One way around this is to reload the object before updating the attribute.
def my_after_save_callback
reload
update_attribute(:foo, "bar")
end
This will reset the 'changed' attributes and only the specific attribute you modify will be updated in the SQL query.
Hope that makes sense :-)
I have a simple has_one/belongs_to relationship between two models.
This is a new association in my application so there are many records that do not yet have the associated record created.
Throughout my application I'm assuming the model has the association and I'm accessing its attributes and methods. However, because the association doesn't exist, I'm running into a lot of errors.
What I would like to do is unobtrusively build the associated record on the fly whenever it's access for the first time through any of its methods and attributes. It does not matter that there is data in record, I simply need it to exist so those methods I'm calling can build the data.
Edit: I do not want to check and create the record on all of the instances where I'm trying to access the relationship, so idealy this needs to be done on the model itself and not in my controllers anywhere.
Any thoughts?
Thanks!
Here's what we ended up with that did the trick. I didn't write it (a co-worker did) but it passes the previously failing tests that I wrote for this case.
def stats_with_create
stats_without_create || create_stats
end
alias_method_chain :stats, :create
In the controller, you could put something like this in the show method (untested, but it should give you an idea:
#thing = Thing.find params[:id]
if #thing.other_thing.nil?
#thing.other_thing = OtherThing.new.save!
#thing.save!
end
This isn't ideal, and you could probably clean it up a lot by putting a method in the Thing model that would check for and create the related model instead of putting it into your controller.
Another option would be to create a new accessor that you use to access the other_thing, creating it as required.
However, the correct thing to do is probably to fix your data, either in a migration or directly, creating the related models properly.
The direct answer is to override method for the relationship. When called it will check if the record exists and create it if it doesn't.
However, I would recommend that you use a migration to create all of the records up front.
I have done this type of thing before but not on the model level. Ive done it on the controller level with a before_filter that ran before all methods which needed to access the model association that did or did not exist yet.
I just realized there is the after_find and after_initialize callbacks that you can use in the model.
You could stick:
def after_initialize
association.build if association.nil?
end
in your model and it should solve your problems.. (disclaimer: untested by me) :)
I have an ActiveRecord model with a status column. When the model is saved with a status change I need to write to a history file the change of status and who was responsible for the change. I was thinking an after_save callback would work great, but I can't use the status_changed? dynamic method to determine that the history write is necessary to execute. I don't want to write to the history if the model is saved but the status wasn't changed. My only thought on handling it right now is to use an instance variable flag to determine if the after_save should execute. Any ideas?
This may have changed since the question was posted, but the after_save callback should have the *_changed? dynamic methods available and set correctly:
class Order
after_save :handle_status_changed, :if => :status_changed?
end
or
class Order
after_save :handle_status_changed
def handle_status_changed
return unless status_changed?
...
end
end
Works correctly for me w/ Rails 2.3.2.
Use a before_save callback instead. Then you have access to both the new and old status values. Callbacks are wrapped in a transaction, so if the save fails or is canceled by another callback, the history write will be rolled back as well.
I see two solutions:
Like you said: add a variable flag and run callback when it is set.
Run save_history after updating your record.
Example:
old_status = #record.status
if #record.update\_attributes(params[:record])
save_history_here if old_status != #record.status
flash[:notice] = "Successful!"
...
else
...
end
Has anyone not heard of database triggers?
If you write an on_update database trigger on the database server, then every time a record gets updated, it will create a historical copy of the previous record's values in the associated audit table.
This is one of the main things I despise about Rails. It spends so much time trying to do everything for the developer that it fools developers into thinking that they have to follow such vulgar courses of action as writing specialized rails methods to do what the freaking database server already is fully capable of doing all by itself.
shakes head at Rails once again