when #user is blank, how to make this if statement work? - ruby-on-rails

I have a header partial linked to my application.html.erb that looks like this:
<header class="unselectable">
<h2 class="float_left">
<% if #user.try(:errors).present? %>
<%= render 'shared/error_messages' %>
<% else %>
<%= #title %>
<% end %>
</h2>
<nav class="round">
<ul>
<% if logged_in? %>
<li><%= link_to "Home", current_user %></li>
<li><%= link_to "Settings", edit_user_path %></li>
<li><%= link_to "Log out", logout_path %></li>
<% else %>
<li><%= link_to "Log in", login_path %></li>
<% end %>
</ul>
</nav>
</header>
This is all well and good unless the page that loads doesn't have an #user variable (such as an about or logout page) in which case i get this:
undefined method `errors' for nil:NilClass
How can I make this work? I tried changing the logic to render the title unless #user.errors.any?but that didn't work either. I'm sure this is a simple fix but I can't figure it out!
EDIT added the fixes suggested (updated in the header partial above) and now get this error:
No route matches {:action=>"edit", :controller=>"users"} which seems to be coming from the edit_user_path

You can use the method .try(:something):
<% if #user.try(:errors).present? %>
<%= render 'shared/error_messages' %>
<% else %>
<%= #title %>
<% end %>
If #user is nil, the .try(:errors) will not raise an error.
The .present? method works for nil too:
.
>> nil.present?
#=> false
>> false.present?
#=> false
>> [].present?
#=> false
>> ''.present?
#=> false
>> 'bonjour'.present?
#=> true
>> ['bonjour'].present?
#=> true
.present? is a combination of .nil? AND .empty?
.present? is actually the opposite result of .blank?

I highly question the need for #user in your partial which is rendered in your application layout, hence its need in every page of your application. I argue that this is not good design at all because now you're relying on a global variable in all views of your application.
I think what you really mean to use is the flash. In which case you want something like this in application.html.erb.
<% flash.each do |key, value| %>
<%= content_tag :div, value, class: key %>
<% end %>
This should be set in the appropriate controller action before it's view is rendered so that the error message displys according to the request that was just made.
If your error messages come from your models, then this should be part of what actually generates these error messages. Typically this is a call to either create or update actions in the controller. In which case you should have the error_messages partial rendered with the form when your validations do not pass and the form is rendered again with the model object.
<%= form_for #user do |f| %>
<%= render 'shared/error_messages', :object => f.object %>
<!-- and so on -->
<% end %>
This way you can be confident that the #user object is always available for the partial to render without any errors since we're explicitly passing the object to the partial itself, and the partial is being rendered with the correct context. Using #users in your partial itself is the equivalent of using a global variable, hence the entire application relying on that global variable to exist.
The #user object is now accessed with a local variable in the partial as object (or whatever your decide to end up naming it).
<% object.errors.full_messages.each do |message| %>
<li>* <%= message %></li>
<% end %>

You can reformulate to like this:
<header>
<h2 class="float_left">
<% if #user.try(:errors).try(:any?) %>
<%= render 'shared/error_messages' %>
<% else %>
<%= #title %>
<% end %>
</h2>
...
</header>
Or add errors_any? to model:
class User
def errors_any?
self.try(:errors).try(:any?)
end
end
And to this:
<header>
<h2 class="float_left">
<% if #user.try(:errors_any?) %>
<%= render 'shared/error_messages' %>
<% else %>
<%= #title %>
<% end %>
</h2>
...
</header>

Related

RoR Rnder a partial cant find method

I have a classes User and Company, I want to re-use the users partial as the to render company staff.
In my CompaniesController I have:
def staff
#company=Company.find(params[:id])
#users=#company.works_fors.paginate(page: params[:page], :per_page => 10)
#title=#company.name+" staff."
end
And in my staff.html.erb template I have:
<% if #users.any? %>
<ul class="users follow">
<%= render #users %>
</ul>
<%= will_paginate %>
<% end %>
This is the works_fors/_works_for partial:
<%= render :partial => 'user' %>
Which Renders
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
<% if current_user.developer? && !current_user?(user) %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</li>
However this throws an error on the user object as it cant find the method
undefined local variable or method `user' for~~
I think this is because Im calling the user object from within companies but there is a defined relationship, or do I need to redefine in companies ?
It's hard to tell, but it appears that what you call #users in your controller is in fact not a User collection, but a WorkFor collection.
#users = #company.works_fors...
What you mean is:
#works_fors = #company.works_fors...
This means that staff.html.erb is working with a works_for collection. So you should rename the variable in your template to avoid confusion.
# staff.html.erb
<% if #works_fors.any? %>
<ul class="users follow">
<%= render #works_fors %>
</ul>
<%= will_paginate #works_fors %>
<% end %>
Now we know we are rendering a works_for partial. So an instance of works_for is be available inside the partial. We need to ask it for its associated user instance, and pass it to the render method.
# works_fors/_works_for.html.erb
<%= render works_for.user %>
As a bonus, you can save yourself some queries by preloading the users.
#works_fors = #company.works_fors.includes(:user)...

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

How to pass variables to partials?

