validates :something, :confirmation => true & attr_accessor confusion - ruby-on-rails-3

am struggling with Ruby validates :confirmation => true in my Rails app. Consider the following code:
# == Schema Information
#
# Table name: things
#
# id :integer not null, primary key
# pin :integer(8)
# created_at :datetime
# updated_at :datetime
#
class Things < ActiveRecord::Base
#attr_accessor :pin
attr_accessible :pin, :pin_confirmation
validates :pin,
:confirmation => true,
:length => { :within => 7..15 },
:numericality => { :only_integer => true }
end
As the code is above, my validation works fine from the Rails console:
1.9.3-p0 :002 > l2 = Thing.create! :pin => 1234567, :pin_confirmation => 1111111
ActiveRecord::RecordInvalid: Validation failed: Pin doesn't match confirmation
....
1.9.3-p0 :003 > l2 = Thing.create! :pin => 1234567, :pin_confirmation => 1234567
=> #<Thing id: 2, pin: 1234567, created_at: "2012-01-30 22:03:29", updated_at: "2012-01-30 22:03:29">
but testing both through rspec and manually from rails server causes the validation to fail, saying they don't match when they darn well do. If I uncomment the attr_accessor for :pin, the validations will pass but the :pin of course will not be written to the database.
I'm completely sure I'm missing something obvious and vital- just running into a brick wall.

Like Frederick said above, the issue is comparing an instance of String with an instance of Integer.
More than likely, here's what you have in your controller:
Thing.new(params[:thing]) # note all these params come in as a string
What's happening is that since #pin is an integer column, you will get the following behaviour:
my_thing = Thing.new
my_thing.pin = "123456"
my_thing.pin # Will be the integer 123456, the attribute has been auto-cast for you
But since #pin_confirmed is just a regular attribute, not an integer column, here's the weirdness you will see:
my_thing = Thing.new
my_thing.pin_confirmation = "123456"
my_thing.pin_confirmation # Will be the *string* "123456", the attribute has been set as is
So naturally, in that case, no matter what values you have, since they come in through the "params" hash (which is always a set of strings), you will end up assigning string values to both attributes, but they will be cast to different types.
There are several ways to fix this.
One, you could create #pin_confirmation as an integer column in the database.
The other is you could add an attribute setter for #pin_confirmation of the following form:
def pin_confirmation=(val)
#pin_confirmation = val.to_i
end

Related

Improving query performance of with rails and mongo

Assuming that I have two models:
class Radar
include MongoMapper::Document
plugin MongoMapper::Plugins::IdentityMap
key :name, String, :required => true
key :slug, String, :required => true
many :stores, index: true
def self.retrieve_radars_and_stores
radars = Radar.all
radars.map { |r|
{
name: r.name,
ip: r.ip,
stores: r.serializable_stores
}
}
end
def serializable_stores
stores.map { |store|
{
name: store.try(:name),
location: store.try(:location)
}
}
end
end
class Store
include MongoMapper::Document
key :name, String, :required => true
key :location, String, :required => true
ensure_index :name
ensure_index :location
end
So, I have a controller method that calls Radar.retrieve_radars_and_stores, getting the result and returning as json.
The code works perfectly, however, having more than 20.000 records, it spends about 1 minute to process the method. Commenting out the stores: r.serializable_stores line, the method spends only few seconds.
My question is, how can I improve this query, reducing the time elapsed?
Thanks!

MassAssignmentSecurity Error when using attr_encrypted (attr_encryptor) gem

For my rails 3.2.3 app, I am using attr_encryptor, which is a fork by danpal of attr_encrypted. I have followed the instructions as given here, but I am getting the following error message when I try to create a new Patient record:
ActiveModel::MassAssignmentSecurity::Error in PatientsController#create
Can't mass-assign protected attributes: mrn, last_name, first_name, date_of_birth(1i), date_of_birth(2i), date_of_birth(3i)
As the instructions say, I have added encrypted_#{field}, encrypted_#{field}_salt, and encrypted_#{field}_iv columns to my Patients table while dropping their non-encrypted counterparts.
The Patient model looks like:
class Patient < ActiveRecord::Base
attr_accessible :age, :gender
attr_encrypted :last_name, :key => 'key 1'
attr_encrypted :first_name, :key => 'key 2'
attr_encrypted :mrn, :key => 'key 3'
attr_encrypted :date_of_birth, :key => 'key 4'
# ...
end
My create method in my Patient controller looks like:
PatientsController < ApplicationController
# ...
def create
#patient = Patient.new
#patient.first_name = params[:patient][:first_name]
#patient.last_name = params[:patient][:last_name]
#patient.mrn = params[:patient][:mrn]
#patient.date_of_birth = Date.new(params[:patient]['date_of_birth(1i)'],
params[:patient]['date_of_birth(2i)'],
params[:patient]['date_of_birth(3i)'])
if #patient.save
# do stuff
else
# do other stuff
end
end
# ...
end
What am I doing wrong? Thanks in advance for the help!
You need to mark these attributes with attr_accessible as well as attr_encrypted since the latter does not imply the former.
This might also be relevant for the date field: Correct way to handle multiparameter attributes corresponding to virtual attributes

