I needed an error object to pass errors between controllers and js views for ajax responses. I wanted to use ActiveModel::Errors since then I could easily merge errors from model validation to non-model errors. But Ive been having some problems using this approach.
Using ActiveModel::Errors.new(nil)
If I try and call to_a() like so:
#errors = ActiveModel::Errors.new(nil)
#errors[:test] = "is a required field."
#errors.to_a().join("<br />")
I get the error:
undefined method `human_attribute_name' for NilClass:Class
Its trying to call the human_attribute_name for the nil I passed into Errors.new which is suppose to be a ActiveRecord Model.
I am wondering if anyone knows of a better way to handle errors or if the approach I am taken has already been made into some gem.
Chances are your validations are related to "something" that could be encapsulated in a model - it just doesn't need to be an ActiveRecord model.
You can use validations in any plain ruby object by doing something like this:
require 'active_model'
class Band
include ActiveModel::Validations
validates_presence_of :name
attr_accessor :name
end
Then, the usual suspects would work just fine:
b = Band.new
b.valid?
#false
b.name = "Machine Head"
b.valid?
#true
Related
I need to handle a particular case of generating email views with URLs constructed from non-persisted data.
Example : assume my user can create posts, and that triggers a post creation notification email, I'd like to send the user an example of fake post creation. For this, I am using a FactoryGirl.build(:post) and passing this to my PostMailer.notify_of_creation(#post)
In everyday Rails life, we use the route url_helpers by passing as argument the model itself, and the route generator will automatically convert the model into its ID to be used for the route URL generation (in article_path(#article), the routes helper converts #article into #article.id for constructing the /articles/:id URL.
I believe it is the same in ActiveRecord, but anyways in Mongoid, this conversion fails if the model is not persisted (and this is somewhat nice as it prevents the generation of URLs that may not correspond to actual data)
So in my specific case, URL generation crashes as the model is not persisted:
<%= post_url(#post_not_persisted) %>
crashes with
ActionView::Template::Error: No route matches {:action=>"show", :controller=>"posts", :post_id=>#<Post _id: 59b3ea2aaba9cf202d4eecb6 ...
Is there a way I can bypass this limitation only in a very specific scope ? Otherwise I could replace all my resource_path(#model) by resource_path(#model.id.to_s) or better #model.class.name but this doesn't feel like the right situation...
EDIT :
The main problem is
Foo.new.to_param # => nil
# whereas
Foo.new.id.to_s # => "59b528e8aba9cf74ce5d06c0"
I need to force to_param to return the ID (or something else) even if the model is not persisted. Right now I'm looking at refinements to see if I can use a scoped monkeypatch but if you have better ideas please be my guest :-)
module ForceToParamToUseIdRefinement
refine Foo do
def to_param
self.class.name + 'ID'
end
end
end
However I seem to have a small scope problem when using my refinement, as this doesn't bubble up as expected to url_helpers. It works fine when using te refinement in the console though (Foo.new.to_param # => 59b528e8aba9cf74ce5d06c0)
I found a way using dynamic method override. I don't really like it but it gets the job done. I am basically monkeypatching the instances I use during my tests.
To make it easier, I have created a class method example_model_accessor that basically behaves like attr_accessor excepts that the setter patches the #to_param method of the object
def example_model_accessor(model_name)
attr_reader model_name
define_method(:"#{model_name}=") do |instance|
def instance.to_param
self.class.name + 'ID'
end
instance_variable_set(:"##{model_name}", instance)
end
end
Then in my code I can just use
class Testing
example_model_accessor :message
def generate_view_with_unpersisted_data
self.message = FactoryGirl.build(:message)
MessageMailer.created(message).deliver_now
end
end
# views/message_mailer/created.html.erb
...
<%= message_path(#message) %> <!-- Will work now and output "/messages/MessageID" ! -->
How can add the error message into #errors on controller then show it on the view like:
<%= #question.errors[:tag][0] %>
with tag is not model element.
Take a look at this part of the Rails validation guide. They work by creating a custom validator, which just appends the desired error message to the desired hash entry.
In your case, this might look like:
class Question < ActiveRecord::Base
validates_with :tag_validator
end
class TagValidator < ActiveModel::Validator
def validate(question)
unless question.special?
question.errors[:tag] << 'Not special enough.'
end
end
end
Working with error messages in rails is done in 3 steps viz.
Validating the active record object using rails-validation-helpers OR creating a custom validator as specified by #chaleyc
Validating the ActiveRecord(AR) object using AR validation methods
Showing the errors on views, creating a view helper is the best practice notices and errors on rails3
Here's a nice rails-cast to get you started
I'm using FactoryGirl for my fixtures and am finding that it's not really producing useful validation errors.
I always get the message for activerecord.errors.models.messages.record_invalid.
Not sure what further details are needed to help diagnose this. This makes it an excruciatingly slow process to track each error down.
Example factory:
Factory.define :partner do |partner|
partner.sequence(:username){ |n| "amcconnon#{n}" }
partner.first_name "Bobby Joe"
partner.last_name "Smiley"
partner.sequence(:email){ |n| "bob{n}#partners.com" }
partner.phone_number "5557 5554"
partner.country_id 75
partner.password "password"
partner.password_confirmation "password"
end
Then Factory(:partner) => "ActiveRecord::RecordInvalid Exception: Looks like something went wrong with these changes"
The actual problem is of course the email sequence doesn't use n properly and there is a unique validation on email. But that's for illustrative purposes.
rails => 3.2.2
factory_girl 2.6.1
Any other deets needed to help diagnose this?
(Note: edited this just to add an easier to read factory)
EDIT:
As per bijan's comment: "What exactly am I trying to do."
Trying to run "rspec spec". I would like when I use a factory like Factory(:partner) in this case for the error message when that fails to contain the same error I would get from Partner.new({blah...}).valid? then looked at the validation failures.
I'm not sure if this is exactly the same problem you were having, but it's a problem I was having with validation error messages not being very useful and I thought it could be useful to others searching for this problem. Below is what I came up with. This could be modified to give different info too.
include FactoryGirl::Syntax::Methods
# Right after you include Factory Girl's syntax methods, do this:
def create_with_info(*args, &block)
create_without_info(*args, &block)
rescue => e
raise unless e.is_a? ActiveRecord::RecordInvalid
raise $!, "#{e.message} (Class #{e.record.class.name})", $!.backtrace
end
alias_method_chain :create, :info
Then, when you use create :model_name, it will always include the model name in the error message. We had some rather deep dependencies (yes, another problem), and had a validation error like, "name is invalid"... and name was an attribute on a few different models. With this method added, it saves significant debugging time.
I think the key point here is that when you're setting up a test you need to make sure that the code you're testing fails at the point that you set the expectation (i.e. when you say "should" in Rspec). The specific problem with testing validations is of course that the validation fails as soon as you try to save the ActiveRecord object; thus the test setup mustn't invoke save.
Specific suggesions:
1) IMO Factory definitions should contain the minimum information required to create a valid object and should change as little as possible. When you want to test validations you can override the specific attribute being tested when you instantiate the new test object.
2) When testing validations, use Factory.build. Factory.build creates the ActiveRecord instance with the specified attributes but doesn't try to save it; this means you get to hold off triggering the validation until you set the expectation in the test.
How about something like this?
it "should fail to validate a crap password" do
partner_with_crap_password = Factory.build(:partner, :password => "crap password")
partner_with_crap_password.should_not be_valid
end
I'm new to RoR/Gems, this is a basic question.
I created a gem, MyNameGem, in order to learn the process. It contains these methods:
def returnValidationString1
puts 'Validation String'
end
def returnValidationString2
puts 'ANother Validation String'
end
I included the gem in a simple rails app, everything seems to be working as expected.
I this to my model:
validates :name => MyNameGem.returnValidationString1
What I'm trying to create is a gem that I can use inside a validation routine. So, for example, I want to do this: validates :name => (call my gem method, return a string, and use that string as the validation requirement)
puts only prints to console.
if you want to return 'MyNameGem' write return 'MyNameGem' or simply 'MyNameGem because the last line gets returned automatically.
The function of puts is to put things to the console, so that's exactly what it will do. What your validates call is doing is kind of unusual though, and doesn't seem to make any sense. Your code evaluates to:
validates :name => (puts "MyNameGem")
That really doesn't mean anything. puts usually returns nil.
If you want to write a custom validation routine that's stored in a gem, that's a different question.
I am using Rails and mongoid to work with mongodb.
Usually in rails when working with Active:Record, you have access to the method .toggle! which simply allows you to invert the value of a boolean field in your db.
Unfortunately this method is not available for mongoDB:
user = User.first
user.toggle!(:admin)
NoMethodError: undefined method `toggle!' for #<User:0x00000100eee700>
This is unfortunate... and stupidly enough I don't see how to get around without some complicated code...
Any suggestion on how to achieve the same result concisely ?
Thanks,
Alex
ps: also one of the problems is that when I want to modify the field, it goes through validation again... and it's asking for the :password which I don't save in the db, so:
User.first.admin = !User.first.admin
won't even work :(
The issue here is specifically mongoid, not mongodb. toggle! is a part of ActiveRecord::Base, but fortunately it's not hard to replicate.
def toggle!(field)
send "#{field}=", !self.send("#{field}?")
save :validation => false
end
Add that into your model (or add it into a module, and include it in your model), and your Mongoid models will gain functionality equivalent to what you're used to in AR. It will read the field's value, invert it, write it (through the setter, per the toggle! documentation), and then save the document, bypassing validation.
# Get object's boolean field and toggle it
# #param [Object] mongoid object
# #param [String, Symbol] flag
# #example
# foo = User.find('123')
# toggle_flag!(object: foo, flag: :bar)
def toggle_flag!(object:, flag:)
object.update(flag => !object[flag])
object.save!
end
Ok the validation did not work because of a type, the code should be:
save :validate => false (not :validation)