I have a common problem that normally I would solve with locals, but this time wont work.
I have the following block:
<% #user.followers.each do |follower| %>
<%= render "follower_card" %>
<% end %>
And the following partial:
<div class="row follower-card">
<%= image_tag follower.avatar_url(:smallthumb), class: "col-4 inline avatar_medium circle" %>
<ul class="col-8 inline">
<li><%= follower.name %></li>
<li><%= follower.location %></li>
<li class="lightgray small inline"><span class="bold"><%= follower.photos.count %></span> Spots</li> -
<li class="lightgray small inline"><span class="bold"><%= follower.albums.count %></span> Spotbooks</li>
</ul>
</div>
I'm getting the following error:
undefined local variable or method `follower' for #<#<Class:0x007fe791a4c8d0>:0x007fe799b14b98>
This should work (specifying the follower variable):
<%= render "follower_card", follower: follower %>
Anyway, I recommend you to use collection rendering for performance reasons. Take a look here: http://guides.rubyonrails.org/layouts_and_rendering.html. Should be something like:
<%= render "follower_card", collection: #user.followers %>
Related question: Render partial :collection => #array specify variable name
Note
Old syntax to pass variables to partials is also valid (verbose, but less elegant IMHO):
<%= render partial: "follower_card", locals: { follower: follower } %>
This is because you are not passing the variable to the partial. The scope of the partial is limited to itself, and you are not making follower available inside. You will have to use:
<% #user.followers.each do |follower| %>
<%= render "follower_card", locals: {follower: follower} %>
<% end %>
Is the proper way.
<%= render "follower_card", follower: follower %>
or
<%= render partial: "follower_card", locals: {follower: follower} %>

How do I get this case statement to work?

In my view, I am doing this:
<% case #post
when #post.has_children? %>
<% #post.children.each do |child| %>
<li><%= link_to child.title, post_path(child)%></li>
<% end %>
<% when #post.has_siblings? %>
<% #post.siblings.where.not(id: #post.id).each do |sibling| %>
<li><%= link_to sibling.title, post_path(sibling)%></li>
<% end %>
<% when !#post.parent.nil? %>
<li><%= link_to #post.parent.title, post_path(#post.parent) %></li>
<% else %>
</ul>
</p>
<p>
There are no related posts.
</p>
<% end %>
Basically what I want to do is I want to check #post for a variety of conditions. If it has_children?, if it has_siblings?, etc.
I don't want the statement to exit if any of the above is true or false.
Once the view loads, it should automatically check for all of these statements. If it finds any of the above true, it should execute the command right below the check.
The issue is when I do this, it always defaults to the else. i.e. the case statement doesn't work.
I know I could simply just do a bunch of disjointed if statements, but then the HTML around it gets a bit weird.
Is there a way I can do this with a CASE statement?
Edit 1
The reason the if statement doesn't work properly, is if I have 3 if statements back to back - none of which that interact with each other (that's the only way to cycle through all of the conditions properly), is that the else doesn't trigger properly.
E.g. if the first two conditions are true, but the third is not...it will print out "there are no related posts"...when that's not the case. It is the case that there are no parent posts.
Basically I just want to have a catch-all related posts, so I am simply iterating through all of the various options and checking to see if those relations exist. If they do, I am pulling them out and if they don't then they move on. If none exist, then I don't print "there are no related posts".
The fact that the view is already looking looking complex is a sign that it may be a good idea to refactor the logic out of the view and place it into the Post model where it belongs. Ideally the view(s) should end up looking like this:
<%# posts/show.html.erb %>
<% if #post.has_related_posts? %>
<%= render partial: 'children', collection: #post.children, as: :child %>
<%= render partial: 'siblings', collection: #post.other_siblings, as: :sibling %>
<%= render partial: 'parent', locals: {parent: #post.parent}%>
<% else %>
<p>There are no related posts</p>
<% end %>
The paritals:
<%# posts/_children.html.erb %>
<li><%= link_to child.title, post_path(child)%></li>
<%# posts/_sibling.html.erb %>
<li><%= link_to sibling.title, post_path(sibling)%></li>
<%# posts/_parent.html.erb %>
<% unless parent.nil? %>
<li><%= link_to parent.title, post_path(parent) %></li>
<% end %>
Then the Post model can organize the logic:
class Post < ActiveRecord::Base
def has_related_posts?
!children.to_a.empty? || !other_siblings.to_a.empty? || !parent.nil?
end
def children
self.children || [] # Rails does this automatically, but just for the example
end
def other_siblings
self.siblings.where.not(id: self.id)
end
#...
end
I know this doesn't directly answer your question, however IMHO I think it's a better solution.
You have two options here.
Use IF ELSIF
<% if #post.has_children? %>
<% #post.children.each do |child| %>
<li><%= link_to child.title, post_path(child)%></li>
<% end %>
<% elsif #post.has_siblings? %>
<% #post.siblings.where.not(id: #post.id).each do |sibling| %>
<li><%= link_to sibling.title, post_path(sibling)%></li>
<% end %>
<% elsif !#post.parent.nil? %>
<li><%= link_to #post.parent.title, post_path(#post.parent) %></li>
<% else %>
</ul>
</p>
<p>
There are no related posts.
</p>
<% end %>
Use only case as doz mentioned
<% case
when #post.has_children? %>
<% #post.children.each do |child| %>
<li><%= link_to child.title, post_path(child)%></li>
<% end %>
<% when #post.has_siblings? %>
<% #post.siblings.where.not(id: #post.id).each do |sibling| %>
<li><%= link_to sibling.title, post_path(sibling)%></li>
<% end %>
<% when !#post.parent.nil? %>
<li><%= link_to #post.parent.title, post_path(#post.parent) %></li>
<% else %>
</ul>
</p>
<p>
There are no related posts.
</p>
<% end %>
You can do it with a case statement. Just give case no parameter. Eg instead of case #post just use case
This is the equivalent of an if statement. And should work for what your trying to achieve
Check out ruby case statements for some examples

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(' ')

Resources