Options for validates_with - ruby-on-rails-3

I'm not able to access the values, passed as option in 'validates_with'
My model:
class Person < ActiveRecord::Base
include ActiveModel::Validations
attr_accessible :name, :uid
validates :name, :presence => "true"
validates :uid, :presence => "true"
validates_with IdValidator, :attr => :uid
My Custom Validator:
Class IdValidator < ActiveModel::Validator
def validate(record)
puts options[:attr]
...
...
end
end
For testing purpose, I'm printing "options[:attr]" and all I see is ":uid" in the terminal and not the value in it. Please help!

When you pass in :attr => :uid, you're just passing in a symbol. There's no magic happening hereā€”it just takes the hash of options you've attached and delivers it as the options hash. So when you write it, you see the symbol you've passed.
What you probably want is
Class IdValidator < ActiveModel::Validator
def validate(record)
puts record.uid
...
...
end
end
Because validates_with is a class method, you can't get the values of an individual record in the options hash. If you are interested in a more DRY version, you could try something like:
class IdValidator < ActiveModel::Validator
def validate(record)
puts record[options[:field]]
end
end
class Person < ActiveRecord::Base
include ActiveModel::Validations
attr_accessible :name, :uid
validates :name, :presence => "true"
validates :uid, :presence => "true"
validates_with IdValidator, :field => :uid
end
Where you pass in the name of the field you want evaluated.

Related

Rails Rspec & FactoryGirl testing Association

I have to model's where I accept Nested Attributes. I would like to build a test to make sure the nested attribute cant be blank etc. I really don't understand how I can make the test.
My two simple models:
# SeoMapping Model
class SeoMapping < ActiveRecord::Base
belongs_to :mappingtable, :polymorphic => true
attr_accessible :seo_url
validates :seo_url, :presence => true, :uniqueness => true
end
# Page Model
class Page < ActiveRecord::Base
has_one :seo_mappings, :as => :mappingtable, :dependent => :destroy
accepts_nested_attributes_for :seo_mappings
attr_accessible :content, :h1, :meta_description, :title, :seo_mappings_attributes
.........
end
Here are my factories for Page and Seo:
FactoryGirl.define do
factory :page do |f|
seo_mapping
f.title { Faker::Name.name }
f.h1 { Faker::Lorem.words(5) }
f.meta_description { Faker::Lorem.words(10) }
f.content { Faker::Lorem.words(30) }
end
end
FactoryGirl.define do
factory :seo_mapping do |f|
f.seo_url { Faker::Internet.domain_word }
end
end
And my tests:
require 'spec_helper'
describe Page do
it "has a valid factory" do
expect(create(:page)).to be_valid
end
# Cant get this spec to work?
it "it is invalid without a seo_url" do
page = build(:page)
seo_mapping = build(:seo_mapping, seo_url: nil)
page.seo_mapping.should_not be_valid
# expect(build(:page, :seo_mapping_attributes[:seo_url] => nil)).to_not be_valid
end
it "is invalid without a title" do
expect(build(:page, title: nil)).to_not be_valid
end
...............
end
Usually for this sort of thing I use a gem called shoulda_matchers. It lets you simply assert that your model validates presence of specific attributes.
it { should validate_presence_of(:seo_url) }
it { should validate_uniqueness_of(:seo_url) }
If you don't want to use the gem, try something like this:
seo_mapping = build(:seo_mapping, seo_url: nil)
page = build(:page, seo_mapping: seo_mapping)
page.should_not be_valid

Rails SRP Modules, attr_accessible

