Rails is persisting on validates - ruby-on-rails

I have an ActiveRecord model that does find and replace filtering on an attribute when its accessor is called, and I'm doing it like the answer to my question here. It looks like this:
class Post < ActiveRecord::Base
include Replaceable
profanity_attrs :body, :title
end
If you haven't looked at the link, then there's a macro called profanity_attrs that calls a profanity_filter method that does find/replace on known keywords.
So when I have something like body = "Oh my poop" and I do a replace with **** so that the filter returns "Oh my ****" then that's what it looks like, and I'm very happy. The DB shows "Oh my poop" and the view shows "Oh my ****", which is exactly what I want, until... I add validation. So if I do this:
class Post < ActiveRecord::Base
include Replaceable
validates :body, length: { maximum: 255 }
profanity_attrs :body, :title
end
Then it still does the replace properly, but the replaced value will be persisted (ie. the db contains "Oh my ****"), and that doesn't make a lot of sense to me. It appears as if the validator is mutating the field that it validates.
Is there a way to preserve the validation, but not have it persist the value returned from the accessor?

Verify that you are not setting the attribute value in your profanity_filter implementation, that you are just returning a filtered copy of it (just validating an attribute shouldn't change it's value unless you're accidently also setting its value in your profanity_filter method).
In addition I would also change a little bit what you did in the macro you defined, instead of
define_method(attr)
I would define the method as
define_method("#{attr}_clean")
and in the views access the filtered version of the attribute by calling the "clean" version, i.e.
<%= post.body_clean %>

Related

How to validate the presence of attributes when updating a record?

I am new to rails and I try to find a validation method corresponding to validate the presence of an attribute when updating a record. If that attribute is not present, meaning the attribute does not exist from the request body, Rails should not update the record.
validates :description, presence: true
and
validates_presence_of :description
doesn't seem to do the job. Is there a method for this purpose? It seems quite common in every day scenarios.
If you say:
model.update(hash_that_has_no_description_key)
then you're not touching :description: sending a hash without a :description key to update is not the same as sending in a hash with :description => nil. If model is already valid (i.e. it has a description) then that update won't invalidate it because it won't touch :description.
You say this:
If that attribute is not present, meaning the attribute does not exist from the request body, Rails should not update the record.
Since you're talking about the request body (which models really shouldn't know anything about) then you should be dealing with this logic in the controller as it prepares the incoming data for the update call.
You could check in the controller and complain:
data = whatever_params
if(!data.has_key?(:description))
# Complain in an appropriate manner...
end
# Continue as now...
or you could include :description => nil if there is no :description:
def whatever_params
data = params.require(...).permit(...)
data[:description] = data[:description].presence # Or however you prefer to do this...
data
end
maybe you should use before_update..
see this: http://edgeguides.rubyonrails.org/active_record_callbacks.html#conditional-callbacks
but use before_update instead before_save..

How to validate field doesnt contain . and ,

I have a form which include number fields
<%= f.number_field :contribution_to_sales, class: 'form-control',:pattern=>["\d+"] %>
It allows 2.0 but I want it should not allow 2.0 .
How to do that?
You are missing anchors on your regular expression which causes the "2" in "2.0" to be matched (on some browsers). The regex to use is:
<%= ... pattern: "^\d+$" %>
You should probably be doing the validation on the model as well, as the HTML5 pattern attribute may not be obeyed by all browsers. Simply add:
class YourModel < ActiveRecord::Base
validates :contribution_to_sales, numericality: { only_integer: true }
...
end
Add the following to the Model this form relies on:
validate :format_numbers
def format_numbers
if self.number_field.include?(',') || self.number_field.include?('.')
errors.add(base: "Do not add commas or periods please")
end
end
That is one way to validate however I prefer the much more user friendly way which removes certain things a user may add that I know I don't want and then continue to process the form instead of reporting back with an error. This would be done by removing commas and since you don't want periods either I would imagine we can strip anything to the right of a period out as well. To do it this way you would do the following in your Model instead of the above:
validate :format_numbers
def format_numbers
self.number_field = self.number_field.gsub(/\.\d+/, '') #This will remove any period and all numbers to the right of that period.
self.number_field = self.number_field.gsub(',','') #This will remove all commas throughout.
end
This provides a smoother user experience since they can now type 2,000.00 in the form field and it will be saved as 2000

ActiveRecord, validates_uniqueness_of :name not catching non-uniquness if I have a capitalize method

