Rails/Mongoid error messages in nested attributes - validation

I have a contact info class defined like this:
class ContactInfo
include Mongoid::Document
validates_presence_of :name, :message => ' cannot be blank'
field :name, :type => String
field :address, :type => String
field :city, :type => String
field :state, :type => String
field :zip, :type => String
field :country, :type => String
embedded_in :user
end
This contact info class is embedd as a nested attribute inside my user class:
class PortalUser
include Mongoid::Document
accepts_nested_attributes_for :contact_info
end
When I attempt to save a user without a name, I get an error message like this:
Contact info is invalid
However, this is not very useful to the end user, because he or she doesn't know what contact info is invalid. The REAL message should be 'Name cannot be blank'. However, this error is not being propagated upwards. Is there a way to get the 'Name cannot be blank' message inside the user.errors instead of the 'Contact info is invalid' error message?
Thanks

Here's the solution I eventually came up with:
Added these lines to the user class
after_validation :handle_post_validation
def handle_post_validation
if not self.errors[:contact_info].nil?
self.contact_info.errors.each{ |attr,msg| self.errors.add(attr, msg)}
self.errors.delete(:contact_info)
end
end

Instead of returning the user.errors.full_messages create a specific error message method for your user model where you handle all your embedded document errors.
class PortalUser
include Mongoid::Document
accepts_nested_attributes_for :contact_info
def associated_errors
contact_info.errors.full_messages unless contact_infos.errors.empty?
end
end
and in your controller
flash[:error] = user.associated_errors

My solution that covers each embedded document validation error is the following:
after_validation :handle_post_validation
def handle_post_validation
sub_errors = ActiveModel::Errors.new(self)
errors.each do |e|
public_send(e).errors.each { |attr,msg| sub_errors.add(attr, msg)}
end
errors.merge!(sub_errors)
end

There might be a solution in the controller...
in the create action you can add something like
params[:portal_user][:contact_info_attributes] = {} if params[:portal_user] && params[:portal_user][:contact_info_attributes].nil?
This will force contact_info creation, and will trigger an error on the right field
If you don't add this, contact_info is not created

Related

DataMapper case-insensitive unique validation

I have something like:
class User
include DataMapper::Resource
property :id, Serial
property :username, String, :unique => true
end
post '/signup' do
user = User.create(username: params['username'])
if user.save
puts "New user was created"
else
puts user.errors
end
end
Parameter :unique => true is case-sensitive. It does not prevent to create users with usernames 'admin' and 'Admin'. How can I validate case-insensitive username unique, with out downcased username property, so users can make usernames as they choose.
You can provide your own custom validation:
class User
include DataMapper::Resource
property :id, Serial
property :username, String
validates_with_method :username,
:method => :case_insensitive_unique_username
def case_insensitive_unique_username
User.first(conditions: ["username ILIKE ?", self.username]).nil?
end
end
Note that ILIKE will only work with PostgreSQL, you will have to find out how to find records case insensitively with your specific adapter for yourself.

Mongoid : The Validation "validates_uniqueness_of" is only triggered when the specific field changes

I have a unique constraint defined using a condition. But the following test does not pass :
class Dummy
include Mongoid::Document
field :name, :type => String
field :status, :type => Boolean
validates_uniqueness_of :name, if: :status
end
describe "UniquenessValidator" do
let!(:d1) { Dummy.create!(name: 'NAME_1', status: true) }
let!(:d2) { Dummy.create!(name: 'NAME_1', status: false) }
it "should raise an error" do
expect {
d2.status = true
d2.save!
}.to raise_error
end
end
Since name_changed? is false, no validation seems to happen and therefore the uniqueness condition is not checked.
Is it a bug ? Or have I forgotten something ? I guess it's an optimization to avoid to run the validation each time the element is modified.
In that case what is the good way to trigger the validation when the status is changed ?
Thanks!
As you case is a edge case, I would advice you to create your own validator class for this, something like this should work:
class NameUniquenessValidator < Mongoid::Validatable::UniquenessValidator
private
def validation_required?(document, attribute)
return true "name" == attribute.to_s
super
end
end
class Dummy
include Mongoid::Document
field :name, :type => String
field :status, :type => Boolean
validates_with(NameUniquenessValidator, :name, if: :status)
end
You are updating the status field so, you need to validate this field. You can do something like this:
class Dummy
include Mongoid::Document
field :name, :type => String
field :status, :type => Boolean
validates_uniqueness_of :name, if: :status
validates_uniqueness_of :status, scope: :name, if: :status
end
I don't know if you can force mongoid to validate all fields when you update a single field.

