Rails 3 strip whitespace before_validation on all forms - ruby-on-rails

I'm relatively new to Rails and a bit surprised this isn't a configurable behavior...at least not one I've been able to find yet?!? I would have thought that 99% of forms would benefit from whitespace being trimmed from all string & text fields?!? Guess I'm wrong...
Regardless, I'm looking for a DRY way to strip all whitespace from form fields (of type :string & :text) in a Rails 3 app.
The Views have Helpers that are automatically referenced (included?) and available to each view...but Models don't seem to have such a thing?!? Or do they?
So currently I doing the following which first requires and then includes the whitespace_helper (aka WhitespaceHelper). but this still doesn't seem very DRY to me but it works...
ClassName.rb:
require 'whitespace_helper'
class ClassName < ActiveRecord::Base
include WhitespaceHelper
before_validation :strip_blanks
...
protected
def strip_blanks
self.attributeA.strip!
self.attributeB.strip!
...
end
lib/whitespace_helper.rb:
module WhitespaceHelper
def strip_whitespace
self.attributes.each_pair do |key, value|
self[key] = value.strip if value.respond_to?('strip')
end
end
I guess I'm looking for a single (D.R.Y.) method (class?) to put somewhere (lib/ ?) that would take a list of params (or attributes) and remove the whitespace (.strip! ?) from each attribute w/out being named specifically.

Create a before_validation helper as seen here
module Trimmer
def trimmed_fields *field_list
before_validation do |model|
field_list.each do |n|
model[n] = model[n].strip if model[n].respond_to?('strip')
end
end
end
end
require 'trimmer'
class ClassName < ActiveRecord::Base
extend Trimmer
trimmed_fields :attributeA, :attributeB
end

Use the AutoStripAttributes gem for Rails. it'll help you to easily and cleanly accomplish the task.
class User < ActiveRecord::Base
# Normal usage where " aaa bbb\t " changes to "aaa bbb"
auto_strip_attributes :nick, :comment
# Squeezes spaces inside the string: "James Bond " => "James Bond"
auto_strip_attributes :name, :squish => true
# Won't set to null even if string is blank. " " => ""
auto_strip_attributes :email, :nullify => false
end

Note I haven't tried this and it might be a crazy idea, but you could create a class like this:
MyActiveRecordBase < ActiveRecord::Base
require 'whitespace_helper'
include WhitespaceHelper
end
... and then have your models inherit from that instead of AR::Base:
MyModel < MyActiveRecordBase
# stuff
end

Related

Grab original method/column name from Rails alias_attribute

Right now, I have some legacy classes with differently-named columns that I've aliased to a new, common name via Rails' alias_attribute, as below:
class User < ActiveRecord::Base
alias_attribute :id, :UserId
...
end
class Car < ActiveRecord::Base
alias_attribute :id, :CarId
...
end
For some logging purposes, I need to access the old column names (eg. CarId and UserId). Is there a general way to access the old name from alias_attribute via its alias? Renaming the old columns is not ideal, since many other parts of the app are still using the old column names.
alias_attribute is a very simple method. All it does is, well, define aliases.
def alias_attribute(new_name, old_name)
# The following reader methods use an explicit `self` receiver in order to
# support aliases that start with an uppercase letter. Otherwise, they would
# be resolved as constants instead.
module_eval <<-STR, __FILE__, __LINE__ + 1
def #{new_name}; self.#{old_name}; end # def subject; self.title; end
def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end
def #{new_name}=(v); self.#{old_name} = v; end # def subject=(v); self.title = v; end
STR
end
So no, there's no way to retrieve the original column name.

Filtering fields from ActiveRecord/ActiveModel JSON output (by magic!)