I'm learning SOLID and trying to introduce SRP into my rails app. I have the following user model with basic authentication:
class User < ActiveRecord::Base
attr_accessible :password, :password_confirmation
attr_accessor :password
before_save :encrypt_password
validates_confirmation_of :password
validates_presence_of :password, :on => :create
def self.authenticate(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
def self.generate_random_password
return ActiveSupport::SecureRandom.hex(12)
end
end
I want to move all the authentication logic to a module like so:
module Authentication
attr_accessible :password, :password_confirmation
attr_accessor :password
before_save :encrypt_password
validates_confirmation_of :password
validates_presence_of :password, :on => :create
def self.authenticate(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
def self.generate_random_password
return ActiveSupport::SecureRandom.hex(12)
end
end
And my user model would be like this:
class User < ActiveRecord::Base
include Authentication #SRP in action! :P
end
And now the errors begin:
undefined method `attr_accessible' for Authentication:Module
How would I fix this error? I am convinced this is the best start to introduce SRP to my Rails app.
Thanks
The attr_accessible method is called in the wrong scope. Take a look at Concerns to fix this:
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
This would result in:
module Authentication
extend ActiveSupport::Concern
included do
attr_accessible :password, :password_confirmation
end
...
end
This will also take care of you class and instance method definitions.
NOTE: To be specific, this does not quite achieve SRP, since multiple responsibilities are still shared within the same class, even though they are separated into modules. Class composition through referencing or decorating would be a more strict solution , but I prefer the pragmatic approach of modules.

How to do conditional statement of validation from controller name in model

Example:
account_controller: do not validate a password
password_controller: validate a password
my idea is...
class User
include Mongoid::Document
...
validates :username,
:presence => true
validates :password,
:presence => { :if => :passord? }
...
def password?
# self.controller.to_s == 'password'
end
end
First of all, my idea is wrong?
Anyone have another good idea?
You can add virtual attribute to your model and conditional validation:
class User
attr_accessor :skip_password_validation
validates :password, :unless => :skip_password_validation
end
And put something like this into controller:
user.skip_password_validation = true

Rails: awesome_nested_set issues

I am using the awesome_nested_set to do a simple drag and drop reordering of news items and the post happens but the position field in my DB is not updated...
Here is my model:
class NewsItem < ActiveRecord::Base
translates :title, :body, :external_url
attr_accessor :locale, :position # to hold temporarily
alias_attribute :content, :body
validates :title, :content, :publish_date, :presence => true
has_friendly_id :title, :use_slug => true
acts_as_indexed :fields => [:title, :body]
acts_as_nested_set
default_scope :order => "publish_date DESC"
# If you're using a named scope that includes a changing variable you need to wrap it in a lambda
# This avoids the query being cached thus becoming unaffected by changes (i.e. Time.now is constant)
scope :not_expired, lambda {
news_items = Arel::Table.new(NewsItem.table_name)
where(news_items[:expiration_date].eq(nil).or(news_items[:expiration_date].gt(Time.now)))
}
scope :published, lambda {
not_expired.where("publish_date < ?", Time.now)
}
scope :latest, lambda { |*l_params|
published.limit( l_params.first || 10)
}
# rejects any page that has not been translated to the current locale.
scope :translated, lambda {
pages = Arel::Table.new(NewsItem.table_name)
translations = Arel::Table.new(NewsItem.translations_table_name)
includes(:translations).where(
translations[:locale].eq(Globalize.locale)).where(pages[:id].eq(translations[:news_item_id]))
}
def not_published? # has the published date not yet arrived?
publish_date > Time.now
end
# for will_paginate
def self.per_page
20
end
end
Anyone know why this wouldn't work?

Rails 3: validates :presence => true vs validates_presence_of

What is the difference between validates :presence and validates_presence_of? Looking through ActiveModel it looks like they setup the validation the same way. However, given the following model definition:
class Account < ActiveRecord::Base
has_one :owner_permission, :class_name => 'AccountPermission', :conditions => { :owner => true, :admin => true }
has_one :owner, :class_name => 'User', :through => :owner_permission, :source => :user
validate :owner, :presence => true
validates_associated :owner
end
Calling save on an instance of Account does not validate the presence of owner. Though, if I use validates_presence_of it will.
All those validates_whatever_of :attr macros do is call validates :attr, :whatever => true.
The problem is you are using validate and not validates.
In Rails 3.x and 4.x - it is now encouraged to use the following syntax:
validates :email, presence: true
validates :password, presence: true
Instead of the 2.x way:
validates_presence_of :email
validates_presence_of :password
In fact validates and validates_presence_of is not entirely equal !
validates_presence_of is allowing you to also lazily check by example of the value in the field is included in another table.
Like that:
validates_presence_of :pay_type, :inclusion => PaymentType.names
Which is something you can't do as easily with something like that
validates :pay_type, presence, :inclusion => PaymentType.names
Cause the inclusion is only evaluated the first time (not in a lazy way)
I would have thought that it is appropriate to use validates :foo presence: true when you want to include other validations of :foo such as length or uniqueness. But if you know the only validation you'll need for an attribute is presence, then validates_presence_of appears to be more efficient.
So:
validates :foo, length: {maximum: 50}, uniqueness: true,
format: {with: /bar/},
presence: true # lots of validations needed
But:
validates_presence_of :foo # only presence validation needed