Complex form with Rails - ruby-on-rails

I have a form where I'd like to create a parent record and a child record at the same time. For a simple example let's say its a Company with the first Employee.
in my controller I do something like:
def new
#company = Company.new
#company.employees.new
end
and in my view this:
<%= form_for(#company) do |form| %>
<div>
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<%= form.fields_for :employees do |employee_form| %>
<div>
<%= employee_form.label :name %>
<%= employee_form.text_field :name %>
</div>
<% end %>
<% end %>
and back in my controller again:
def create
#company = Company.new(params[:company])
#company.employees << Employee.new(params[:company][:employees_attributes]["0"])
# save stuff
end
Question 1:
I couldn't get the employee collection on the company to be populated with the single employee created in the form. When I looked at the params I found the [:employees_attributes]["0"] stuff.
What I have works, but is there a cleaner way to do this?
Question 2:
If the validation doesn't pass for the employee I get a generic "Employees is invalid" instead of the Name required validator message. I get I am calling save on the collection and rails is doing its best to bubble a validation error up, but is there a cleaner way to do this so I can get the errors specific to the employee?
In Short
How can I clean this up so the related models are created automatically from the params, and so that I get the validation messages for a single employee.
Thanks for looking.

1) fields_for arranges for the child objects attributes to be nested inside the parent objects attributes in the params hash that gets sent back to the controller action. To get Rails to automatically update the child objects tell the parent model to accept nested attributes using the accepts_nested_attributes_for declaration.
2) There is an errors object for every ActiveRecord object. Loop through the errors list and display the messages.
Best way to achieve this is to create a partial and a view helper method that will take render the errors for you. then replace the generated errors messages in the forms with a call to your render_error_messages method. You have all the code to do this already in the generated forms. You just need to refactor that code into a partial, create the helper - which should accept an array of model names as a parameter then do what you want with the info. Wither render a partial for each model or render a partial that will deal with child objects as well as the parent object. Totally your call.
3) Change your new action to build rather that create a new child object so instead of
def new
#company = Company.new
#company.employees.new
end
do this
def new
#company = Company.new
#company.employees.build
end
4) Watch those Railscasts to see how accepts_nested_attributes works
http://railscasts.com/episodes/196-nested-model-form-part-1
and
http://railscasts.com/episodes/197-nested-model-form-part-2
Update
So how does the above information leave you in relation to your questions.
1) What I have works, but is there a cleaner way to do this?
You've fixed the new action as per point 3 above right? Now your create action can look like this
def create
#company = Company.new(params[:company])
# save stuff
end
Which is much cleaner as it has reverted to the original generated create action.
You may not think that's much of an update and therefore not that much cleaner. Well in isolation you'd be right. But consider that you could add as many relationships as you like ad add as many fields_for declarations as you like nd you could turn the user -> employee relationship into a has_many (I know that you wouldn't). You could do all that and your create and update actions stay EXACTLY the same and that's why it's cleaner.
2) is there a cleaner way to do this so I can get the errors specific to the employee?
Given my response in point 2 above you know that there is an errors object on the employee object as well as on the user object right? You also know now that you can loop through that errors object to get the messages right?
So you could do this
<% if #user.employee.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#user.employee.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% #user.employee.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
At the risk of repeating myself I'll just say that you should refactor your error messages view code into a partial that will take any object as a parameter then you can call it from any view thus enabling you to change the styling and the functionality for all your forms.
Hope that's clearer

Related

Ruby on Rails: Check_boxes for each item in a list

