Rails before_validation space bar best practices

I would like my User model to deactivate some data before saving. At the moment, the simplest white spaces. Therefore, to avoid registering people with Harry and, for example, pretend to be Harry.

I suggest that it is a good idea to do this deletion before validation, so that validates_uniqueness_of can avoid accidental duplicates.

class User < ActiveRecord::Base has_many :open_ids validates_presence_of :name validates_presence_of :email validates_uniqueness_of :name validates_uniqueness_of :email validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[az]{2,})\Z/i before_validation :strip_whitespace, :only => [:name, :email, :nick] private def strip_whitespace(value) value.responds_to?('strip') ? value.strip : value end end 

However, this code contains an ArgumentError error: the wrong number of arguments (0 for 1). I assumed that the callback would be passed values.

Also: is stripping really a good idea? Or I should rather settle in space and tell the user that “Harry” contains an invalid space (I want to allow “Harry Potter”, but not “Harry \ s \ sPotter”).

Edit: As stated in the comment, my code is incorrect (that is why I asked ao question). Please make sure that you read the accepted answer in addition to my question about the correct code and avoid the same mistakes that I made.

+54
validation ruby-on-rails model
Jul 16 '10 at 19:05
source share
14 answers

I do not believe that before_validation works like that. You will probably want to write your method as follows:

 def strip_whitespace self.name = self.name.strip unless self.name.nil? self.email = self.email.strip unless self.email.nil? self.nick = self.nick.strip unless self.nick.nil? end 

You can make it more dynamic if you want to use something like self.columns , but its essence.

+55
Jul 16 '10 at 19:21
source share

There are several gems to do this automatically. These gems work similarly to creating a callback in before_validation. One good gem at https://github.com/holli/auto_strip_attributes

 gem "auto_strip_attributes", "~> 2.2" class User < ActiveRecord::Base auto_strip_attributes :name, :nick, nullify: false, squish: true auto_strip_attributes :email end 

Undressing is often a good idea. Especially for leading and trailing spaces. The user often creates trailing spaces when copying / pasting a value into a form. With names and other identifying strings, you might also want to compress the string. So “Harry Potter” will become “Harry Potter” (squishy version in the pearl).

+43
Aug 28 '11 at 10:50
source share

Charlie answers well, but there is a bit of verbosity. Here's a denser version:

 def clean_data # trim whitespace from beginning and end of string attributes attribute_names.each do |name| if send(name).respond_to?(:strip) send("#{name}=", send(name).strip) end end end 

The reason we use

 self.foo = "bar" 

instead

 foo = "bar" 

in the context of ActiveRecord objects, Ruby interprets the latter as a local variable assignment. It will simply set the foo variable in the scope of your method instead of calling the "foo =" method of your object.

But if you call a method, there is no ambiguity. The interpreter knows that you do not mean the local variable foo, because it does not exist. So, for example, with:

 self.foo = foo + 1 

you need to use "self" to assign, but not read the current value.

+27
Feb 05 2018-12-12T00:
source share

I would like to add one trap that you can experience with the "before_validations" solutions above. Take this example:

 u = User.new(name: " lala") u.name # => " lala" u.save u.name # => "lala" 

This means that you have inconsistent behavior based on whether or not your object is saved. If you want to address this issue, I propose another solution to your problem: rewriting the appropriate configuration methods.

 class User < ActiveRecord::Base def name=(name) write_attribute(:name, name.try(:strip)) end end 

I also like this approach because it does not force you to include the removal of all attributes that support it, unlike attribute_names.each , mentioned earlier. In addition, no callbacks are required.

+18
Jun 17 '12 at 13:20
source share

I like Carl's answer, but is there any way to do this without referring to each of the attributes by name? That is, is there a way to simply run the model attributes and the call bar on each of them (if it answers this method)?

This would be desirable, so I don’t need to update the remove_whitespace method whenever I change the model.

UPDATE

I see that Karl meant that you might want to do that. I did not immediately understand how this can be done, but here is something that works for me, as described above. This is probably the best way to do this, but it works:

 def clean_data # trim whitespace from beginning and end of string attributes attribute_names().each do |name| if self.send(name.to_sym).respond_to?(:strip) self.send("#{name}=".to_sym, self.send(name).strip) end end 

end

+9
Dec 09 '10 at 18:30
source share

