I know that in Rails you can call model.update_attribute :foo, 'bar' and it will update just that one attribute in the db without validating the rest of the model. This causes one SQL transaction.
You can also set multiple attributes with .update_attributes, but this cannot skip validations.
Or, you can call .save( :validate=>false ) and update the model without validation. However, this saves all the attributes on the model in their current state, rather than being able to limit this to certain columns.
My question is, is there any way to set more than one value on a model, but not all of them, without triggering validations, in a single SQL transaction?
Why don't you just set attributes and then call save with :validate => false?
#record.attributes = your_hash # won't nil non-mentioned attributes, as you expect it to
#record.save :validate => false
There are a number of ways to consider going depending on what you want. You can set your attributes and then call save with validate: false
model.attribute = value
model.other_attribute = other_value
model.save(validate: false)
You can also use update_columns. It is the fastest way to write to your db, but keep in mind that this will not only skip validations, it also skips callbacks, and it will even skip updating updated_at.
model.update_columns(attribute: value, other_attribute: other_value)
https://apidock.com/rails/ActiveRecord/Persistence/update_columns
Based on some of your comments it sounds like assign_attributes might actually be what you want. This won't save to the database at all, but sets the attributes so that you can save after your form is complete. Again, there are a number of ways to go depending on your specific need.
https://api.rubyonrails.org/v6.0/classes/ActiveModel/AttributeAssignment.html#method-i-assign_attributes
Related
In Rails 5, what is the difference between update and update_attributes methods. I'm seeing the following results for both the methods
Returns true/false
Checking for active record validation
Call backs are triggered
and also regarding update method a new thing was introduced in active record relation. I'm not able to understand it. What is the difference?
Moreover are we using update_attributes in Rails 5. It's not there in active record documentation.
I'm confused with all update methods. I need clarity
As of Rails 4.0.2, #update returns false if the update failed. Before Rails 4.0.2, #update returned the object that got updated. The main difference therefore was the return value. After this change, #update_attributes is just an alias of #update. It seems there are talks to deprecate #update_attributes in Rails 6 which is not released yet.
https://github.com/rails/rails/pull/31998
https://github.com/rails/rails/commit/5645149d3a27054450bd1130ff5715504638a5f5
From the rails 5 files it seems to me update can be used to update multiple objects(array of records) but update_attributes only work on single records otherwise both are same
From rails core files for update_attributes:
Updates a single attribute and saves the record.
This is especially useful for boolean flags on existing records. Also note that
Validation is skipped.
\Callbacks are invoked.
updated_at/updated_on column is updated if that column is available.
Updates all the attributes that are dirty in this object.
This method raises an ActiveRecord::ActiveRecordError if the
attribute is marked as readonly.
def update_attribute(name, value)
name = name.to_s
verify_readonly_attribute(name)
public_send("#{name}=", value)
save(validate: false)
end
For Update
Updates 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.
==== Parameters
+id+ - This should be the id or an array of ids to be updated.
+attributes+ - This should be a hash of attributes or an array of hashes.
==== Examples
# Updates one record
Person.update(15, user_name: "Samuel", group: "expert")
# Updates multiple records
people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
Person.update(people.keys, people.values)
# Updates multiple records from the result of a relation
people = Person.where(group: "expert")
people.update(group: "masters")
Note: Updating a large number of records will run an UPDATE
query for each record, which may cause a performance issue.
When running callbacks is not needed for each record update,
it is preferred to use {update_all}[rdoc-ref:Relation#update_all]
for updating all records in a single query.
def update(id, attributes)
if id.is_a?(Array)
id.map { |one_id| find(one_id) }.each_with_index { |object, idx|
object.update(attributes[idx])
}
else
if ActiveRecord::Base === id
raise ArgumentError,
"You are passing an instance of ActiveRecord::Base to `update`. " \
"Please pass the id of the object by calling `.id`."
end
object = find(id)
object.update(attributes)
object
end
end
When we are working with update_column that time update is done on the database level there is no any contact with the rails ORM so whatever logic we have implemented like callbacks and validations all will be waste and wont be useful as this is going to be bypassed.
I found this article explained really well in just 30 seconds.
.update
Use update when you want to return false, for example in an if/else:
if record.update(params)
display_success
else
react_to_problem
end
.update!
Use update! when you want an error (for example: to avoid erroring silently, which could be very bad if an error was unexpected and you needed to know about it to fix it!):
record.update!(params) # raises is invalid
'update' respects the validation rules on model, while 'update_attributes' ignores validations.
In my Eloquent collections, I'd like to add an extra column called "editable". "Editable" should be included in each query I run on some models. "Editable" show either be true or false, based on a raw query.
So I have a query that should be runned in each query on my models. Adding an extra column to my collection. The value of "editable" is determined by a raw query.
What is the best way to do this?
You could add an addSelect() method to your query chain to include the custom attribute..
Something like
$results = YourModelClass::select("*")
->addSelect(DB::raw("IF(condition,1,0) AS editable"))
->get();
In the above case, you would replace condition with your relevant SQL statement that would be evaluated per-row as part of the query. If the statement is true, then editable = 1 and if false then editable = 0 for each row returned to your Collection.
EDIT: I just saw that you want this on every query, so you probably would need a global scope/trait for your models, but the above technique for including the extra attribute should be the correct one.
I won't copy/paste the documentation on adding global scopes, that's in the core Laravel docs and I'm sure you can find it.
You can add a custom getter to your model:
public function getEditableAttribute()
{
/* return result from your raw query here */;
}
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 was wondering what the best implementation for displaying a warning for a particular field being sent to the database.
To give you an example, somebody provides data which is considered valid, but questionable. So we want to treat it as if it was a regular validation error on the first go and confirm that it's what the user actually wants to enter. At this point they will have the option to either continue or change the data being entered. If they choose to continue they'll be given the go-ahead and we'll skip that validation on the next run-through.
However (and this is the part I'm not sure about), if they change that field to another value that can be considered questionable we want to take them through the same process. Keep in mind these are new records and not records that have already been persisted to the database.
Can such a feat be accomplished with basic conditional validations? Would there be a better option?
Just to clarify my application knows exactly how to handle this questionable data, but it's going to be processed differently than normal data and we just want to inform the user ahead of time with a warning.
Currently the validation is your typical custom validation method that dictates the validity of an object.
validate :some_field_some_rules
def some_field_some_rules
if some_conditions_must_be_true
errors.add(:some_field, "warning message")
end
end
Edited, let's try with a custom validation that will be triggered only when you need to.
class MyModel < ActiveRecord::Base
attr_accessor :check_questionable
validate :questionable_values_validation, on: :create, if: Proc.new { |m| m.check_questionable }
def initialize
check_questionable = true
end
private
def questionable_values_validation
if attribute1 == "Questionable value"
self.errors[:base] << "Attribute1 is questionable"
check_questionable = false
end
end
end
Then, when you render the create form, be sure to add an hidden_field for check_questionable :
f.hidden_field :check_questionable
So the first time, when calling the create action, it'll save with check_questionable = true. If there's a questionable value, we add an error to ActiveRecord standard errors AND set the check_questionable to false. You'll then be re-rendering the new action but this time with the hidden_field set to false.
This way, when the form is re-submitted, it won't trigger questionable_values_validation ...
I didn't test it, might need some tweak, but it's a good start I believe!
Let's say you're in your user controller and you want to change the name a #user based on some params you have available to you.
I want to know if there is any difference between the following:
#user.name = params[:user][:name]
or
#user.assign_attributes({:name=> params[:user][:name]})
Thanks in advance!
A great way to figure out questions like this is to dive into the source. I found the method in activerecord/lib/active_record/attribute_assignment.rbCheck it out here.
The assign_attributes method will actually just loop through the parameters given and sends the :name= message to your model. However, because you are possibly assigning many attributes, it takes into account mass-assignment precautions. (ie. make sure that the attribute is listed as attr_accessible).
The = (e.g. #user.name = params[:user][:name]) directly calls the attribute setter with no security check. The assign_attributes checks security for the values passed in.
From the Rails API for assign_attributes:
Allows you to set all the attributes for a particular mass-assignment
security role by passing in a hash of attributes with keys matching
the attribute names (which again matches the column names) and the
role name using the :as option.
Source for assign_attributes