Rails local variable order in erb causing errors - ruby-on-rails

So I have the following erb that works:
<ul class="list-group">
<%- #user_skills.each do |user_skill| %>
<li class="list-group-item"><%= user_skill.skill.name %></li>
<% end %>
</ul>
<%= semantic_form_for current_user.user_skills.new do |f| %>
<%= f.inputs %>
<%= f.actions %>
<% end %>
In my controller, I have a #user_skills = current_user.user_skills.
Once I swap the two blocks:
<%= semantic_form_for current_user.user_skills.new do |f| %>
<%= f.inputs %>
<%= f.actions %>
<% end %>
<ul class="list-group">
<%- #user_skills.each do |user_skill| %>
<li class="list-group-item"><%= user_skill.skill.name %></li>
<% end %>
</ul>
It raises an error like this: undefined methodname' for nil:NilClass`. So I'm guessing the form builder is somehow overwriting the user_skill inside the ul block. But why and how do I resolve the problem? It doesn't seem to be a formtastic issue, since I also used the native form builder.
UPDATE:
So I found out that the #user_skills array actually contains the just created user_skill, which hasn't been persisted and do not have a #skill_id or #skill.name! And if I do UserSkill.new at the start of the form, then the #user_skills array will be ok. Maybe there is a caching layer in activerecord that will automatically include just created objects into the query. Or is this a bug?

The call current_user.user_skills.new adds a blank user_skill to the has_many association of current_user. Since #user_skills is simply a reference to current_user.user_skills, the blank instance is included in the the unordered list when iterating after initializing the new user_skill record.
You'd be better off simply initializing a new user_skill in the form:
<%= semantic_form_for UserSkill.new do |f| %>
<%= f.inputs %>
<%= f.actions %>
<% end %>
Then, in the create action of your UserSkillsController, associate the user_skill attributes with the current_user. A bare-bones version could look something like:
def create
#user_skill = current_user.user_skills.create(params[:user_skill])
respond_with #user_skill
end

Related

Scope of each on rails template

I'm new to rails and I'm trying to build a view that will list the parents and related children
Ex:
Passport has many Visas
I want to list information about the passport and the visas that the passport has.
So I have
<% #passport_list.each do |passport| %>
# passportFields
<% passport.visas.each do |visa| %>
<%= t.text_field :visa_type %>
<% end %>
<% end %>
I'm getting the error
undefined method `visa_type' for #Passport:0x000000091b8b28
It looks like rails is trying to find the property visa_type for passport, instead of in visa. How does the scope work within each? Can I force it to access visa_type from visa?
I think you're looking for the fields_for form helper. This will allow you to create fields for the relevant visa attributes. Replace your code sample with the following, and you should be all set.
<% #passport_list.each do |passport| %>
# passportFields
<% t.fields_for :visas do |visa_fields| %>
<%= visa_fields.text_field :visa_type %>
<% end %>
<% end %>
You can also iterate over the list as follows:
<% #passport_list.each do |passport| %>
# passportFields
<% passport.visas.each do |visa| %>
<% t.fields_for :visas do |visa_fields| %>
<%= visa_fields.text_field :visa_type %>
<% end %>
<% end %>
<% end %>
For more information on fields_for, check out the link I added above, and to customize further for your use case, check out the "One-to-many" section.
IMO you should always handle the null case of an object.
Something like this if you use rails (present? is a Rails function)...
<% if #passport_list.present? %>
<% #passport_list.each do |passport| %>
passportFields
<% passport.visas.each do |visa| %>
<%= t.text_field :visa_type %>
<%end%>
<%end%>
<% else %>
<p>Nothing to see here</p>
<% end %>
However if your #passport_list is backed by an ActiveRecord Query, you can handle this in the model/helper/controller by returning the .none query on the model. Note that this differs from an empty array because it is an ActiveRecord Scope, so you can chain AR queries onto it
# scope on AR model
def self.awesomeville
where(country_of_origin: "awesomeville")
end
# method queried in controller
#passport_list = Passport.all
if #passport_list.present?
#passport_list
else
Passport.none
end
# additional filtering in view is now possible without fear of NoMethodError
#passport_list.awesomeville
Whereas a ruby Array would raise an error as it would respond to the Array methods.

Ruby on Rails: How to print contents of variable in view and make checkbox?

I am trying to create a checklist in rails using form_for. This checklist is taken from a table which I gained in the create action of my sign_ins controller:
#content = OrientationContent.where(site_id: session[:site_id])
In my view I want to use the form_for helper to iterate through the list in #content:
<%= form_for(:sign_ups ) do |f| %>
<% #content.each do |c| %>
<%= f.check_box nil %> <%= c %> <br>
<% end %>
<% end %>
However this is not working and it produces two square brackets on the page: [].
How do I go through the list and print the name while creating a check box on the left of it? The check box does not have any meaning or data, I just need it present for reference.
Solved:
In the controller, need to pluck an individual field:
#content = OrientationContent.where(site_id: 1).pluck(:content)
In the view, structure as so:
<%= form_for(:sign_ups) do |f| %>
<% #content.each do |c| %>
<%= f.check_box nil %> <%= c %> <br>
<% end %>
<% end %>

undefined method `model_name' for NilClass:Class with Partials and Form for