I have a simple capitalize method so that when user submits a new band in the band page it returns it with the first letter capitalized.
Inside my Band class I also have a validates_uniqueness_of :band_name to see if there is already a band with the same entry. See code below:
class Band < ActiveRecord::Base
has_and_belongs_to_many :venues
validates :band_name, :presence => true
before_save :title_case
validates_uniqueness_of :band_name
private
def title_case
self.band_name.capitalize!
end
end
So if I type in someband, it creates it and displays it as Someband. If I type someband again, ActiveRecord sees it as unique and I'll get another Someband. The only way it works is if I type Someband. How would I remedy this situation?
I think what you want to do is this
validates_uniqueness_of :band_name, :case_sensitive :false, allow_blank: false
Take a look at http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
:case_sensitive - Looks for an exact match. Ignored by non-text
columns (true by default).
The reason your code doesn't work is because validations happen before the before_save callbacks are triggered. Check out the list of ActiveRecord::Callbacks for the order in which things are called.
MZaragoza's answer is a great option for making your validation work regardless of what casing your users might enter. It will prevent things like "someband" and "SomeBand" from being added. I recommend including that as part of your solution.
Another option very similar to the code you already have is to switch to using the before_validation callback:
before_validation :title_case
I highly recommend using the before_validation callbacks instead of before_save callbacks whenever data changes that may be relevant to your validation rules, regardless of what other changes you make. That ensures that you are checking that actual state of the model that you plan to save to the database.
You can use attribute setter instead of before_save callback to capitalize your value without postponing.
def band_name=(value)
self['band_name'] = value && value.mb_chars.capitalize
end

Possible bug in rails conditional validation?

I have a form that allow me to edit an entry. I have a secondary set of attributes that must all be here if one of them have a value. I use conditional validates to do so :
validates_presence_of :raison_sociale,:nom_contact,
:prenom_contact,:telephone,
if: (:nom_contact?||:raison_sociale?||
:prenom_contact? || :telephone?)
But the strange thing is, those 4 fields are not evaluated the same way! If i remove nom_contact, it save. But if i remove it and telephone, it fails.
What i observed is that it was ignoring a blank field if it was the first one in the if condition! As soon as i put prenom contact as first condition i cannot save without nom_contact, but now it's prenon_contact that is ignored!
Why does my conditions behave strangly and what can i do?
What ca i do to avoid that?
You could put the if validation in a proc.
"Symbol-only" conditional validation expects a symbol for the name of a method that will be called.
You're including conditionals, which AFAIK won't work without being in a proc.
You can do some combinations by using an array, as per the docs:
class Computer < ActiveRecord::Base
validates :mouse, presence: true,
if: ["market.retail?", :desktop?]
unless: Proc.new { |c| c.trackpad.present? }
end
But those are and, not or.
I'd probably wrap it up in a method anyway; IMO it's a bit long for an in-line block, but that's more a matter of opinion.

How do I define synthetic attributes for an ActiveRecord model?

I have an ActiveRecord model whose fields mostly come from the database. There are additional attributes, which come from a nested serialised blob of stuff. This has been done so that I can use these attributes from forms without having to jump through hoops (or so I thought in the beginning, anyway) while allowing forwards and backwards compatibility without having to write complicated migrations.
Basically I am doing this:
class Licence < ActiveRecord::Base
attr_accessor :load_worker_count
strip_attributes!
validates_numericality_of :load_worker_count,
:greater_than => 2, :allow_nil => true, :allow_blank => true
before_save :serialise_fields_into_properties
def serialise_fields_into_properties
...
end
def after_initialize
...
end
...
end
The problem I noticed was that I can't get empty values in :load_worker_count to be accepted by the validator, because:
If I omit :allow_blank, it fails validation complaining about it being blank
If I put in :allow_blank, it converts the blank to 0, which when fails on the :greater_than => 2
In tracking down why these blank values are getting to the validation stage in the first place, I discovered the root of the problem: strip_attributes! only affects actual attributes, as returned by the attributes method. So the values which should be nil at time of validation are not. So it feels like the root cause is that the synthetic attributes I added in aren't seen when setting which attributes to strip, so therefore I ask:
Is there a proper way of creating synthetic attributes which are recognised as proper attributes by other code which integrates with ActiveRecord?
I assume you are talking of the strip_attributes plugin; looking at the code, it uses the method attributes, defined in active_record/base.rb, which uses #attributes, which is initialized (in initialize) as #attributes = attributes_from_column_definition.
Maybe it's possible to hack ActiveRecord::Base somehow, but it would be a hard work: #attributes is also used when getting/putting stuff from/to db, so you would have to do a lot of hacking.
There's a much simpler solution:
before_validate :serialise_fields_into_properties
...
def serialise_fields_into_properties
if load_worker_count.respond_to? :strip
load_worker_count = load_worker_count.blank? ? nil : load_worker_count.strip
end
...
end
After all, this is what strip_attributes! does.
Wouldn't it be easier to just use Rails' serialize macro here?
class License < ActiveRecord::Base
serialize :special_attributes
end
Now you can assign a hash or array or whatever you need to special_attributes and Rails will serialize it a text field in the database.
license = License.new
license.special_attributes = { :beer => true, :water => false }
This will keep your code clean and you don't have to worry about serializing/deserializing attributes yourself.

Resources