"Retweets" with Datamapper

I want to impliment something which is similar to Twitter Repost System, therefore I will use this as an example. So let's say I have a Tweet Model and I want to allow other user to repost a certian tweet of another user, how do I impliment something like this?
I thought I would be a cool idea to put the retweet class into the tweet to be able to acess the repost too when I use Tweet.all to recive all tweets stored in the database, but somehow I didn't worked as expected...
The following Code is just an example which should show how to impliment this even if it is not working...
Any ideas how I could build a working repost model which also allows me to access both tweets and retweet by using Tweet.all?
class Tweet
class Retweet
include DataMapper::Resource
belongs_to :user, key => true
belongs_to :tweet, key => true
end
include DataMapper::Resource
property :text, String
property :timestamp, String
belongs_to :user
end
Important: I should be carrierwave compatible.
class Tweet
include DataMapper::Resource
property :id, Serial
has n, :retweets, 'Tweet', :child_key => :parent_id
belongs_to :parent, 'Tweet', :required => false
belongs_to :user
def is_retweet?
self.parent_id ? true : false
end
end
original = Tweet.create :user => user1
retweet = Tweet.create :parent => original, :user => user2
retweet.is_retweet? # => true

Validation error in test but error array is empty

I have this code for creating a topic and post in a forum application in Rails 3.1:
def create
#topic = Topic.new(:name => params[:topic][:name], :last_post_at => Time.now)
#topic.forum_id = params[:topic][:forum_id]
#topic.user = current_user
if #topic.save
#post = Post.new(:content => params[:post][:content])
#post.topic = #topic
#post.user = current_user
#post.save!
...
When posting to the create method via the corresponding form, the topic and the post are created and both save calls are successful.
When I call the create method via a functional test, the topic is saved but the post has validation errors.
ActiveRecord::RecordInvalid: Validation failed:
app/controllers/topics_controller.rb:23:in `create'
test/functional/topics_controller_test.rb:26:in `block in <class:TopicsControllerTest>'
The test looks like this:
test "should create topic" do
post :create, :topic => {:name => "New topic", :forum_id => forums(:one).id}, :post => {:content => "Post content"}
end
(current_user is logged in via a setup method.)
When I display the errors of the post object via the debugger or with #post.errors.full_messages, the error array is empty.
The Post model looks like this:
class Post < ActiveRecord::Base
attr_accessible :content
belongs_to :topic
belongs_to :user
end
And the Topic model:
class Topic < ActiveRecord::Base
belongs_to :user
belongs_to :last_poster, class_name: 'User'
attr_accessible :name, :last_poster_id, :last_post_at
belongs_to :forum
has_many :posts, :dependent => :destroy
end
How can I find out what is causing the validation error?
The problem was that I used mocha's Post.any_instance.stubs(:valid?).returns(false) in a test that was executed before my failing test.
Apparently, you have to restore the original behavior before proceeding with other tests by calling Post.any_instance.unstub(:valid?).

Rails Model callback: Passing field as parameter to callback classes?

I want to implement before_validaton callback in a separate class so that it can be reused by multiple model classes.
Here in callback i want to strip field passed as parameter but i am not sure how to pass parameter to callback class. Also i want to pass this as reference rather than by value(not sure if this concept is in Ruby Rails). I am following the link http://guides.rubyonrails.org/active_record_validations_callbacks.html#callback-classes
Here is code which is not completely correct, please help for same
class StripFieldsCallback
def self.before_validation(field)
field = field.strip
end
end
class User < ActiveRecord::Base
validates_uniqueness_of :name, :case_sensitive => false
validates_length_of :name, :maximum => 50
before__validation StripFieldsCallback(name)
end
If i define method in model in itself rather than defining in separate callback class code is like this (which works fine)
class User < ActiveRecord::Base
validates_uniqueness_of :name, :case_sensitive => false
validates_length_of :name, :maximum => 50
before__validation :strip_blanks
protected
def strip_blanks
self.name = self.name.strip
end
end
Of course it is not good to replicate methods in all of models so i want to define method in callback classes.
You may do this or use normalize_attributes gem
module StripFieldsCallback
def before_validation_z(field)
write_attribute(field, read_attribute(field).strip) if read_attribute(field)
end
end
class User < ActiveRecord::Base
include StripFieldsCallback
before_validation lambda{|data| data.before_validation_z(:name)}
end

Resources