I am having a weird issue where my rails application seemingly isn't reading the local variable passed to the partial, but only inside of form_for.
I have a set up a button where a user can bookmark an object, let's call it zoo.
The code looks like this:
Zoo Listing View:
<li><%= render 'directory/bookmarks', zoo: zoo %></i></li>
Then the directory/bookmarks partial:
<% if user_signed_in? %>
<% if current_user.has_zoo_bookmarked_already? %>
<%= form_for(current_user.bookmarks.find_by_zoo_id(zoo.id), html: { method: :delete }, remote: true) do |f| %>
<%= button_tag do %>
<button class="btn btn-lg btn-block btn-primary">remove bookmark
<% end %>
<% end %>
<% else %>
<%= form_for(current_user.bookmarks.build(zoo_id: zoo.id)) do |f| %>
<%= f.hidden_field :zoo_id, value: zoo.id %>
<%= button_tag do %>
<button class="btn btn-lg btn-block btn-primary">bookmark
<% end %>
<% end %>
<% end %>
<% else %>
<% end %>
Anyway, the problem is the zoo.id on the 3rd line is not being evaluated. I get this error.
undefined method `model_name' for NilClass:Class
The weird part is that locals are being read on the page.
If I place this code snippet <%= zoo.id %> anywhere on the page, I get the id. Then, the find_by_zoo_id is being evaluated also...if I put 255 a hardcoded number, it works. Or if I set it to a variable, #id = 255 and then pass in the #id, it works.
The zoo.id only does not work inside of the form_for for some reason. If I set it to an instance variable on the page, #id = zoo.id, that doesn't work either.
Any ideas? I'm sure it is something minor.
EDITED:
The relationship is has_many :through. Forgot to mention, I use this code for a different association in the same application and it works fine.
That error you're getting suggests current_user.bookmarks.find_by_zoo_id(zoo.id) is nil. find_by_zoo_id might be returning you nothing
Of course it was -- I was iterating through a list of zoos with bookmarks, but not all of them would be bookmarked by the user. I just had to add an additional condition.
<% if user_signed_in? %>
<% if current_user.has_zoo_bookmarked_already? && current_user.bookmarks.find_by_zoo_id(zoo.id) %>
#....

Error Handling Multi-Model Form

I have a multi-model form and I cannot seem to capture the errors related to the nested models. This is the hierarchy of the form Project->Team->Roles->Role_skill_relationship. I am able to capture errors related the project model instance #project but I have been unsuccessful at capturing validations related to the Role and Skills models. The errors are directly returned in the browser page rather than redirecting and flashing to the screen. One example of an error if I intentionally fill out a role portion of the form incorrectly is
NoMethodError in Roles#create
Showing
C:/Users/Dstile/Documents/GitHub/creunity_app/app/views/skills/_form.html.erb
where line #8 raised:
undefined method `map' for nil:NilClass Extracted source (around line #8)
7: <%= skill_form.label :skill %>
8: <%= skill_form.collection_select :skill_id, #skills, :id, :name, :prompt => "Select a > skill" %>
Here is a portion of the code
<%= render 'shared/project_error_messages' %>
<%= project_form.label :title %>
<%= project_form.text_field :title %>
<%= project_form.label :category, "Category" %>
<%= project_form.select(:category, Project::CATEGORY_TYPES) %>
<%= project_form.label :description %>
<%= project_form.text_area :description %>
<%= project_form.label :goal_1, "Goal 1:" %>
<%= project_form.text_field :goal_1 %>
<h2>Your Team</h2>
<%= project_form.fields_for :team do |f| %>
<%= render 'teams/form', :team_form => f %>
<% end %>`
Here is the error render code
'<% if #project.errors.any? %>
<div id="error_explanation">
<div class="alert alert-error">
The form contains <%= pluralize(#project.errors.count, "error") %>.
</div>
<ul>
<% #project.errors.full_messages.each do |msg| %>
<li>* <%= msg %></li>
<% end %>
</ul>
</div>
<% end %>'
The role form is nested within the team form in the same manner team is nested in project. From all of the other threads I have read my understanding is that the errors for project and all of its children models (team, roles, role_skill_relationships) should be captured by the #parent object.
Is there a config setting or piece of code I am missing that should force the browser to ignore the errors? My thinking is that the errors may be in #project but that this process is interrupted.
It appears #skills has not been set. #collection_select runs #map on the passed-in collection (#skills in this case), and will show that error if the collection is nil (which is the default value for undefined instance variables).
Remember that, in the case of validation errors, the #create method in your controller will simply be rendering a template - it does not run the corresponding action method for that template. So if you're defining #skills in your #new action, then you need to also define it in your #create action (or, better yet, in a before_filter).

f.error_messages in Rails 3.0

Rails 3.0 deprecated f.error_messages and now requires a plugin to work correctly - I however want to learn how to display error messages the (new) native way. I am following the getting started guide, which uses the deprecated method when implementing the comments form. For example:
<h2>Add a comment:</h2>
<%= form_for([#post, #post.comments.build]) do |f| %>
<%= f.error_messages %>
<div class="field">
<% f.label :commenter %><br />
<%= f.text_field :commenter %>
</div>
<div class="field">
<%= f.label :body %><br />
<%= f.text_area :body %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Here is the correct way to do it (as generated by the scaffold):
<%= form_for(#post) do |f| %>
<% if #post.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#post.errors.count, "error") %> prohibited this post from being saved:</h2>
<ul>
<% #post.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
. . .
I understand that I use the #post variable in the latter example, but what variable do I reference in the former to get the error messages for comment creation?
The best and clean way to implement error_messages in your form is by implementing error_messages in a FormBuilder.
For example, here is the error_messages method I've implemented for my last project.
By implemeting your own FormBuilder you can follow the rules and styles of your webdesigner...
Here is an example that will output the errors list in ul/li's with some custom styles :
class StandardBuilder < ActionView::Helpers::FormBuilder
def error_messages
return unless object.respond_to?(:errors) && object.errors.any?
errors_list = ""
errors_list << #template.content_tag(:span, "There are errors!", :class => "title-error")
errors_list << object.errors.full_messages.map { |message| #template.content_tag(:li, message) }.join("\n")
#template.content_tag(:ul, errors_list.html_safe, :class => "error-recap round-border")
end
end
Then in my forms :
= f.error_messages
And that's all.
I'm pretty sure all you'd need to do is reference #post.comments
So you could do something like:
<% #post.comments.each do |comment| %>
<% if comment.errors.any? %>
<% comment.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
<% end %>
<% end %>
Or just pull all the errors out out:
comment_errors = #post.comments.map(&:errors)
and then loop through them in your display logic to output each of the comment errors.
This functionality exists as a standalone gem dynamic_form.
Add the the following to your Gemfile
gem 'dynamic_form'
From the github page:
DynamicForm holds a few helpers method to help you deal with your Rails3 models, they are:
input(record, method, options = {})
form(record, options = {})
error_message_on(object, method, options={})
error_messages_for(record, options={})
It also adds f.error_messages and f.error_message_on to your form builders.
Here is my solution to the whole error scene.
I created a partial which simply uses a model variable which one would pass when rendering it:
<%# app/views/errors/_error.html.erb %>
<%= content_for :message do %>
<% if model.errors.any? %>
<ul>
<% model.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<% end %>
<% end %>
You can easily add dynamic html class and/or id names based on the name of the model, as well as generic ones.
I have things setup where my error messages render in all the same place in a layout file:
<%# app/views/layouts/application.html.erb %>
<%= yield :message %>
If one didn't want that functionality, removing the content_for in the partial would do the trick.
Then in really any view you want you can simply write:
<%= render 'errors/error', model: #some_model %>
One could further expand this by creating a partial which takes a collection and leverages the error partial above:
<%# app/views/errors/_collection.html.erb %>
<% collection.each do |model| %>
<%= render 'errors/error', model: model %>
<% end %>
Render it with:
<%= render 'errors/collection', collection: #some_model.some_has_many_association %>
I like this way. It is simple, easy to manage/maintain, and incredibly tweakable.
I hope this helps!
EDIT: Everything in HAML
-# app/views/errors/_error.html.haml
= content_for :message do
- if model.errors.any?
%ul
- model.errors.full_messages.each do |msg|
%li= msg
-# app/views/layouts/application.html.haml
= yield :message
= render 'errors/error', model: #some_model
-# app/views/errors/_collection.html.haml
- collection.each do |model|
= render 'errors/errors', model: #some_model
= render 'errors/_collection', collection: #some_model.some_has_many_association
I guess that the [#post, #post.comments.build] array is just passed to polymorphic_path inside form_for. This generates a sub-resource path for comments (like /posts/1/comments in this case). So it looks like your first example uses comments as sub-resources to posts, right?.
So actually the controller that will be called here is the CommentsController. The reason why Lukas' solution doesn't work for you might be that you actually don't use #post.comments.build inside the controller when creating the comment (it doesn't matter that you use it in the view when calling form_for). The CommentsController#create method should look like this (more or less):
def create
#post = Post.find(params[:post_id]
#comment = #post.comments.build(params[:comment])
if(#comment.save)
# you would probably redirect to #post
else
# you would probably render post#show or wherever you have the form
end
end
Then you can use the code generated by scaffolding, only replace #post instance variable with #comment in all the lines except form_for call.
I think it may also be a good idea to add the #comment = #post.comment.build to the controller method that displays this form and use form_for([#post, #comment], ...) to keep the form contents displayed in the form if there're errors.
If this doesn't work and you're not able to figure it out, please add your CommentsController#create method to the question.
I just looked into the docrails github issues, and they've decided to remove f.error_messages instead of explaining how to do validation for comments.
a rather simple implementation can be achieved with
class ActionView::Helpers::FormBuilder
def error_message(method)
return unless object.respond_to?(:errors) && object.errors.any?
#template.content_tag(:div, object.errors.full_messages_for(:"#{method}").first, class: 'error')
end
end
which allows one to use
<%= form.label :first_name %>
<%= form.text_field :first_name %>
<%= form.error_message :first_name %>
and with the following sass
#import variables
.error
padding: $margin-s
margin-left: $size-xl
color: red
.field_with_errors
input
border: 1px red solid
input:focus
outline-color: red
it looks like
using simple form gives you quite similiar functionality with more advanced functionality.
For example check out their examples with bootstrap

Resources