Rails .html.erb - if, else statement not working - ruby-on-rails

I am trying to show something if a user has 'messages'
views/messages/index.html.erb
<% if #user.messages.any? %>
You have messages!
<% else %>
Sorry, you have no messages
<% end %>
controllers/messages_controller.rb
class MessagesController < ApplicationController
def index
#user = current_user
#messages = #user.messages
end
When I have this code implemented even though the user has no messages (as confirmed by rails console :
user = User.find(1)
user.messages.any?
=> false
The message 'You have messages!' still appears
Also thought to add that the authentication is using the devise gem

<% if #user.messages.any? %>
<%= "User with id #{#user.id} has messages" %>
<% else %>
<%= "User with id #{#user.id} doesnt have messages" %>
<% end %>
This will show something like
User with id 5 has messages
or
User with id 6 doesnt have messages
Now verify this from rails console
User.find(5).messages.any?

try
<% if #user.present? %>
<%= puts "User is selected" %>
<% if #user.messages.present? %>
<%= puts "User have message" %>
<% end %>
<% end %>

Related

Rails Searching in database

I want a searchfunction that can search strings in Database.
What I tried is:
def search_user
#users_search = User.where("lastname LIKE ?", "#{#search}%")
redirect_to :back
end
For example if I type H it should show all Users with a beginning H
View:
<% if #users_search.present? %>
sdf
<% for user in #users_search %>
<%= user.lastname %>
<% end %>
<% end %>

How to display error messages in a multi-model form with transaction?

Two models, Organization and User, have a 1:many relationship. I have a combined signup form where an organization plus a user for that organization get signed up.
The problem I'm experiencing is: When submitting invalid information for the user, it renders the form again, as it should, but the error messages (such as "username can't be blank") for the user are not displayed. The form does work when valid information is submitted and it does display error messages for organization, just not for user.
How should I adjust the code below so that also the error messages for user get displayed?
def new
#organization = Organization.new
#user = #organization.users.build
end
def create
#organization = Organization.new(new_params.except(:users_attributes)) #Validations require the organization to be saved before user, since user requires an organization_id. That's why users_attributs are above excluded and why below it's managed in a transaction that rollbacks if either organization or user is invalid. This works as desired.
#organization.transaction do
if #organization.valid?
#organization.save
begin
# I executed next line in debugger (with invalid user info), which correctly responds with: ActiveRecord::RecordInvalid Exception: Validation failed: Email can't be blank, Email is invalid, Username can't be blank, etc.
#organization.users.create!(users_attributes)
rescue
# Should I perhaps add some line here that adds the users errors to the memory?
raise ActiveRecord::Rollback
end
end
end
if #organization.persisted?
flash[:success] = "Yeah!"
redirect_to root_url
else
#user = #organization.users.build(users_attributes) # Otherwise the filled in information for user is gone (fields for user are then empty)
render :new
end
end
The form view includes:
<%= form_for #organization, url: next_url do |f| %>
<%= render partial: 'shared/error_messages', locals: { object: f.object, nested_models: f.object.users } %>
<%= f.text_field :name %>
# Other fields
<%= f.fields_for :users do |p| %>
<%= p.email_field :email %>
# Other fields
<% end %>
<%= f.submit "Submit" %>
<% end %>
The error messages partial is as follows:
<% object.errors.full_messages.each do |msg| %>
<li><%= msg.html_safe %></li>
<% end %>
Update: Following the steps from Rob's answer I arrived at the errors partial below. This still does not display error messages for User. I added debugger responses inside the code below and for some reason nested_model.errors.any? returns false, while the debugger inside the controller (see above) does return error messages for user.
<% if object.errors.any? %>
<div id="error_explanation">
<div class="alert alert-danger">
The form contains <%= pluralize(object.errors.count, "error") %>.
</div>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg.html_safe %></li>
<% end %>
</ul>
</div>
<% end %>
<% if defined?(nested_models) && nested_models.any? %>
# Debugger: responds with "local-variable" for "defined?(nested_models)" and for "nested_models.any?" returns true.
<div id="error_explanation">
<ul>
<% nested_models.each do |nested_model| %>
# Debugger: "nested_model" has the same values as "nested_models.any?", as you would expect. But for "nested_model.errors.any?" it returns false, which it shouldn't.
<% if nested_model.errors.any? %> #Initially had "unless nested_model.valid?" but then errors for User are immediately displayed on loading the form page (new method).
<ul>
<% nested_model.errors.full_messages.each do |msg| %>
<li><%= msg.html_safe %></li>
<% end %>
</ul>
<% end %>
<% end %>
</ul>
</div>
<% end %>
Try adding validates_associated :users under your has_many :users association in Organization.
http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates_associated
Did you code successfully create a person during the rescue block?
rescue ActiveRecord::RecordInvalid => exception
# do something with exception here
raise ActiveRecord::Rollback
#organization.users.build if #organization.users.blank?
render :new and return
This code looks like it will create a new empty User regardless of incorrect validations. And render new will simply return no errors because the user was successfully created, assuming Organization has no Users.
The control flow of this method has a few outcomes, definitely needs to be broken down some more. I would use byebug and walk through the block with an incorrect Organization, then incorrect name. Then an empty Organization with incorrect User attributes.
organization has_many :users and user belongs_to :organization
organization.rb
accepts_nested_attributes_for :users
new.html.erb
<%= form_for #organization, url: next_url do |f| %>
<%= render 'shared/error_messages', object: #organization %>
<%= f.text_field :name %>
# Other fields
<%= f.fields_for(:users,#organization.users.build) do |p| %>
<%= p.email_field :email %>
# Other fields
<% end %>
<%= f.submit "Submit" %>
<% end %>
In controller
def create
#organization = Organization.new(new_params)
if #organization.save
flash[:success] = "Yeah!"
redirect_to root_url
else
render :new
end
end
This is very related to this question. The key is that <%= render 'shared/error_messages', object: f.object %> is, I assume, only calling the .errors method on the object it is passed (in this case, organization).
However, because the user errors reside with the user object, they won't be returned and therefore will not be displayed. This requires simply changing the view logic to also display the results of .errors on the various user models. How you want to do so is up to you. In the linked thread, the accepted answer had the error message display code inline instead of in a partial, so you could do it that way, but it would be somewhat redundant.
I would modify my shared/error_messages.html.erb file to check for another passed local called something like nested_models. Then it would use that to search the associated models and include the errors on that. We just would need to check whether it is defined first so that your other views that don't have a nested model won't cause it to raise an error.
shared/error_messages.html.erb
<% if object.errors.any? %>
<div class="error-messages">
Object Errors:
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<% if defined?(nested_models) && nested_models.any? %>
Nested Model(s) Errors:
<ul>
<% nested_models.each do |nested_model| %>
<% unless nested_model.valid? %>
<li>
<ul>
<% nested_model.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</li>
<% end %>
<% end %>
</ul>
<% end %>
</div>
<% end %>
Then you would just need to change a single line in your view:
<%= render partial: 'shared/error_messages', locals: { object: #organization, nested_models: #organization.users } %>
Looks like you have a lot of untestable logic in your controller. Looks like for you logic will be better to use simple FormObject pattern.
https://robots.thoughtbot.com/activemodel-form-objects

Form_for - redirect with errors

I use form_for(#user) in a view settings#info :
# ./views/settings/info.html.erb
<%= form_for(#user) do |f| %>
<% if #user.errors.any? %>
<% #user.errors.full_messages.each do |msg| %>
<li>* <%= msg %></li>
<% end %>
<% end %>
...
<% end %>
# ./controllers/users_controller.rb
def update
#user = User.find(current_user.id)
if #user.update_attributes(params["user"])
....
else
render template: 'settings/info'
end
end
Everything is working great, errors are shown if there are some. But, because I pass some variables in info action of settings_controllers.rb, I need to change it to a redirect (with errors). How can I do that ?
Thanks in advance
in the case of a redirect i usually use the rails flash object and add the errors directly.
flash[:alert] = #user.errors.full_messages.join(' ')

Rails validating with two models

Ok, bit confused on how to solve this issue.
I have one form and two models. Here is my form:
<% if #booking.errors.any? %>
<% #booking.errors.full_messages.each do |msg| %>
<p class="error"><%= msg %></p>
<% end %>
<% end %>
<% if #guest.errors.any? %>
<% #guest.errors.full_messages.each do |msg| %>
<p class="error"><%= msg %></p>
<% end %>
<% end %>
<%= form_for :booking, url: bookings_path do |f| %>
<%= label_tag :email, "Guest's Email Address" %>
<%= text_field_tag :email %>
<%= f.label :nights, "Nights" %>
<%= f.text_field :nights %>
<%= f.label :nights, "People" %>
<%= f.text_field :people %>
<%= f.label :nights, "Arrival Date" %>
<%= f.text_field :arrival %>
<% end %>
As you can see, the email field isn't part of the form builder. The email address will be used to create a new Guest record if the email doesn't already exist. Once I have the ID of the guest then the booking record can be made also.
Here is the code for create action in my BookingController - where the form is submitted to...
...
def create
accommodation = current_user.accommodation
guest = Guest.find_or_create_by(:email => params[:email])
#booking = accommodation.bookings.new(post_params.merge(:guest_id => guest.id))
if #booking.save
flash[:success] = 'The booking has been added successfully.'
redirect_to :controller => 'bookings', :action => 'index'
else
render 'new'
end
end
...
I do realise this question isn't new but I can't find a good solution anywhere to my problem - I want to be able to set the form up properly (if necessary) and validate all fields using the two models. Then I need to display the error messages. At the moment, my email is ignored during validation and I'm not sure what to do next.
Any help much appreciated.
It seems to me that the easiest way to is to validate the email in the controller itself and add any validation error to the booking variable. Something like this:
def create
accommodation = current_user.accommodation
guest = Guest.find_or_create_by(:email => params[:email])
#booking = accommodation.bookings.new(post_params.merge(:guest_id => guest.id))
if #booking.save
flash[:success] = 'The booking has been added successfully.'
redirect_to :controller => 'bookings', :action => 'index'
else
<% if params[:email].blank> %>
#booking.errors.add(:email, "can't be blank.")
<% end %>
#You can do the same thing for whatever other validation errors you have
render 'new'
end
end
Note: I did not test the code
This is probably not the best way possible but it gets the job done and is easy. You could use accept_nested_attributes_for but it seems to me a little bit unnecessary considering that you are only validating an email. Nevertheless, if you want to do it the cleanest way, stick with accept_nested_attributes_for.
EDIT
Actually, your code is the right track. You just made a syntax error. The real reason your guests errors are not being shown is that you used a local variable instead of a instance variable. Try this:
#guest = Guest.find_or_create_by(:email => params[:email])
Your error messages should be displayed with the code you already have
<% if #guest.errors.any? %>
<% #guest.errors.full_messages.each do |msg| %>
<p class="error"><%= msg %></p>
<% end %>
<% end %>
EDIT 2
In order to avoid a booking instance from beings saved in case the guest email is invalid you can do something like this:
if !#guest.errors.any? && #booking.save
flash[:success] = 'The booking has been added successfully.'
redirect_to :controller => 'bookings', :action => 'index'
else
Therefore, if the guest has any errors, the if statement will terminate before the #booking.save statement is executed.
You can try to do a transaction. If one of them is invalid, rails will do a rollback and you can render the errors.
Follow this question code, see if it helps.

Rails - error messages can't be displayed

When I send wrong email, validation can't pass but error messages in views don't be displayed :(
I have in models:
validate :recipient_not_have_invitation, :notice => "That user have already invitation"
def recipient_not_have_invitation
errors.add :notice, 'That user have already invitation' if InvitationToGroup.find_by_recipient_email_and_group_id(recipient_email, group_id)
end
in controller:
(...)
if #invitation_to_group.save
Mailer.invitation_to_group(#invitation_to_group).deliver
redirect_to root_url, :notice => "Successfully send invitation to user #{#invitation_to_group.recipient_email}"
else
redirect_to new_invitation_to_group_path(:group_id => #invitation_to_group.group_id)
end
In views (invitation_to_groups/new.html.erb)
<h2>New Invitation to group </h2>
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<%= form_for #invitation_to_group do |f| %>
<% if #invitation_to_group.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#invitation_to_group.errors.count, "error") %> prohibited this user from being invitation:</h2>
<ul>
<% #invitation_to_group.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
(...)
The problem is that you are using redirect_to after else in your controller. So you are going to new action and in this action you have InvitationToGroup.new. So you build new object without errors ;) You need to use render method instesd.
PS. you should really consider using "formtastic". It will imporve your code in views. Please watch this two railscasts: http://railscasts.com/episodes/184-formtastic-part-1 and http://railscasts.com/episodes/185-formtastic-part-2

Resources