I am making an app in Rails 4. I use Simple Form.
I have a profile model and a qualifications model.
The associations are:
profile.rb
belongs_to :profile
qualifications.rb
has_many :qualifications
I have a form in my profile views, which includes a part of a form from my qualifications view.
profiles#form
<%= simple_form_for(#profile) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<div class="row">
<div class="intpol2">
Your professional qualifications
</div>
<%= render 'qualifications/form', f: f %>
</div>
Qualifications#form
<%= simple_fields_for :qualification do |f| %>
<div class="form-inputs">
<div class="row">
<div class="col-md-6">
<%= f.input :title, :label => "Your award" %>
</div>
<div class="col-md-6">
</div>
</div>
<div class="row">
<div class="col-md-6">
<%= f.input :level, collection: [ "Bachelor's degree", "Master's degree", "Ph.D", "Post Doctoral award"] %>
</div>
<div class="col-md-6">
<%= f.input :year_earned, :label => "When did you graduate?", collection: (Date.today.year - 50)..(Date.today.year) %>
</div>
</div>
Users may have more than one degree. I want to add a field that is a button which says 'add another qualification' and then a new set of the qualification form fields is available.
I found this post which tries to do something slightly different. I don't want 10 blank sets of the form field (it will make the form look too long).
Creating multiple records for a model in a single view in Rails
Is there another way to achieve this?
You'll be looking for a gem called cocoon; you can also watch this Railscast (Nested forms) which is woefully outdated but still explains the structure very well.
The pattern is very simple, but requires some extra parts:
Have an ajax button which calls the controller
The controller needs to return a form and built fields_for
You'll use JS to append the new fields_for to the original form
The biggest problem is the id of your new fields_for - new implementations of this pattern use child_index: Time.now.to_i
I've written about this here.
Here's a new version:
Ajax
Firstly, you need an "Add Qualification" button, which links to your controller through ajax:
#app/views/profiles/_form.html.erb
<%= simple_form_for(#profile) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<div class="row">
<div class="intpol2">Your professional qualifications</div>
<%= render 'qualifications/form', f: f %>
</div>
</div>
<%= button_to "+", new_profile_path, method: :get %>
<% end %>
Controller
This will go through the new controller method, which we should be able to manage to return the specific response for the ajax request:
#app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
respond_to :js, :html, only: :new
def new
#profile = Profile.new
#profile.qualifications.build
respond_with #profile #-> will invoke app/views/profiles/new.js.erb
end
end
Response
Once your new.js.erb has fired, we need to build the new HTML form, extract the fields_for and append it to your view:
#app/views/profiles/new.js.erb
var fields_for = $("<%=j render "profiles/form" %>").html(); //-> might need tweaking
$(fields_for).appendTo("#new_profile.form-inputs");
child_index
You should also change your qualifications/form to include the child_index:
#app/views/qualifications/form.html.erb
<%= simple_fields_for :qualifications, child_index: Time.now.to_i do ...
Child index is meant to denote the index of the fields_for elements. In our case (since we're making all new records), it doesn't matter. Using Time.now.to_i ensures a totally unique id each time.
Finally, you need to make sure you're calling:
<%= simple_fields_for :qualifications ... %>
... plural
It seems like that you have to use nested form. You have to try your link tutorial because I will use this too. For another tutorial you can use this as reference nested_forms-rails-4.2.
I hope this help you.
Related
I am creating a Rails app, and I need a form to function in one of my views and submit data to a table without the use of a scaffold (like I usually do).
Now, the place where this comment form is going to appear is in one view within the blog folder. It will need to allow the user to put in their comment, save it to the table, and then return to the same page.
While this is a pretty commonplace error, I am confused because I am specifying two things that seem critical: creating resources in my routes file for the form, and second, using a create method in my controller.
In the blog.html.erb, this happens in this form:
<%= form_for :cements do |f| %>
<div class="form-group">
<div class="field">
<%= f.label :post %><br>
<%= f.text_area :post, class: "form-control" %>
</div>
</div>
<h5 id="username">Username</h5>
<div class="form-group">
<div class="field">
<%= f.text_field :username, class: "form-control" %>
</div>
</div>
<%= f.hidden_field :slug, :id => "hiddenPicker"%>
<div class="actions">
<%= f.submit "Save", class: "btn btn-success-outline" %>
</div>
<% end %>
Then, in my controller, I have a create method that should redirect back to the original page, as I wanted.
blogs_controller.rb
class BlogsController < ActionController::Base
def index
#posts = Post.order('updated_at DESC').all
#comments = Cement.all
end
def blog
#posts = Post.where(slug: params[:id]).all
#comments = Cement.all
end
def create
#cements= Cement.new(story_params)
#cements.save
redirect_to(:back)
end
private
def story_params
params.require(:cements).permit(:username, :post, :slug)
end
end
Good news: the comment form renders in the view. Bad news: when I submit, I am getting this error: No route matches [POST] "/blog".
My expectation is this will be an issue with my Routes file; however, I have a resources method already in there:
Rails.application.routes.draw do
resources :posts
resources :cements
resources :blogs
The naming convention is the same as my controller file, so I am confused why this error is happening. Any ideas?
:cement is not an object it is just a symbol, so how rails will determine where to POST form? If you inspect your form you will see form action as /blog (current page url).
You should either do
<%= form_for :cements, url: cements_path do |f| %>
or
<%= form_for Cement.new do |f| %>
Both of above will generate form action as /cements, which will submit to CementsController create action, But I see in your case you want to submit it to BlogsController so use the appropriate routes(blogs_path). You can use url in second version also.
I have a form in my rails app that accepts nested attributes. However, what I want to do is for rails to reject the creation of the nested model if a checkbox (outside the model itself) is checked.
Any idea on how to pass an attribute to the :reject_if option of the accepts_nested_attributes_for in the model from the controller?
Thank you very much in advance.
EDIT:
My controller looks like this:
def new
#course = Course.new
#course.course_template = CourseTemplate.new
end
def create
#course = Course.new(course_params)
#course.user = current_user
if #course.save
flash[:success] = t(".new_course_created_succefully")
redirect_to courses_path
else
render 'new'
end
end
And the form:
<%= form_for #course do |f| %>
<%= render 'shared/error_messages', error_model: #course %>
<div class="form-group has-feedback mb">
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
</div>
<div class="form-group has-feedback mb">
<div class="checkbox c-checkbox needsclick">
<label class="needsclick">
<%= check_box_tag "template", "1", false, {class: "needsclick"} %>
<span class="fa fa-check"></span>Is Template?
</label>
</div>
</div>
<%= f.fields_for :course_template do |ff| %>
<div class="form-group has-feedback mb">
<%= ff.label :name %>
<%= ff.text_field :name %>
</div>
<% end %>
<% end %>
send that checkbox as a parameter from the form and put the build operation inside an if statement. No need to bother with the reject_if
You need to handle your create and build operations separately. so instead of passing your model all attributes, youll pass the model the model attributes, and the association, the nested attributes
# controller
course = Course.new(course_params.reject{|attrib| attrib == :course_template_attributes})
unless params[:skip_create]
course.course_templates.build(course_params[:course_template_attributes]
end
...
what you need to do is conditionally create the course_templates, so you can just pass Course.new all your course_params because that creates both the course and the templates, which needs to be done separately.
Note I'm shorthanding with that reject statement up there. you can either manually add in the various params or better yet create another method with strong params and whitelist only the model attributes (not including the course_template_attributes)
additionally. the params[:skip_create] is whatever the parameter is for that checkbox that decides whether or not you want to create the templates
I am using the cocoon gem to try and achieve adding an object which belongs to another with nested fields. I have a 'user_resolution' which has many 'milestones'. I have set up the associations accordingly in both of these models. For some reason, milestones are failing to be created, however if I add one manually in the database I can successfully update it. I am able to dynamically add the fields and remove them using the cocoon gem but that is all. When I click 'add milestone' it redirects me to the show view of the user resolution and throws the success message saying user resolution has been updated, no errors are thrown but the milestone(s) is/are not created.
user_resolution.rb
has_many :milestones
accepts_nested_attributes_for :milestones, reject_if: :all_blank, allow_destroy: true
milestone.rb
belongs_to :user_resolution
I have set up the nested form within the edit view as for now I only want users to add a milestone to a resolution in the edit view.
user_resolutions/edit.html.erb
<%= form_for(#user_resolution) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<%= f.fields_for :milestones do |milestone| %>
<%= render 'milestone_fields', f: milestone %>
<% end %>
<%= link_to_add_association 'Add Milestone', f, :milestones %>
<%= f.submit "Add Milestone" %>
<% end %>
_milestone_fields.html.erb
<div class="nested-fields">
<div class="field-row">
<%= f.label :name, 'Name' %>
<%= f.text_field :name %>
</div>
<div class="field-row">
<%= f.label :description, 'Name' %>
<%= f.text_area :description %>
</div>
<div class="field-row">
<%= f.label :severity, 'severity' %>
<%= f.check_box :severity %>
</div>
<div class="field-row">
<%= f.label :target_date, 'target_date' %>
<%= f.date_select :target_date %>
</div>
<%= link_to_remove_association 'Remove', f %>
</div>
The permitted parameters within the user resolutions controller also contain the following
milestones_attributes: [:id, :user_resolution_id, :name, :description, :target_date, :severity, :complete, :_destroy]
The milestones themselves have no views, they only have a model and a controller. The controller create action (which i'm unsure is required for nested forms) contains the standard following code
def create
#milestone = Milestone.new(milestone_params)
if #milestone.save
redirect_to user_resolutions_path,
:flash => { :success => "You successfully created a milestone" }
else
redirect_to new_milestone_path,
:flash => { :error => "Oops something went wrong. Try again." }
end
end
I've been as informative as I can but if you need anything else let me know. Thanks guys.
which i'm unsure is required for nested forms
You don't need a create action for milestones - they'll be populated from the user_resolutions#create controller action.
There are several things to look at with this. I'll detail some here. This won't be a specific answer, but may help point you in the right direction.
Firstly, you need to make sure you're receiving the correct params.
Cocoon does a great job building the nested form - you need to make sure it's obliging Rails' nested attribute structure.
To do this, you should right-click > view source.
In the f.fields_for section (it won't be called that in the HTML), you'll be looking for the equivalent to the following:
<input type="text" name="milestones_attributes[0][name]" value="">
The important thing to note is the name...
Each time you use a form, or any Rails view helper for that matter, you're really just building standard HTML. form_for just creates an HTML form, and thus any params contained within it need to adhere to a certain structure for Rails to recognize the params.
The f.fields_for elements will typically be called x_attributes[:id][:param] - this is passed to Rails, which cycles through each [:id] to determine the number of nested params to add.
You need to check the source for the above naming structure. If you see it, that's good. If not, it means you haven't built your form properly.
Secondly, you need to make sure your objects are being built in the controller.
I'm not sure how Cocoon does this, but essentially, each time you use f.fields_for, you have to build the associated object before:
def new
#user_reservation = UserReservation.new
#user_reservation.milestones.build #-> this is what makes f.fields_for work
end
If the first step shows incorrect element naming, it means your associative objects are not being built (which is why they're not being recognized).
To test it, you should build the associative objects in the new method, before sending.
Finally, you'll want to post your params.
These tell you in explicit detail what Rails is doing with the nested attributes, allowing you to determine what's happening with them.
Sorry for the long-winded answer. You'll not have received any answers anyway, so I felt it prudent to give you something.
I have a Client model that can have many Projects.
These are my view files:
edit.html.erb
<%= form_for(#project) do |f| %>
<%= render 'fields', :f => f %>
<%= f.submit Create %>
<% end %>
_fields.html.erb
<div>
<%= f.label :name %><br/>
<%= f.text_field :name %>
</div>
<div>
<%= f.label :client_id %><br/>
<%= f.select(:client_id, current_user.client_names) %>
</div>
This is easy and works great.
But is there a way to create a Project from within a Client view as well?
For example on the client edit page it would be nice to have a link New Project for this Client that leads to the above New Project form, but with the respective client preselected in the select box.
Can this be done somehow?
I can't seem to find a way to pass the Client ID to the New Project form.
Thanks for any help!
This is certainly possible, but requires passing the client id to the new_projects_path. This can be done by nesting the routes to projects inside of clients, or just appending the client id. The difference would be URLs that look like the following:
/clients/1/projects/new
- or -
/projects/new?client_id=1
In your projects controller, you should be able to instantiate the #project variable with a preselected client:
#project = Project.new :client_id => params[:client_id]
Yes, you can do this. You're going to use accepts_nested_attributes_for :project in your Client model. Nesting this will allow you to do something like
<%= f.fields_for :project do |p| %>
<fieldset>
<%= p.text_area :content %>
</fieldset>
<% end %>
Check out the railscasts episode #196 on Nested Model Form.
What is the correct way to include a select field within a nested form in Rails 3.2?
I currently have
//IN THE PARENT FORM
<%= f.fields_for :crayons do |crayon| %>
<%= render 'caryon_fields', :f=>crayon %>
<% end %>
//IN THE PARTIAL
<div class="nested-fields">
<%= select (:crayon, :color, [['Red',1],['Blue',2],['Yellow',3],['Green',4]] ) %>
</div>
This is not saving the selected value to the database. I assume this is because the builder is not being passed.
How should I define a select field with hardcoded options in a nested field? Do I need to change tag, i.e. select_tag or collection_select. I'm still unsure of the differences between all these. If anyone can point me towards a clear description it would be much appreciated.
Thanks!
You need associate to your form in your partial like that :
<div class="nested-fields">
<%= f.select (:crayon, :color, [['Red',1],['Blue',2],['Yellow',3],['Green',4]] ) %>
</div>
Whitout partial it's :
<%= f.fields_for :crayons do |crayon| %>
<div class="nested-fields">
<%= crayon.select (:crayon, :color, [['Red',1],['Blue',2],['Yellow',3],['Green',4]] ) %>
</div>
<% end %>