Rails 3: Apply the same validation rules to multiple table fields

I have created a model with several fields that should accept the same data format (strings, but can be anything, FWIW). I'd like to apply the same validation rule to all those fields. Of course, I can just go ahead and copy/paste stuff, but that would be against DRY principle, and common sense too...
I guess this one is pretty easy, but I'm a Rails newcomer/hipster, so excuse-moi for a trivial question. =)
So if you had say three fields to validate:
:first_name
:last_name
:age
And you wanted them all to be validated? So something like this:
validates_presence_of :first_name, :last_name, :age
Edit: There are numerous different validation methods in Rails )and they're wonderfully flexible). For the format of the field you can use validates_format_of, and then use a Regular Expression to match against it. Here's an example of matching an email:
validates_format_of :email, :with => /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
I'd check out the Active Record Validations and Callbacks guide; it provides comprehensive insight about a lot of the features Active Record provides in terms of validation. You can also check out the documentation here.
If you are using any of the built-in validations (presence, length_of) you can apply a single validation to multiple attributes like this:
validates_presence_of :name, :email
If you have custom logic you can create a validator object to house the code and apply it individually
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << (options[:message] || "is not an email") unless
value =~ /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
end
end
def Person
validates :home_email, :email => true
validates :work_email, :email => true
end
see: http://thelucid.com/2010/01/08/sexy-validation-in-edge-rails-rails-3/
In Rails 4 you can apply the same validation to multiple columns by using a loop:
[:beds, :baths].each do |column|
validates column, allow_blank: true, length: { maximum: 25 }
end
Both beds and baths are validated using the same validations.
Edit:
In Rails 4.2 you can do this same thing by putting multiple symbols after the validates function call. Example:
validates :beds, :baths, allow_blank: true
Use Themis for this:
# Describe common validation in module
module CommonValidation
extend Themis::Validation
validates_presence_of :foo
validates_length_of :bar, :maximum => 255
end
class ModelA < ActiveRecord::Base
# import validations
include CommonValidation
end
class ModelB < ActiveRecord::Base
# import validations
include CommonValidation
end
Or you can use "with_options", for example:
with_options presence: true do |video|
REQUIRED_COLUMNS.map do |attr|
video.validates attr
end
end

Why is user.save true but email shows as nil?

I'm using a nested model form for sign-up and am working through the kinks as a beginner. One issue that popped up in particular though that I don't really get is user.email is returning as nil.
Before I started playing around with the nested model form, I could create records in the console wihtout a problem. Now, however I can't create records and some of the latest records created have nil as their email. (I'm not sure if it has anything to do with the nested model at all, but that's my reference point for when it started going haywire.)
If I go into rails console to create a new User/Profile, I follow this process:
user = User.new
user.email = ""
user.password = ""
user.profile = Profile.new
user.profile.first_name = ""
...
user.profile.save
user.save
Everything goes well until user.save, which gives me the NameError: undefined local variable or method 'params' for #<User:>. In rails console it pinpoints to user.rb:25 in create_profile
So here is my User model:
class User < ActiveRecord::Base
attr_accessor :password, :email
has_one :profile, :dependent => :destroy
accepts_nested_attributes_for :profile
validates :email, :uniqueness => true,
:length => { :within => 5..50 },
:format => { :with => /^[^#][\w.-]+#[\w.-]+[.][a-z]{2,4}$/i }
validates :password, :confirmation => true,
:length => { :within 4..20 },
:presence => true,
:if => :password_required?
before_save :encrypt_new_password
after_save :create_profile
def self.authenticate(email, password)
user = find_by_email(email)
return user if user && user.authenticated?(password)
end
def authenticated?(password)
self.hashed_password == encrypt(password
end
protected
def encrypt_new_password
return if password.blank?
self.hashed_password = encrypt(password)
end
def password_required?
hashed_password.blank? || password.present?
end
def encrypt(string)
Digest::SHA1.hexdigest(string)
end
end
Can anyone help me figure out what's going on?
UPDATE: I tried changing my regex but I'm still seeing nil for email. Though a prior SO post said not to blindly copy regex without testing, so maybe I just didn't test it correctly. Good news though: I no longer get the error.
attr_accessor simply defines a "property" on the object and has no relation to the attributes of a ActiveRecord model (attributes is a Hash of the fields and values obtained from a table).
ActiveRecord does not save such "properties" as defined by the attr_accessor. (Essentially, attr_accessor defines a attr_reader and attr_writer (i.e. "getter" and "setter") at the same time)

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?