Why does delete not work on full_messages function? - ruby-on-rails

I have the following code:
def register_learner
#event = Event.find(params[:event_id])
#registation = EventRegistration.new first_name: params[:first_name], last_name: params[:last_name], email: params[:email], event_id: params[:event_id]
if !#registation.valid?
#registation.errors.full_messages.delete("Event has already been taken"
flash[:notice] = #registation.errors.full_messages.to_sentence
redirect_to(event_path(#event))
else
#registation.save
end
end
Note the line #registation.errors.full_messages.delete("Event has already been taken") where I am trying to delete this particular message from the full_messages array, however it does not work. The next line is the flash message, and the message "Event has already been taken" is still being displayed.
Here is a sanity check via the console...
2.1.5 :001 > errors = ["Event has already been taken", "Last name can't be blank"]
=> ["Event has already been taken", "Last name can't be blank"]
2.1.5 :002 > errors.delete "Event has already been taken"
=> "Event has already been taken"
2.1.5 :003 > errors
=> ["Last name can't be blank"]
What am I missing?

This is because full_messages is a method, which gerneates a new array every time you call it. To do what you want:
errors = #registation.errors.full_messages
errors.delete("Event has already been taken")
flash[:notice] = errors.to_sentence
That answers the question, now there is a matter of - why do you need to do this? There might be a better way.
In general relying on strings is usually a bad idea, imagine that in a half a year you will need to change an error message for this validation. Can you be 100% sure you will remember to change it here as well? If not, you have a bug.

Well, because Rails re-creates #full_messages using errors every time you call the method:
def full_messages(options = {})
#errors.values.inject([]) do |full_messages, errors|
full_messages + errors.map { |error| error.full_message }
end
end
Source
You can use #select to skip that message:
#registation.errors.full_messages.select{|x| x != "Event has already been taken"}.to_sentence
Also, you can use #delete on #errors.messages (not full_messages), because they are exposed attribute of the underlying object (not a copy, as with full_messages):
#registration.errors.messages[:event].delete('has already been taken')

Related

Ruby why is my object saving without an ID?

I feel like I'm missing something very basic, and someone can look at this and very quickly telling me what that is...
def create
#user = User.find_by_id(create_params[:user_id])
#plan = #user.plans.new(create_params)
if #plan.save
puts #plan.inspect
end
end
Right now the puts statement returns this:
#<Plan id: nil, zipcode: "02114", selected_plan: 5, user_id: 1>
So basically.. all the attributes are there, but there's no id...
More info:
create_params = {zipcode:"02114", selected_plan:"5", user_id: 1}
And if it's helpful to know, save! does work and successfully saves the plan with an id... and plan.save == true returns true
Your code should have the following pattern.
#user = User.find(create_params[:user_id]) # see changes
#plan = #user.plans.new(create_params)
if #plan.save
puts #plan.reload.inspect #reload object to see if the object has been saved!
else
puts #plan.errors.messages.inspect # if object is not saved there should be Object#errors.messages should have messages
end

Flash[:error] add more than one at the same time

I use 'nokogiri' among others to check the schema from some uploaded xml's. And i will print out all errors which occurs:
xsd.validate(doc).each do |error|
flash[:error] = error.message
end
If I do so, I see only the last added error, if more than one exists.
I find also find a similar question, about this problem rails-easy-way-to-add-more-than-one-flashnotice-at-a-time but the accepted solution dosen't work for me.
Thanks
change the method to
flash[:error] = xsd.validate(doc).map(&:message).to_sentence
UPDATE
Using br tags to separate each error
flash[:error] = xsd.validate(doc).map(&:message).join('<br>').html_safe
I find also find a similar question, about this problem
rails-easy-way-to-add-more-than-one-flashnotice-at-a-time but the
accepted solution dosen't work for me.
In what way doesn't it work for you?
You can add your own flash types like flash[:errors] and write your own helper methods for convenience.
def my_flash(type, message)
flash[type] ||= []
flash[type] += Array.wrap(message)
end
Then you can use an array or a string as the message, making it easy to pass multiple in, like so.
my_flash :errors, "name cannot be blank"
my_flash :errors, ["age must be greater than 17", "phone number is invalid"]
p flash[:errors]
#=> ["name cannot be blank", "age must be greater than 17", "phone number is invalid"]

Strong parameters in test issue

Ruby 2.1.1p76 on Rails 4.1.1.
Please check out my controller:
def update
begin
current_user.update_settings user_settings_params unless params[:user_setting].blank?
current_user.update_attribute :district_id, params[:user][:district_id] unless params[:user].blank? || params[:user][:district_id].blank?
flash[:success] = "Preferencje zostały zaktualizowane"
redirect_to subscription_index_path
rescue UserLevelException => exception
flash[:alert] = "Sprytnie, za karę zostałeś wylogowany ;)"
session[:user_id] = nil
redirect_to root_path
return
end
end
private
def user_settings_params
params.require(:user_setting).permit(
:inquiry_subject, :inquiry_body,
:offer_subject, :offer_body,
:only_companies_with_email,
{:district_ids => []},
# {:district_ids => params[:user_setting][:district_ids].try(:keys)},
:delivery_address,
)
end
See the commented line? In the form above - user_settings_params will not return :district_ids array of ids, and this is fine, since I can use the line below instead to have them (got it from guides).
The problem I have is when running this test:
test 'should set user level10 districts' do
user = login_user :paid10
post :update, :user_setting => {:district_ids => [districts(:zachodniopomorskie).id, districts(:slaskie).id]}
assert_equal nil, flash[:alert]
assert_equal 'Preferencje zostały zaktualizowane', flash[:success]
db_user_districts = User.find(user.id).settings.districts.all
assert db_user_districts.include? districts(:zachodniopomorskie)
assert db_user_districts.include? districts(:slaskie)
assert_equal 2, db_user_districts.count
end
It passes. When debugging user_settings_param has :district_ids available as if strong parameters were disabled or something. I wanted to submit an issue to rails but most probably I'm doing something wrong and can't figure it out.
I've found it - it was because of quirky way I was creating checkboxes for HABTM
= check_box_tag "user_setting[district_ids][#{district.id}]", district.id, user.settings.district_ids.include?(district.id)
= label_tag "user_setting[district_ids][#{district.id}]", district.name
For no particular reason I've inserted ids into params keys AND values. And because of that those were passed to params object as hash. In test though those were sent as array. So it was the view to blame.
= check_box_tag "user_setting[district_ids][]", district.id, user.settings.district_ids.include?(district.id)
= label_tag "user_setting[district_ids][]", district.name

Rails - why does one work and not the other?

I have a seeds.rb file and for some reason, this doesn't work:
#doesn't work
u=User.new
u['email']=h['email']
u['password']=h['password']
puts u['email']
puts u['password']
if u.save
puts "that saved"
else
puts "that did not save"
end
but this does:
#does work
User.create({:email => h['email'], :password => h['password']})
Is there any reason one works and one doesn't? From rails console, the first does work? Would there be any differences in validations? I run 'rake db:seed' so would think validations would be in effect in both.
thx
edit #1
sorry for lack of info. each one errors with "password can't be blank" but it then echoes out the password. Hmm...
Find out what's happening by looking at the validation errors:
else
puts "that did not save because " + u.errors.join(' ')
end
Edit: Now that you've added that validation errors contain 'password cant be blank' and it still fails to save, it becomes clear that User.create() does a number of things which User.save() omits.
What's missing is the generation of your password hash - hence the blank password error.
Try it this way:
u = User.new
u.email = h['email']
u.password = h['password']
puts u.inspect
if u.save!
puts "that saved"
else
puts "that did not save"
end
I believe the approach of accessing the attribute on the u object as u['password'] is steering you wrong.
Also, doing a u.inspect prints the entire state of the model and that should provide a cleaner way of seeing what's happening. Finally, adding a bang ! at the end of the save will cause it to 'fail fast' and give you the error immediately.

Rails Unit Testing won't update database

I'm trying to run the following unit-test:
def test_passwordchange
# check success
assert_equal #longbob, Usuario.autenticar("longbob", "longtest")
#change password
#longbob.password = "nonbobpasswd"
#longbob.password_confirmation = "nonbobpasswd"
assert #longbob.save!
#new password works
assert_equal #longbob, Usuario.autenticar("longbob", "nonbobpasswd")
#old pasword doesn't work anymore
assert_nil Usuario.autenticar("longbob", "longtest")
#change back again
#longbob.password = "longtest"
#longbob.password_confirmation = "longtest"
assert #longbob.save!
assert_equal #longbob, Usuario.autenticar("longbob", "longtest")
assert_nil Usuario.autenticar("longbob", "nonbobpasswd")
end
However, it throws error on the 1st line that contains "assert_equal" that says:
<#<Usuario ID: 1000003, login: "longbob", hashed_password: "078cf6ae2de80ed6c004c8c8576a5572e077a52c", salt: "1000", nombre: nil, apellido: nil, email: "lbob#mcbob.com", telefono: nil, tipo_usuario: nil, foto: nil, bol_activo: nil>> expected but was <nil>.
Here's my authenticate method:
def self.authenticate (login, pass)
u=find(:first, :conditions=>["login = ?", login])
return nil if u.nil?
return u if Usuario.encrypt(pass, u.salt)==u.hashed_password
nil
end
Also, I defined the following:
def password=(pass)
#password=pass
self.salt = Usuario.random_string(10) if !self.salt?
self.hashed_password = Usuario.encrypt(#password, self.salt)
end
So, I guess that should update the hashed_password every time I reassigned something to "password"... right?
Whats happening?
Thx.
UPDATE: I noticed that if I change:
assert_equal #longbob,
Usuario.autenticar("longbob",
"nonbobpasswd")
to
assert_equal #longbob2,
Usuario.autenticar("longbob",
"nonbobpasswd")
It passes that test, however it fails in the following line... Trowing the same error... What's up with that?
To answer after my comment, we find out that the problem is the call to save does not update the record in the database. I have suggested you to write another test to see this behaviour and be able to fix it, the test you have writed start to be too long and in fact the bug does not have anything related to the authenticate mechanism.
Here is the test I would write :
def test_change_password_save
old_hash = #longbob.hashed_password
#longbob.password = "nonbobpasswd"
#longbob.password_confirmation = "nonbobpasswd"
#longbob.save
assert_not_equal(old_hash, #longbox.reload.hashed_password)
end
If this test failed, I would suggest you to write another question in stackoverflow for this bug.
You're probably getting a validation error. Check the contents of #longbob.errors.
What happens if you split this into two separate statements? (Which is good practice anyway)
#longbob.password = #longbob.password_confirmation = "nonbobpasswd"
See, #password_confirmation= is actually a method, which might not return the value that was passed to it, depending on how the method was implemented.
Is it possible that changing the password variables doesn't update the hashed_password field in the database.
You probably need something like this in Usuario:
before_save :rehash
def rehash
self.hashed_password = ??? #replace ??? with whatever your hashing logic is
end

Resources