I want to filter out specific fields from ActiveRecord/ActiveModel classes when outputting JSON.
The most straightforward way to do this is just overriding as_json, perhaps like so:
def as_json (options = nil)
options ||= {}
super(options.deep_merge({:except => filter_attributes}))
end
def filter_attributes
[:password_digest, :some_attribute]
end
This works, but it's a little verbose and lends itself to not being DRY pretty fast. I thought it would be nice to just declare the filtered properties with a magical class method. For example:
class User < ActiveRecord::Base
include FilterJson
has_secure_password
filter_json :password_digest
#...
end
module FilterJson
extend ActiveSupport::Concern
module ClassMethods
def filter_json (*attributes)
(#filter_attributes ||= Set.new).merge(attributes.map(&:to_s))
end
def filter_attributes
#filter_attributes
end
end
def as_json (options = nil)
options ||= {}
super(options.deep_merge({:except => self.class.filter_attributes.to_a}))
end
end
The problem with this is getting it to deal with inheritance properly. Let's say I subclass User:
class SecretiveUser < User
filter_json :some_attribute, :another_attribute
#...
end
Logically, it makes sense to filter out :some_attribute, :another_attribute, and also :password_digest.
However, this will only filter the attributes declared on the class. To the desired end, I tried to call super within filter_attributes, but that failed. I came up with this, and it's a hack.
def filter_attributes
if superclass.respond_to?(:filter_attributes)
superclass.filter_attributes + #filter_attributes
else
#filter_attributes
end
end
This is obviously brittle and not idiomatic, but there's the "what" that I'm trying to accomplish.
Can anyone think of a way to do it more correctly (and hopefully more elegantly)? Thanks!
I think it is a safer solution to white-list attributes than to black-list them. This will prevent unwanted future attributes added to User or SomeUser from making it into your JSON response because you forgot to add said attributes to filter_json.
You seem to be looking for a solution to your specific inheritance issue. I'm still going to point out active_model_serializers, as I feel it is a saner way to manage serialization.
class UserSerializer < ActiveModel::Serializer
attributes :id, :first_name, :last_name
end
class SecretUserSerializer < UserSerializer
attributes :secret_attribute, :another_attribute
end
Given some SecretUser s you can do
SecretUserSerializer.new(s).as_json
and you'll get :id, :first_name, :last_name, :secret_attribute, and :another_attribute. The inheritance works as expected.

Using strip before field gets saved to databse in ROR

I would like to strip all whitespace in some fields before they go into my database.
I am using devise and have added additional fields to the members table (used members instead of users).
On my sign up form I have some fields such as telephone and address however I would like to strip all whitespace for certain fields like :telephone, :mobile and :emergency_number.
Sounds like a job for before_save!
class Member < ActiveRecord::Base
before_save :strip_whitespace
private
def strip_whitespace
self.telephone.gsub!(/\s+/, '')
# etc...
end
end
An easy way to #Chowlett's solution
class Member < ActiveRecord::Base
before_save :strip_whitespace
private:
def strip_whitespace
self.telephone.join('')
# etc...
end
end

rails virtual attributes won't read from form_for submission

I am trying to implement a rails tagging model as outlined in Ryan Bate's railscast #167. http://railscasts.com/episodes/167-more-on-virtual-attributes
This is a great system to use. However, I cannot get the form to submit the tag_names to the controller. The definition for tag_names is :
def tag_names
#tag_names || tags.map(&:name).join(' ')
end
Unfortunately, #tag_names never gets assigned on form submission in my case. I cannot figure out why. SO it always defaults to tags.map(&:name).join(' '). This means that I can't create Articles because their tag_names are not there, and I also can't edit these tags on existing ones. Anyone can help?
In short, your class is missing a setter (or in Ruby lingo, an attribute writer). There are two ways in which you can define a setter and handle converting the string of space-separated tag names into Tag objects and persist them in the database.
Solution 1 (Ryan's solution)
In your class, define your setter using Ruby's attr_writer method and convert the string of tag names (e.g. "tag1 tag2 tag3") to Tag objects and save them in the database in an after save callback. You will also need a getter that converts the array of Tag object for the article into a string representation in which tags are separated by spaces:
class Article << ActiveRecord::Base
# here we are delcaring the setter
attr_writer :tag_names
# here we are asking rails to run the assign_tags method after
# we save the Article
after_save :assign_tags
def tag_names
#tag_names || tags.map(&:name).join(' ')
end
private
def assign_tags
if #tag_names
self.tags = #tag_names.split(/\s+/).map do |name|
Tag.find_or_create_by_name(name)
end
end
end
end
Solution 2: Converting the string of tag names to Tag objects in the setter
class Article << ActiveRecord::Base
# notice that we are no longer using the after save callback
# instead, using :autosave => true, we are asking Rails to save
# the tags for this article when we save the article
has_many :tags, :through => :taggings, :autosave => true
# notice that we are no longer using attr_writer
# and instead we are providing our own setter
def tag_names=(names)
self.tags.clear
names.split(/\s+/).each do |name|
self.tags.build(:name => name)
end
end
def tag_names
tags.map(&:name).join(' ')
end
end

Rails attr_accessible does not work for :type?

Im trying set the single table inheritance model type in a form. So i have a select menu for attribute :type and the values are the names of the STI subclasses. The problem is the error log keeps printing:
WARNING: Can't mass-assign these protected attributes: type
So i added "attr_accessible :type" to the model:
class ContentItem < ActiveRecord::Base
# needed so we can set/update :type in mass
attr_accessible :position, :description, :type, :url, :youtube_id, :start_time, :end_time
validates_presence_of :position
belongs_to :chapter
has_many :user_content_items
end
Doesn't change anything, the ContentItem still has :type=nil after .update_attributes() is called in the controller. Any idea how to mass update the :type from a form?
we can override attributes_protected_by_default
class Example < ActiveRecord::Base
def self.attributes_protected_by_default
# default is ["id","type"]
["id"]
end
end
e = Example.new(:type=>"my_type")
You should use the proper constructor based on the subclass you want to create, instead of calling the superclass constructor and assigning type manually. Let ActiveRecord do this for you:
# in controller
def create
# assuming your select has a name of 'content_item_type'
params[:content_item_type].constantize.new(params[:content_item])
end
This gives you the benefits of defining different behavior in your subclasses initialize() method or callbacks. If you don't need these sorts of benefits or are planning to change the class of an object frequently, you may want to reconsider using inheritance and just stick with an attribute.
Duplex at railsforum.com found a workaround:
use a virtual attribute in the forms
and in the model instead of type
dirtectly:
def type_helper
self.type
end
def type_helper=(type)
self.type = type
end
Worked like a charm.
"type" sometimes causes troubles... I usually use "kind" instead.
See also: http://wiki.rubyonrails.org/rails/pages/ReservedWords
I followed http://coderrr.wordpress.com/2008/04/22/building-the-right-class-with-sti-in-rails/ for solving the same problem I had. I'm fairly new to Rails world so am not so sure if this approach is good or bad, but it works very well. I've copied the code below.
class GenericClass < ActiveRecord::Base
class << self
def new_with_cast(*a, &b)
if (h = a.first).is_a? Hash and (type = h[:type] || h['type']) and (klass = type.constantize) != self
raise "wtF hax!!" unless klass < self # klass should be a descendant of us
return klass.new(*a, &b)
end
new_without_cast(*a, &b)
end
alias_method_chain :new, :cast
end
class X < GenericClass; end
GenericClass.new(:type => 'X') # => #<X:0xb79e89d4 #attrs={:type=>"X"}>

Resources