I'm trying to render simple check_boxes in a list of results so users can select on which of these results to get additional content for.
Example model setup (names changed):
UserRequest has_one :response
Response has_many :individual_results
IndividualResult has_one :contact_info
In the UserRequestsController I have a show action, which shows the user_request (duh), response, and its individual_results in a table.
The user should be able to select items (= contact_info) to request additional content for those. For some reason I don't manage to render the check_boxes after days of trying. Basically, I don't really understand where and how to store the array of selected contact_infos and how to pass it to the method that then gets the additional content.
My attempt was:
Create a ContactInfosController (didn't exist before, the user was only creating and showing her requests up to now, so contact_info was "model only" before)
Create a method request_content(contact_infos) (as post, to pass the user-selected contact_infos to. If I were to make it Restful, it would probably be "edit/update")
Add the form with check_boxes search_request#show
My search_request/show.html.erb:
<tbody>
<% if #response %>
<% form_for #contact_infos, url: contact_infos_request_content_path(#contact_infos) do |form| %>
<% #response.individual_results.each do |result| %>
# result.foo, ...
# result.contact_info.bar
<%= form.check_box "contact_info", "contact_info.request_content?", "true", "false %>
<% end %>
# form.submit
<% end %>
<% end %>
</tbody>
As it didn't work, I also had to declare the instance variable #contact_infos in RequestsController#show as #contact_infos = #response.individual_results.map { |r| r.contact_info }
Now, it fails at the check_box with "undefined method `merge'". Also, not sure how the params would be passed? I feel I went seriously "off the rails" and probably screwed up the design with this as it seems way too complicated...
Would anybody be so kind and help me get into the right direction, e.g., how would you pass the response to a method to request additional information? Read tons online but somehow couldn't apply it.
Thanks so much!

Edit/create nested resources in Formtastic (Rails)

This question is regarding Rails 4/postgresql and the app is hosted on Heroku.
I am making a Quiz-functionality on a website and I am wondering on how to implement the forms (using Formtastic) best to make this is easy as possible. I have three models:
Quiz (has_many :quiz_questions), e.g. "Test to see how awesome you are"
QuizQuestion(belongs_to :quiz, has_many :quiz_options). e.g. "1. Which is your favorite color")
QuizOption (belongs_to :quiz_question). e.g. "Blue"
I have set up the forms like this:
<%= semantic_form_for([:admin, #quiz], :url => admin_quiz_path(#quiz.id)) do |f| %>
<%= render 'form' , :f => f %>
<% end %>
where the form looks like this:
<%= f.inputs %>
<h3>Quiz questions</h3>
<%= f.semantic_fields_for :quiz_questions do |qq_f| %>
<%= qq_f.inputs %>
<h4>Quiz options</h4>
<%= qq_f.semantic_fields_for :quiz_options do |qqo_f| %>
<%= qqo_f.inputs %>
<% end %>
<% end %>
<%= f.actions do %>
<%= f.action :submit %>
or go <%= link_to 'back', admin_quizzes_path %>
<% end %>
It seems, however, not to be working the way I want. I expect to be able to see the fields of QuizQuestion and QuizOptions in this form (there are objects for those) but I don't.
More importantly is that I would like to be able to create a New QuizQuestion and subsequently QuizOption in this form. It doesn't necessarily have to be jQuery/ajax or anything but I would like to do it all from this form.
Basically, I would like my workflow to be like:
Create a Quiz and add values to it. Click Create.
Add QuizQuestion number one and add the values to it (like "name label"). Click Create.
Add QuizOption related to QuizQuestion number one, and its "name label". Click create.
Repeat for QuizQuestion/QuizOption until the Quiz is done.
How can I do this?
For your workflow you might have to add accept_nested_attributes_for for the nested resources, this way when creating an object object you can actually create nested children (as long as they fulfill all the validations). This way:
# A quiz :has_many :quiz_questions
#quiz = Quiz.create(...)
with a declaration like:
has_many :quiz_questions
accepts_nested_attributes_for :quiz_questions
in your Quiz model you'll actually be able to create QuizQuestion from the quiz model like:
# using the previously quiz model
quiz.quiz_questions.create(...)
Doing the same for the deeply nested associations will do have the same effect.
Perhaps the reason why you don't see any field on the form is because there is not nested object created. Let me explain. When you create a new Quiz object, in your quizs_controller (or whatever the inflection for quiz is...) you need a:
def new
quiz = Quiz.new()
end
and
def create
Quiz.new(quiz_params)
end
private
def quiz_params
# whitelisted parameters sent along with the form
params.require(:quiz).permit(...)
end
if you actually want to be able to see the fields in the form you'll have to use the build method and actually populate that new object with respective the nested resources.
Note that for this to work with the form you will have to whitelist in the quizzes_controller the right attributes. You can debug the params you receive once you send the new quiz formulary and check that everything is right.
TIP! if you don't want to worry about the JS when adding nested resources dynamically, I recommend you using the cocoon gem

How to show a view based on user attribute?

In my Rails app, I have a store with products and users. Both of those have models and controllers.
What I want to achieve is to show on a view template a product to a current_user based on the attribute from a model that he has. For an example if a user has "Female" attribute from user model, and then to show some products related to this attribute. How can I achieve this?
These are my product views where all products are showed:
<% #products.each do |product| %>
<%= render "product_row", product: product, order_item: #order_item %>
<% end %>
_product_row.html.erb
<h4><%= product.name %></small></h4>
<div class="image">
<%= image_tag product.image.url(:original), class: "img-responsive" %></div>
<p>Some description.</p>
Add to Package
It sounds like you need to retrieve objects from your product class based on an attribute from the User class. Sounds like a basic service object or just a method on the user:
class User < ActiveRecord::Base
def products_for_gender
if gender == 'female'
Product.where("do some logic here based on female")
elsif gender == 'male'
Product.where("do some logic here based on male")
else
#do some other logic just in case it's nil
end
end
end
then in your controller you do this
#products = current_user.products_for_gender
in your view you then render a list with those products. This prevents you from putting logic in your view, which is rarely a good idea.
Also there's more abstraction possible, the if statement is not the prettiest, but this will cover your issue i believe. Eventually you could look into using service objects for example, https://blog.engineyard.com/2014/keeping-your-rails-controllers-dry-with-services
How are you saving the users model. If you are using devise then you have current_user helper method available in your views and you can use that to get the curren_user.gender attribute and show the view based on this

Each Loop in Rails 4 Controller to create new instance variable

I have a parent form (lead) and child form (QuoteMetal) (which is rendered multiple times on the same submit). All the information from the forms gets written to their respective data tables, but I need the information from the child forms to perform a query and return those values. I have the forms created and a controller which writes the information to the data tables.
I need help with making the query results for each of the child forms then accessing them in the views. Here is what I currently have.
class LeadsController < ApplicationController
def index
#lead = Lead.new
#quote_metal = #lead.quote_metals.build
end
def create
#raise params.inspect
#lead = Lead.create!(lead_params) #write to data tables (which works)
#lead.quote_metals.each do |f|
#metal = Metal.calculate_metal(f).first #here is where my problem is! the #calculate_metal is the query located in my model
end
end
def show
end
private
def lead_params
params.require(:lead).permit([:name, .....
quote_metals_attributes: [:id...],
quote_melees_attributes: [:shape...],
quote_diamonds_attributes: [:shape...]
])
end
end
and the view:
<div class='container'>
<div class="row">
<div class='col-sm-3 col-sm-offset-2'>
<h3 class="text-center">Your Search: </h3>
</div>
<div class='col-sm-5'>
<h4 class="text-center">Returning estimates for a <%= #metal.metal %> setting
weighing <%= #metal.weight %> <%= #metal.unit %>. I am paying
<%= number_to_currency(#metal.price, precision: 2) %> per <%= #metal.unit %>.</h4>
</div>
</div>
Actions on QuoteMetal instances should be handled in the QuoteMetal class. So, I would replace:
#lead.quote_metals.each do |f|
#metal = Metal.calculate_metal(f).first #here is where my problem is! the #calculate_metal is the query located in my model
end
with:
#lead.quote_metals.each do |f|
f.create
end
And then in your QuoteMetal class, you can use a before_save callback and perform the calculation there. Keep in mind this will invoke calculate_metal every time a QuoteMetal is saved or updated.
Even better would be to use accepts_nested_attributes_for in the Lead model so that quote_metals can be automatically created when leads are created. See Rails documentation here. With this approach, you could eliminate the above three lines in the controller, but would still need the callback in the QuoteMetal class to perform the custom calculation.
Separately, be aware that your call to create! will raise an exception if validation fails. Not sure you intend that.

Rails Model-Association Form Question

Hey guys, I'm having trouble understanding a Rails construct. I'm using Rails 3 but I doubt this is specific to this version.
I have a model, Goal that has_many :commits and naturally, a Commit model that belongs_to :goal. I created the proper migration so that commits_table.references :goal.
I am actually going through the Rails Getting Started guide, except in that article they use a Post and Comment respectively.
Now that I've described the situation I can express my confusion. On the Goal's show view, I have embedded a form to create a new Commit which is 'attached' to the currently viewed Goal. This works fine and all. However, I am having trouble understanding why we do this
<%= form_for([#goal, #goal.commits.build]) do |f| %>
Shouldn't it be form_for(#commit)? I understand why we want the #goal, to provide some context since the commit is a nested resource. However, in the actual generated source, the form is appropriately named as commit, that is, the fields are named commit_blah. How did Rails know this? I understand that there's this whole system of "trust and magic" and all, but I mean at least at the high level, what from this code hinted to Rails that I wanted a commit?
I looked at the documentation for form_for and it seems like one of the parameters could be the action to take for the form. I imagine that in this case, that's what the #goal.commits.build parameter is for? To designate the action to take? Is this how Rails deduces that I want a commit? Would this also explain why this form is handled by the Commit controller even though this code is in the Goal's view?
Also, why are these parameters passed as an array ([])? In Ruby, will the method still just take it as two separate parameters, or is there a reason why this was passed this way?
Finally, rails generate automatically gave me some error showing code in my other _form.html.erb partials:
<% if #commit.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#commit.errors.count, "error") %> prohibited this commit from being saved:</h2>
<ul>
<% #commit.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
If I want to add this to this embedded form, how would I do so? I guess my question is, what would I use in place of #commit?
Thanks. I'm just trying to get my head around these new concepts.
If you go back to the documentation and click 'show source', you'll see
def form_for(record_or_name_or_array, *args, &proc)
...
case record_or_name_or_array
when String, Symbol
...
when Array
object = record_or_name_or_array.last
object_name = options[:as] || ActiveModel::Naming.singular(object)
apply_form_for_options!(record_or_name_or_array, options)
args.unshift object
else
...
end
...
output << fields_for(object_name, *(args << options), &proc)
...
For form_for, the first parameter can be a record, name or an array. In your case, you pass it an array. The code then determines the 'object' as the last member of that array, which is your #goal.commits.build object. The object name is determined from the ActiveModel::Naming.singular method.
console > ActiveModel::Naming.singular(#goal.commits.build)
=> "commit"
Then it generated the appropriate form fields using fields_for and 'commit'.
It looks like you are using nested resources. Check your routes.rb file to see if you have something like:
map.resources :commits, :has_many => :goals
or perhaps:
map.resources :commits do |commit|
commit.resources :goals
end
If that is the case, then you will need to supply both the commit and goal objects to the form_for method.

Resources