Instead, we can write a more efficient method, a more general one, regardless of what type of attributes an object can have (it can have 3 string types, several Boolean, several numeric)

 before_validation :strip_input_fields def strip_input_fields self.attributes.each do |key, value| self[key] = value.strip if value.respond_to?("strip") end end 

Hope this helps someone!

+9
Jun 25 '14 at 6:52
source share

If you have access to ActiveSupport, use squish instead of strip.

http://api.rubyonrails.org/classes/String.html#method-i-squish

+8
Apr 20 '13 at 4:00
source share

StripAttributes Gem

I used strip_attributes . It is truly amazing and easy to implement.

Default behavior

 class DrunkPokerPlayer < ActiveRecord::Base strip_attributes end 

By default, this will erase the leading and trailing spaces and will affect all attributes of the model. This is ideal because it is not destructive and does not require you to specify which attributes should be striped.

Using except

 # all attributes will be stripped except :boxers class SoberPokerPlayer < ActiveRecord::Base strip_attributes :except => :boxers end 

Using only

 # only :shoe, :sock, and :glove attributes will be stripped class ConservativePokerPlayer < ActiveRecord::Base strip_attributes :only => [:shoe, :sock, :glove] end 

Using allow_empty

 # Empty attributes will not be converted to nil class BrokePokerPlayer < ActiveRecord::Base strip_attributes :allow_empty => true end 

Using collapse_spaces

 # Sequential spaces in attributes will be collapsed to one space class EloquentPokerPlayer < ActiveRecord::Base strip_attributes :collapse_spaces => true end 

Using regex

 class User < ActiveRecord::Base # Strip off characters defined by RegEx strip_attributes :only => [:first_name, :last_name], :regex => /[^[:alpha:]\s]/ # Strip off non-integers strip_attributes :only => [:phone], :regex => /[^0-9]/ end 
+6
Sep 24 '14 at 9:04
source share

Here's an alternative approach if you're mostly worried that users are mistakenly entering data into their front-end forms ...

 # app/assets/javascripts/trim.inputs.js.coffee $(document).on "change", "input", -> $(this).val $(this).val().trim() 

Then include the file in your application.js if you have not yet included the entire tree.

This ensures that every input is deleted before the leading and trailing spaces are removed before it is sent for storage in Rails. It is attached to document and delegated to inputs, so any inputs added to the page later will also be processed.

Pros:

  • No need to list individual attributes by name
  • No metaprogramming required
  • Does not require external library dependencies

Minuses:

  • Data transferred in any other way than forms (for example, through the API) will not be truncated
  • It has no advanced features like squish (but you can add it yourself)
  • As mentioned in the comments, does not work if JS is disabled (but who coordinates this?)
+4
Jun 14 '12 at 6:32
source share

Overriding attribute writing methods is another good way. For example:

 class MyModel def email=(value) super(value.try(:strip)) end end 

Then any part of the application that sets the value will be stripped of it, including assign_attributes, etc.

+4
Aug 05 '14 at 4:13
source share

Although I could use a similar approach to Carl's answers, I prefer a shorter syntax with fewer assignments:

 def strip_whitespace self.name.try(:strip!) self.email.try(:strip!) self.nick.try(:strip!) end 
+3
Nov 09 '17 at 17:40
source share

Since I cannot comment yet, I have to ask here: which method provides an ArgumentError? strip , or responds_to?

In addition, .strip only removes leading and trailing spaces. If you want Harry Potter to not be accepted with two spaces, you would need to use a regular expression or, more simply, you could call .split, which removes the spaces, and re-concatenate the string with one space.

Regarding the fact that deleting is a good idea, I don't see a problem when it is just leading / trailing empty space. If there are several spaces between words between words, I would notify the user instead of automatically removing the extra spaces and provide the user with a username that is not what they sent.

+2
Jul 16 '10 at 7:20
source share

Another gem option is attribute_normalizer :

 # By default it will strip leading and trailing whitespace # and set to nil if blank. normalize_attributes :author, :publisher 

: strip The strip skips leading and trailing spaces.

 normalize_attribute :author, :with => :strip 
+1
Apr 08 '16 at 16:10
source share

Starting with Ruby 2.3.0 you can use the safe navigation operator (&.)

 before_validation :strip_whitespace def strip_whitespace self.name&.strip! self.email&.strip! self.nick&.strip! end 

GEMS:
https://github.com/rmm5t/strip_attributes/
https://github.com/holli/auto_strip_attributes/

0
May 15 '19 at 13:52
source share



All Articles