I have an if statement in my update action in one of my controllers. It looks like this:
if !#bid.attributes.values.include?(nil)
build(#bid.id)
end
I am checking to see if there are any nil valued attributes in my Bid object before building a bid report. I know the build method works fine because it builds a report when not wrapped in the if statement. When it is wrapped in this if statement, it doesn't run. I have checked to make sure that there are no nil values in the object. I went into the rails console and all attributes have non-nil values. In addition, I am able to check this in the views to confirm that there are no nil values.
I have also tried writing as:
build(#bid.id) unless #bid.attributes.values.include?(nil)
and a couple other variations. None are allowing the build to run.
Your code seems fine, I'm betting it's your data that's the problem instead. Mostly likely, assuming this an active record instance, the attribute is id which will be nil until the new record gets saved.
What do you get in the terminal when you add this line right before your if?
puts #bid.attributes.to_yaml
You should be able to see what has values and what does not. And I'm pretty sure at least one of those values is nil.
I would recommend being more explicit about exactly which fields are required. And this is exactly what validations are for.
class Person < ActiveRecord::Base
validates :name, presence: true
end
You explicitly validate each field so that when it's absent you get a very specific error message about why: "Person name can't be blank." So instead of wondering why it wont save, you get told why at the point it fails to save.
Related
I'm currently trying to understand how permitted parameters works in ruby.
Usually, in my_model.rb I have:
has_many: some_other_model
*
*
*
def my_model_params
params.require(:my_model).permit( :column1, some_other_model_attributes %i[other_column1])
etc...
and in the update function
my_object.update_attributes(my_model_params)
with a well formatted json which has some my_model root, and some_other_model_attributes as a child (array) with values.
My problem is I receive a json like this one
However the different arrays inside (such as codification, general_information) do contain attributes of the mission (general_information contains reference that is a column in the mission table) but there isn't any column named codification, or relation to a codification_attributes.
So, when I add :
general_information: %i[reference] in the permitted params, it says unknown attribute 'general_information' for Mission.
If not, no error are raised but in the log I can see unpermitted_parameter: general_information. And my object is not updated.
Finally if I reject it, there is no more unpermitted_parameter: general_information in the log but my object is not updated either.
I tried to set config.action_controller.action_on_unpermitted_parameters to false in my development config, it did nothing and it's probably a bad idea for production environment anyway.
The use of .permit! (even if it works) is currently not an option. And even though I think the json needs to be re-formatted it'd be better to find an other solution.
Thanks for the help.
unpermitted_parameter: ... in logs in not a problem which you need to fix, it's just an info.
How it works - you just permit needed parameters, you may think about them as a hash. Unpermitted parameters will not go into the model even if they are present in params. It means when you call
my_object.update_attributes(my_model_params)
it works like
my_object.update_attributes(column1: value_for_column1_from_params)
Keys in params should be named exactly as columns in the model, otherwise you need to prepare params somehow before create/update
CODE
# Item Model
class Item < ActiveRecord::Base
attr_accessor :paid_amount
after_save :amount_processed?
def amount_processed?
if self.try(:paid_amount)
return true
else
return false
end
end
end
# Controller snippet
...
# params = {"paid_amount" => 10}
#item.assign_attributes(params)
if #item.valid?
#item.save
end
...
Currently the callback is not running, i.e., the code never checks amount_processed?. The reason this is happening is because paid_amount isn't a db attribute for Item. But that is by design. The question is ASSUMING this design were to stay, would there be a way for me to run a callback to check amount_processed? simply based on the fact that the attribute was passed? (i.e., if you run #item.paid_amount you'd get "10" after the #item.assign_attributes).
Note that the following callbacks will not work:
after_save or after_touch because as above, the paid_amount is never saved so the #item is never updated
after_find because this runs, by definition, before the attribute assignment. So with this validation, even though amount_processed? is checked, when it is checked, #item.paid_amount = nil
Would love to combine the two...
Since the question asks how to do this GIVEN current design, a perfectly acceptable answer is to say in the current design, it's not possible. The callback will only work if the attribute is actually updated. In that case, I already have 2 strategies to tackle this, the easiest of which being moving amount_processed? to the controller level so I can check the paid_amount after the assign_attributes. The other strategy is to have a Child of Item, but this is dependent on other info about the code that, for simplicity's sake, I have withheld.
Thanks!
Ook I think I have the answer here, thanks for the comments. Willem is right, in the current design, I can ensure amount_processed? is run by using a custom validation, changing the callback to:
validate :amount_processed?
However, doing so then makes the code a bit hacky, since I'm co-opting a validation to do the work of a callback. In other words, I would have to ensure amount_processed? always returned true (at end of the if statement; obviously other work would be done with paid_amount). There are some other considerations as well looking holistically at my code.
Given that, may change the design... but this was still a very helpful exercise
I am upgrading an app to Rails 4.2 and am running into an issue where nil values in a field that is serialized as an Array are getting interpreted as an empty array. Is there a way to get Rails 4.2 to differentiate between nil and an empty array for a serialized-as-Array attribute?
Top level problem demonstration:
#[old_app]
> Rails.version
=> "3.0.3"
> a = AsrProperty.new; a.save; a.keeps
=> nil
#[new_app]
> Rails.version
=> "4.2.3"
> a = AsrProperty.new; a.save; a.keeps
=> []
But it is important for my code to distinguish between nil and [], so this is a problem.
The model:
class AsrProperty < ActiveRecord::Base
serialize :keeps, Array
#[...]
end
I think the issue lies with Rails deciding to take a shortcut for attributes that are serialized as a specific type (e.g. Array) by storing the empty instance of that type as nil in the database. This can be seen by looking at the SQL statement executed in each app:
[old_app]: INSERT INTO asr_properties (lock_version, keeps)
VALUES (0, NULL)
Note that the above log line has been edited for clarity; there are other serialized attributes that were being written due to old Rails' behavior.
[new_app]: INSERT INTO asr_properties (lock_version)
VALUES (0)
There is a workaround: by removing the "Array" declaration on the serialization, Rails is forced to save [] and {} differently:
class AsrProperty < ActiveRecord::Base
serialize :keeps #NOT ARRAY
#[...]
end
Changing the statement generated on saving [] to be:
INSERT INTO asr_properties (keeps, lock_version) VALUES ('---[]\n', 0)
Allowing:
> a = AsrProperty.new; a.save; a.keeps
=> nil
I'll use this workaround for now, but:
(1) I feel like declaring a type might allow more efficiency, and also prevents bugs by explicitly prohibiting the wrong data type being stored
(2) I'd really like to figure out the "right" way to do it, if Rails does allow it.
So: can Rails 4.2 be told to store [] as its own thing in a serialized-as-Array attribute?
What's going on?
What you're experiencing is due to how Rails 4 treats the 2nd argument to the serialize call. It changes its behavior based on the three different values the argument can have (more on this in the solution). The last branch here is the one we're interested in as when you pass the Array class, it gets passed to the ActiveRecord::Coders::YAMLColumn instance that is created. The load method receives the YAML from the database and attempts to turn it back into a Ruby object here. If the coder was not given the default class of Object and the yaml argument is nil in the case of a null column, it will return a new instance of the class, hence the empty array.
Solution
There doesn't appear to be a simple Rails-y way to say, "hey, if this is null in the database, give me nil." However looking at the second branch here we see that we can pass any object that implements the load and dump methods or what I call the basic coder protocol.
Example code
One of the members of my team built this simple class to handle just this case.
class NullableSerializer < ActiveRecord::Coders::YAMLColumn
def load(yaml)
return nil if yaml.nil?
super
end
end
This class inherits from the same YAMLColumn class provided by ActiveRecord so it already handles the load and dump methods. We do not need any modifications to dump but we want to slightly handle loading differently. We simply tell it to return nil when the database column is empty and otherwise call super to work as if we made no other modification.
Usage
To use it, it simply needs to be instantiated with your intended serialization class and passed to the Rails serialize method as in the following, using your naming from above:
class AsrProperty < ActiveRecord::Base
serialize :keeps, NullableSerializer.new(Array)
# …
end
The "right" way
Getting things done and getting your code shipped is paramount and I hope this helps you. After all, if the code isn't being used and doing good, who cares how ideal it is?
I would argue that Rails' approach is the right way in this case especially when you take Ruby's philosophy of The Principle of Least Surprise into account. When an attribute can possibly be an array, it should always return that type, even if empty, to avoid having to constantly special case nil. I would argue the same for any database column that you can put a reasonable default on (i.e. t.integer :anything_besides_a_foreign_key, default: 0). I've always been grateful to past-Aaron for remembering this most of the time whenever I get an unexpected NoMethodError: undefined method 'whatever' for nil:NilClass. Almost always my special case for this nil is to supply a sensible default.
This varies greatly on you, your team, your app, and your application and it's needs so it's never hard and fast. It's just something I've found helps me out immensely when I'm working on something and wondering if amount could default to 0 or if there's some reason buried in the code or in the minds of your teammates why it needs to be able to be nil.
I know how to check an attribute for errors:
#post.errors[:title].any?
Is it possible to check which validation failed (for example "uniqueness")?
Recently I came across a situation where I need the same thing: The user can add/edit multiple records at once from a single form.
Since at validation time not all records have been written to the database I cannot use #David's solution. To make things even more complicated it is possible that the records already existing in the database can become duplicates, which are detected by the uniqueness validator.
TL;DR: You can't check for a specific validator, but you can check for a specific error.
I'm using this:
# The record has a duplicate value in `my_attribute`, detected by custom code.
if my_attribute_is_not_unique?
# Check if a previous uniqueness validator has already detected this:
unless #record.errors.added?(:my_attribute, :taken)
# No previous `:taken` error or at least a different text.
#record.errors.add(:my_attribute, :taken)
end
end
Some remarks:
It does work with I18n, but you have to provide the same interpolation parameters to added? as the previous validator did.
This doesn't work if the previous validator has written a custom message instead of the default one (:taken)
Regarding checking for uniqueness validation specifically, this didn't work for me:
#post.errors.added?(:title, :taken)
It seems the behaviour has changed so the value must also be passed. This works:
#post.errors.added?(:title, :taken, value: #post.title)
That's the one to use ^ but these also work:
#post.errors.details[:title].map { |e| e[:error] }.include? :taken
#post.errors.added?(:title, 'has already been taken')
Ref #34629, #34652
By "taken", I assume you mean that the title already exists in the database. I further assume that you have the following line in your Post model:
validates_uniqueness_of :title
Personally, I think that checking to see if the title is already taken by checking the validation errors is going to be fragile. #post.errors[:title] will return something like ["has already been taken"]. But what if you decide to change the error message or if you internationalize your application? I think you'd be better off writing a method to do the test:
class Post < ActiveRecord::Base
def title_unique?
Post.where(:title => self.title).count == 0
end
end
Then you can test if the title is unique with #post.title_unique?. I wouldn't be surprised if there's already a Rubygem that dynamically adds a method like this to ActiveRecord models.
If you're using Rails 5+ you can use errors.details. For earlier Rails versions, use the backport gem: https://github.com/cowbell/active_model-errors_details
is_duplicate_title = #post.errors.details[:title].any? do |detail|
detail[:error] == :uniqueness
end
Rails Guide: http://guides.rubyonrails.org/active_record_validations.html#working-with-validation-errors-errors-details
I need to validate that a field is a positive value, and wrote a little methid in lib/validations.rb which does exactly that and added it to my model. However, now one of my rspec tests is failing complaining...
NoMethodError: undefined method '<' for nil:NilClass"
...when the value is undefined. I've also added validates_presence_of :price to my model, above my custom validates_positive_or_zero method.
I would think that validates_presence_of would be called first, and when the field is nil it wouldn't run further validation. Do I have to change my validates_positive_or_zero method to check if the field exists to fix this? Can anyone give me some insight into what's going on?
Rails already has this:
http://apidock.com/rails/ActiveModel/Validations/HelperMethods/validates_numericality_of
Use :greater_than => 0
All validators are run, regardless of the order they're defined in. That way the error array is filled with all the validation errors, allowing the end user to fix all the errors and not just one at a time.
So yes, you need to check if a value exists for the field, and that the value can be compared to a number. For example, uou probably also need to take into account that the value can be anything other than a number - a blank String would be a likely value - which would also cause an error.
But it's all academic, because as Stephen pointed out, just use validates_numericality_of :field, ::greater_than_or_equal_to => 0