My model has a method that will update several attributes in a model from a remote resource. I want to test that with Rspec, but cannot find how to test whether a field was created or updated.
Some pseudocode to explain the question
def update_from_remote
attributes = {}
fields.each_key do |field_name|
attributes[field_name] = scrape_from_remote field_name
end
update_attributes(attributes)
end
In reality this code is a lot more complex, but that will only clutter my question. A create_from_remote is rather similar, only that it does not call update_attributes but simply sets them and then saves the object.
I would like to test this. What I want, is a spec that tests whether the fields were updated or filled:
it 'should fill or set all local attributes on a new profile' do
#how to test if a list of fields were updated in the Database?
end
it 'should update all local attributes on an existing profile' do
#how to test if a list of fields were created in the Database?
end
I am using mongoId, but AFAIK that should not make much difference.
I should have dug deeper in the MongoID documentation. There is a chapter on Dirty tracking which can be used:
it "should be updated"
#something.field.should be_changed
end
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.
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 have an example Action in a Controller.
def some_action
product = Product.new
product.name = "namepro"
if product.save
client.update_attribute(:product_id,product.id)
end
end
How to add transactions for this code? I try with this example code:
def some_action
**transaction do**
product = Product.new
product.name = "namepro"
if product.save
client.update_attribute(:product_create,Time.now)
end
**end**
end
But it produces this error:
undefined method `transaction'
I read about using transactions in Controllers is a bad practice but I don't know why is the reason (http://markdaggett.com/blog/2011/12/01/transactions-in-rails/)
In the example, if product has been created and saved and the client update fail... Rails must not do nothing.
thanks.
You can use a transaction in a controller if you really want to. As you noted, it's bad practice, but if you want to do it, just call Product.transaction do instead of transaction do. transaction is a class method on ActiveRecord::Base, so you need to call it on an ActiveRecord-derived class. Any model class in your application will do (nit-picking caveat: if you are connecting to different databases for different models, that may not be true...but you're probably not doing that).
The reason this is a bad practice is that it doesn't properly separate concerns according to the MVC paradigm. Your controller shouldn't be so concerned with your data persistence implementation. A better approach would be to add a method to Product. Maybe something like this:
def save_and_update_create_time
transaction do
if save
client.update_attribute(:product_create, Time.now)
end
end
end
Then instead of calling product.save in your controller, call product.save_and_update_client_create_time. You may need to pass client to that method too; it's unclear from your code where client comes from. If it's an attribute on product, then the method above should work.
There are better, more Railsy ways to do this, too, especially if a product knows about its client without needing any controller data. Then you can just use an after_save callback, like this (add to Product class):
after_save :update_client
private
def update_client(product)
product.client.update_attribute(:product_create, Time.now)
end
Then every time a Product is saved, the field on the associated client will be updated. You'll possibly have to introduce some code to check for the existence of a client first.
The benefit to using callbacks, besides cleaner code, is that the entire callback chain runs in a single transaction along with the save; you don't need to create the transaction manually. You can read more about callbacks in the Rails documentation.
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!
In a Rails application, I have a particular form with many fields for editing a resource. Since I also want to log what was changed for this particular resource, I need to know which params changed.
Currently in this form, I have duplicated every field in the form with hidden field tags, so in the controller every field is compared to the corresponding hidden field to determine if the value was changed. But it's a LOT of work in the view and in the controller.
Being relatively new to Rails, I'm finding all kinds of Rails "magic" as I go along, so I wonder: does the framework provide a way to do this for me? Or is this pretty much the only way?
ActiveModel has exactly what you're looking for, take a look at the examples in the docs...
http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
person = Person.find_by_name('Uncle Bob')
person.changed? # => false
person.name = 'Bob'
person.changed? # => true
person.name_changed? # => true
person.name_was # => 'Uncle Bob'
person.name_change # => ['Uncle Bob', 'Bob']
person.name = 'Bill'
person.name_change # => ['Uncle Bob', 'Bill']
You can try Dirty attributes for this task.
#post.attributes = params[:post]
#post.changed # or changes if you want to see what values on which was changed
More on that you can find by this link: http://ar.rubyonrails.org/classes/ActiveRecord/Dirty.html
Also if you want to use versioning or auditing, try this gems (best ones are: paper_trail, acts_as_audited, vestal_versions):
https://www.ruby-toolbox.com/categories/Active_Record_Versioning
https://www.ruby-toolbox.com/categories/Active_Record_User_Stamping