I have a number of standard rails validations within my model:
validates_presence_of :url_string
validates_uniqueness_of :url_string
validates_presence_of :stream_source
validates_presence_of :width
validates_presence_of :height
validates_presence_of :name
validates_uniqueness_of :name
validates_presence_of :customer_name
validates_presence_of :iframe_background_color
If I don't fill out one of these fields within my form then I am taken back to the form as expected but the odd thing is no error messages are displayed. I am using the code below to display the error messages:
<% #camera.errors.full_messages.each do |error| %>
<p><%= error %></p>
<% end %
I also attempted to print out the #camera.errors object and this is what is shown:
#<ActiveModel::Errors:0x12db19bc #base=#<Camera id: 1, stream_source: "test", width: 640, height: 360, active: true, name: "test", url_string: "CAYD19Vp", customer_name: "test", iframe_background_color: "#FFFFFF", online: true, created_at: "2011-08-30 15:54:16", updated_at: "2011-09-06 15:52:48", audio: true, iframe_text_color: "#FF00FF", iframe_link_color: "#FF0000", notes: "Some notes!", offline_image_file_name: "Cake.jpg", offline_image_content_type: "image/jpeg", offline_image_file_size: 196591, offline_image_updated_at: "2011-09-06 12:12:38", pull_stream_url: "test", bitrate: "300-500", show_branding: false>, #messages={}>
#
As you can see the messages hash is empty. I tried setting the validation error message manually by doing the following:
validates_presence_of :name, :message => "No name present"
but it did not populate the messages hash either.
Controller update action is shown below:
def update
#camera = Camera.find(params[:id])
if #camera.update_attributes(params[:camera])
flash[:notice] = "Camera updated"
redirect_to nwcadmin_camera_path
else
redirect_to :action => :edit
end
end
I am using Ruby version ruby 1.9.2p290 and Rails version 3.1.0.
Any assistance would be great!
Thanks
Just a heads up that you'll get a Validation failed (ActiveRecord::RecordInvalid) error with an empty error message (if there are no other errors) when you have before_validation declarations and any of them returns false.
Note that before_validation callbacks must not return false (nil is okay) and this can happen by accident, e.g., if you are assigning false to a boolean attribute in the last line inside that callback method. Explicitly write return true in your callback methods to make this work (or just true at the end if your callback is a block (as noted here)).
UPDATE: This will no longer be an issue starting Rails 5.0, as return false will no longer halt the callback chain (throw :abort will now halt the callback chain).
UPDATE: You might also receive ActiveRecord::RecordNotSaved: Failed to save the record if a callback returns false.
I managed to get to the bottom of my problem. In the controller I was using:
redirect_to :action => :edit
I should have been using:
render :action => :edit
By using redirect_to I was hitting the edit action within the controller which was then getting a new camera object from the database rather than preserving the current camera object from the update action.
Unless you call #camera.save or #camera.valid?, the errors hash will not be populated with the validation errors. Please check your controller code.
You can use flash[:message] or flash[:notice] in controller code to store the error message, which can be used in view to display the errors.Link Have a look in the link,it's clearly explained, how to append the error messages and use them to display.The instance variable doestnot contains any errors as no validation runs in update.
You can use #camera_errors = #camera.save to collect the errors and then
<% #camera_errors.errors.full_messages.each do |error| %>
<p><%= error %></p>
<% end %>
I'm not sure if this is something that you might be interested in or not, but you can use this official Rails gem: dynamic_form
This gem provides you two helper methods: error_messages and error_messages_for
Refer to the following Rails guide for more: http://guides.rubyonrails.org/active_record_validations_callbacks.html#displaying-validation-errors-in-the-view
Related
I am having some trouble saving arrays in Rails.
Rails version: 4.2.3 | Ruby version: 2.2.1 | DB: PostgreSQL
In my view, I have a collection of check boxes that shows the conferences that my member attended.
<%= f.fields_for :conferences_member do |conference| %>
<%= collection_check_boxes(:conferences_member, :conference_id, #all_conferences, :id, :name)%>
<% end %>
I put a break point (binding.pry) after the create action in my MembersController, and surprisingly, it shows the selected check boxes:
Processing by Database::MembersController#create as HTML
Parameters: {"utf8"=>"✓","authenticity_token"=>"XYZ==",
[...] "conferences_member"=> {"conference_id"=>["3", "5", ""]}, [...]
Now, if I go to rails c, and type ConferencesMember.last to check what was saved, I get:
pry(main)> ConferencesMember.last
ConferencesMember Load (0.5ms) SELECT "conferences_members".* FROM
"conferences_members" ORDER BY "conferences_members"."id" DESC LIMIT 1
=> nil
These are my associations:
#=> member.rb
has_one :conferences_member
accepts_nested_attributes_for :conferences_member, allow_destroy: true, reject_if: :all_blank
#=> conferences_member.rb
serialize :conference_id, Array
belongs_to :member
#=> members_controller.rb
params.require(:member).permit( [...]
:conference_member_attributes => [:id, :member_id, :conference_id => []],
[...])
I want to thank you in advance. I've tried almost everything here on StackOverflow, but I don't see my error.
Thank you again.
EDIT:
More of my MembersController:
def new
#member = Member.new
#member.build_conferences_member
end
def create
#member = Member.new(member_params)
binding.pry
end
The log doesn't show any error, it just shows that conferences were not saved at all.
First, your field needs to be renamed to nest the :conference_id in :conferences_member_attributes (not in :conferences_member as you do now). Take advantage of the form object yielded by fields_for:
<%= f.fields_for :conferences_member do |conference| %>
<%= conference.collection_check_boxes :conference_id, #all_conferences, :id, :name %>
<% end %>
You also need to actually save the record in the create action: Member.new builds the record but does not save it. Typically, the create action branches based on whether the record saved or did not (due to validations). So you might rewrite this method like so:
def create
#member = Member.new(member_params)
# when #member.save returns true, it saved to the db successfully
if #member.save
redirect_to members_path, notice: "Member #{#member.id} saved!"
# otherwise, it didn't save because of a validation error, so we render the error
# to the user and give them a chance to fix it
else
flash[:error] = "Member didn't save: #{#member.errors.full_messages.to_sentence}"
render :new
end
end
Lastly, to make sure your data gets through your strong parameters, check your logs for any messages that parameters were filtered out. The messages look like:
Unpermitted parameters: your_favorite_attribute
I'm having a lot of trouble getting a simple true/false to evaluate properly in Rails. I have this in a helper:
def should_we_ask_to_subscribe?
if #user.email.blank?
true
elsif #user.subscriber == false
true
else
false
end
end
I test for blank because the user object is created by omniauth and sometimes, as with Twitter, I don't get an email. After the authorization is created, the user is directed to update their profile and add an email. If there's no email, I make sure to present the option to subscribe because they can't be subscribed. This is the view:
<% if should_we_ask_to_subscribe? %>
<div class="field">
<%= f.check_box :subscriber %>
<%= f.label :subscriber, "It's okay to send me the occassional email. I know I can unsubscribe at any time." %>
</div>
<% else %>
<div class="field">
You're subscribed to our email list.<br />
<%= f.check_box :unsubscribe %>
<%= f.label :unsubscribe, "Please unsubscribe me." %>
</div>
<% end %>
However, even when the subscribe boolean is set to f -- say a user has entered an email, but not subscribed (or unsubscribed, which is the only way the boolean would be f) should_we_ask_to_subscribe? still evaluates to false. I've tried various permuations but am just not getting it.
Update
Okay, so it's clear that the database is storing the boolean value as false, but rails is interpreting it as true and I can't really get why. Following advice below with inspect, the log shows #user.subscriber = true even though in the console and in the database I get subscriber: false for that particular user.
Second Update
So I've written some unit tests and they all pass. In my logs, I notice the code is doing the following:
(0.4ms) UPDATE "users" SET "subscriber" = 'f', "updated_at" = '2011-07-05 13:40:58.178186' WHERE "users"."id" = 47
and this is the code, in a method in my model, that does that:
self.update_attribute(:subscriber, false)
When I look at the database, both through an SQLite view and the console, I see an f and u.subscriber => false, respectively. But, when I use logger.info "#user.subscriber = #{#user.subscriber.inspect}" to see how the view helper is interpreting it, I get: #user.subscriber = true in the log. So, I'm assuming the problem is with how I set false since it seems rails is interpreting any value in the subscriber column as true (except in the console, where it seems to be saying its false?).
This is a good candidate for a unit test that will exercise your model and determine if the correct behavior is implemented. For instance:
#user = User.create(...)
assert #user.email.blank?
assert !#user.subscriber?
assert_equal true, #user.should_we_ask_to_subscribe?
#user.email = 'test#example.email'
assert_equal false, #user.should_we_ask_to_subscribe?
#user.unsubscribe = true
assert_equal true, #user.should_we_ask_to_subscribe?
As a note, if you're dealing with variables that are interpreted as either true or false, you don't need to be so formal about comparing values to false specifically. A simple refactoring would look like this:
def should_we_ask_to_subscribe?
# Show subscription option if no email has been provided.
return true if #user.email.blank?
# Otherwise ask to subscribe if not a subscriber
!#user.subscriber?
end
Diagnose whether the value of subscriber is not nil (which is a false equivalent) or whether it is not any other value.
Put this line in the should_we_ask_to_subscribe function:
logger.info "#user.subscriber = #{#user.subscriber.inspect}"
Is your subscriber attribute declared as boolean? The checkbox would send a value of "0" (a string), which will evaluate to true in ruby, but usually is magically translated to 'false', but only for boolean attributes.
I am currious to see in what way the subscriber field information is saved in your database.
Could you raise #user.subscriber within your should_we_ask_to_subscribe? function please?
Add this at the top of your function :
raise #user.subscriber.inspect
So I've got a user model, with login, email address, password, password confirmation, name, avatar (picture), etc. There are validations on the first 5, basically stating that all 5 need to exist in order to create a new model.
However, this causes problems for me where updates are concerned.
I've got an edit page, where the user can only edit their name and avatar. I'm not currently intending to let them change their login, and I wish to do an email and password change from a different page.
So the edit form looks like this:
<% form_for #user, :html => { :multipart => true } do |u| %>
<p>
<label>Name:</label>
<%= u.text_field :name %>
</p>
<p>
<label>Avatar:</label>
<%= display_user_avatar %>
<%= u.file_field :avatar%>
</p>
<p>
<%= submit_tag %>
</p>
<% end %>
If I attempt to do a #user.update_attributes(params[:user]), then because the only 2 params are name and avatar, the update fails, since stuff like password, password confirmation, email, etc are required to validate the entry, and they simply don't exist in that form.
I can get around this by doing #user.update_attribute(:name, params[:user][:name]), but then I worry about whether avoiding validations is a Good Thing™ or not. Especially with regards to something like password updates, where I do need to validate the new password.
Is there another way?
And if I were to do this simply using update_attribute for :name and :avatar, how would I go about doing it?
Would this work?
params[:user].each do |attribute|
#user.update_attribute(attribute, params[:user][attribute])
end
Is this an acceptable way to do this...?
--edit as follow up --
Okie, I tried as you suggested and did
def update
#user = User.find_by_login(params[:id])
if #user.update_attributes!(params[:user])
redirect_to edit_user_path(#user)
else
flash[:notice] = #user.errors
redirect_to edit_user_path(#user)
end
end
So it's doing the ! version, and the exception caught & displayed in the browser is:
Validation failed: Password is too short (minimum is 5 characters)
The info in the server log is:
Processing UsersController#update (for 127.0.0.1 at 2010-07-18 11:56:59) [PUT]
Parameters: {"user"=>{"name"=>"testeeeeee"}, "commit"=>"Save changes", "action"=>"update", "_method"=>"put", "authenticity_token"=>"BMEGRW/pmIJVs1zlVH2TtZX2TQW8soeCXmMx4kquzMA=", "id"=>"tester", "controller"=>"users"}
Urm. Looking at this, I just realised that it is submitting "id"=>"tester". Now, I have my routes set up so that it is showing the users login name, instead of the user_id... Could that be why? It is attempting to find a update a user with user_id == tester, but since it doesn't exist, it attempts to create one instead?
Is it actually something I'm doing wrong due to the route?
Hmmm... rake routes tells me that the route is:
edit_user GET /users/:id/edit(.:format) {:action=>"edit", :controller=>"users"}
PUT /users/:id(.:format) {:action=>"update", :controller=>"users"}
And I set up the route like that in the user.rb file:
def to_param
"#{login}"
end
but it's definitely been displaying login instead of id all this time. But I'm also doing right at the beginning of the update action, a #user = User.find_by_login(params[:id]), and then updating that #user.
I'm very confused. >.<
Second update:
My User.rb validation stuff are as follows:
validates_length_of :login, :within => 3..20
validates_length_of :password, :within => 5..20
validates_presence_of :login, :email, :password, :password_confirmation, :salt, :name, :on => :create
validates_uniqueness_of :login, :case_sensitive => false
validates_confirmation_of :password
validates_format_of :email, :with => /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :message => "format is invalid."
attr_accessor :password, :password_confirmation
And the hashed_password section is here:
def password=(pass)
#password = pass
self.salt = User.random_string(10) if !self.salt?
self.hashed_password = User.encrypt(#password, self.salt)
end
u.attributes gives me
>> u.attributes
=> {"salt"=>"NHpH5glxsU", "name"=>"test er", "avatar_updated_at"=>nil, "updated_at"=>Sat Jul 17 07:04:24 UTC 2010, "avatar_file_size"=>nil, "avatar_file_name"=>nil, "hashed_password"=>"84f8675c1ed43ef7f8645a375ea9f867c9a25c83", "id"=>1, "avatar_content_type"=>nil, "login"=>"tester", "email"=>"tester#tester.com", "created_at"=>Fri May 07 10:09:37 UTC 2010}
Urmmm... Ok, so it's what you said, about the virtual attribute password being actually nonexistent...
So how do I get around that?
Bugger, here I thought I was being smart fiddling with my own authentication code...
How easy is it to change to one of those authentication plugins? Will I need to create a new User model? Or should the plugin be able to work with my current one?
Thanks for all the help so far, btw! :D
I've checked this and a partial update of just 2 attributes via update_attributes works fine. All the other attributes are left with their previous values, meaning that the validation shouldn't fail. A couple of things to try:
In your controller action are you loading the user via User.find? i.e. are you starting from a valid model.
Are you sure the update is failing due to validation errors? Try replacing the update_attributes with update_attributes!. The latter will throw an exception if the update fails due to validation. Or check #user.errors after the attempted update to confirm which validation has failed.
Update
If User.find_by_login doesn't find a matching record it will return nil and won't create a new record for you. Is it possible that the tester user in the database has a password that is too short? Maybe that user was created before you put the validations in your code? Are you using any kind of plugin or callback to encrypt user passwords before saving the records? Is password actually a virtual attribute that isn't saved and the actual password is in a field like encrypted_password?
Try this from script/console (use the same environment as you are testing the app with - development or production)
> user = User.find_by_login 'tester'
> user.valid?
> user.attributes
The user.valid? will return true of false and will tell you whether the user is valid to start with, before you even try an update.
Update 2 (fixing the validation)
In terms of fixing your own code, you could add a method like the following to your User model:
def password_validation_required?
hashed_password.blank? || !#password.blank?
end
and then update all your password related validation rules so that they only apply if this method returns true e.g.
validates_length_of :password, :within => 5..20,
:if => :password_validation_required?
What this is saying is only do the password validation rule if we don't yet have a hashed_password (on a new user for example) or if a new plain text password has been specified via password=. If the user already has a password and it is being left unchanged then skip the password validation.
You are right to be considering using a plugin though. Writing your own authentication code can be an interesting excercise and can be required if you have some unusual requirements. The down side is that there can be security issues that you haven't thought of. Retrofitting something like restful_authentication to your app shouldn't be too bad. You might just need to rename one or two fields on your User model.
I'm trying to modify the vestal_versions plugin to accept a parameter I set upon saving. This parameter will act as a new flag to determine when to create a revision upon updating. Currently it will always run upon update when a new revision is needed. Here is the affected area of unmodified plugin code:
after_update :create_version, :if => :needs_version?
def create_version
versions.create(:changes => changes.slice(*versioned_columns), :number => (last_version + 1))
reset_version
end
The parameter I am sending in the view upon submit is "forcerevision=n". How would I pull in this parameter and what conditional would I use to allow this to only run when "forcerevision=y"? It seems it would be cleanest to modify the after_update filter?
Here is the log of the data being passed on update.
Processing NotesController#update (for 521.0.0.1 at 2009-12-05 13:25:45) [PUT]
Parameters: {"authenticity_token"=>"#########################+k=", "id"=>"4", "forcerevision"=>"n", "note"=>{"notebook_id"=>"", "public"=>"n", "body"=>"A versioned note", "title"=>"Version Note Test", "flag"=>"important", "star"=>"false", "tag_list"=>""}}
vestal_versions on Github
The cleanest way to do this would be to add an attr_accessor when a model is declared to be versioned.
Then override needs_version? to check that attribute.
Anywhere in LaserLemon::VestalVersions::ClassMethods#versioned add this line:
attr_accessor :force_reversion
Then rewrite LaserLemon::VestalVersions::InstanceMethods#needs_version? to check this attribute:
N.B. due to the way checkboxes are handled "0" is considered false for checkboxes, and boolean fields, but is a true value in ruby. So we can't just check if force_reversion does not evaluate to false.
def needs_version?
!(versioned_columns & changed).empty? ||
![nil, "0", 0, false].include?(force_reversion)
end
And you're all set. Just pass any value for force_reversion as if it were a column.
After the above changes with the following model:
class User
#user.update_attributes(:force_reversion => true, :unversioned_column => new_value)
Or in a form:
<%= form_for #user do |f| %>
<%= f.label :force_reversion, "Force New Version" %>
<%= f.check_box :force_reversion %>
... More fields ...
<% end %>
Say I have the following model:
class Information < ActiveRecord::Base
...
validates_length_of :name, :minimum=>3, :message=>"should be longer than 3 characters!"
...
What I want to have as an error is:
Information should be longer than 3 characters! (or similar)
and NOT "Information name should be longer than 3 characters!".
Two possible workarounds I've looked at:
human_attribute_name method (mentioned here): doesn't work with my Rails 2.3.2. :-(
directly do a information.errors.add "","..." if information.name.length < 3: however, this removes many useful properties triggered by the validated_length_of method like the special class-tags (for coloring the stuff red).
Any ideas? Thank you for your time.
I suppose that you display errors through full_messages method, which is meant for console, not for web application use. You should use error_message_on or error_messages_for helpers instead (see documentation for more info), which allow you to customize error messages.
Example:
<%= error_message_on "information", "name", :prepend_text => 'Information ' %>
don't use the rails helper to make the errors, normally i have inline errors so something like:
def inline_error_block(obj, meth, prepend="", append="", klass="error error-form", &block)
content = capture(&block)
obj = (obj.respond_to?(:errors) ? obj : instance_variable_get("##{obj}"))
if obj
errors = obj.errors.on(meth.to_s)
if errors
output = content_tag(:div, :class => klass) do
content_tag(:p, "#{prepend}#{errors.is_a?(Array) ? errors.first : errors}#{append}", :class => "error-msg clearfix") + content
end
return concat(output)
end
end
concat(content_tag(:div, content, :class => "no-error"))
end
tends to do the trick, but, it only shows one error per form field, am sure you could rearrange it to show them all, should you need to! (errors.first to errors.each).
To get the full name, just write the message with the field name as you want it displayed:
validates_length_of :name, :minimum=>3, :message=>"Information should be longer than 3 characters!"
You could always set your :message to an empty string in the model then set the :prepend_text in the view to whatever you like.