Rails 4, edit multiple model in one form - ruby-on-rails

I'm working on the following feature :
In one page, the user can upload multiple pictures at one time, and then on the following page he can edit the fields for those pictures.
So what i want is basically a form to edit multiple model at one time (the model are not yet saved in the database).
The application use the https://github.com/bootstrap-ruby/rails-bootstrap-forms to generate forms, but if you have a solution with only the form helpers from rails it would maybe be enough to help me to solve my problem.
I'm currently trying
<%= bootstrap_form_tag(url: import_save_client_pictures_path(#client), layout: :horizontal) do |f| %>
<% #imported_pictures.each_with_index do |picture, index| %>
<%= bootstrap_form_for([#client, picture], url: import_save_client_pictures_path(#client), layout: :horizontal) do |ff| %>
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-4 col-lg-4">
<%= image_tag(picture.file_tmp_url, class: 'img-responsive') %>
</div>
<div class="col-xs-12 col-sm-12 col-md-8 col-lg-8">
<%= ff.text_field :title, value: picture.title, required: true %>
<%= ff.text_field :description_en, value: picture.description_en %>
<%= ff.text_field :description_fr, value: picture.description_fr %>
<%= ff.text_field :description_de, value: picture.description_de %>
<%= ff.text_field :copyright, value: picture.copyright %>
<%= ff.collection_select :country, country_sorted_list_with_first_country(:XX), :first, :second, selected: picture.country %>
<%= ff.collection_check_boxes :category_ids, #client.categories.visible_for(current_user), :id, :title %>
<%= ff.text_field(:reference) if current_user.global_admin? %>
</div>
</div>
<%= '<hr />'.html_safe if (index + 1) != #imported_pictures.count %>
<% end %>
<% end %>
<div class="form-actions">
<%= f.primary %>
<% end %>
<%= link_to t(:cancel), [#client, :pictures], class: 'btn btn-default' %>
</div>
But when i click on sending the form, nothing happen.
I'm thinking on going to only one form and generate all the fields for each picture with setting the fields's names to something like 'field_name_index', but it's not very elegant.
What i try to achieve is to have some sort of array that is passed to the controller with the data being like pictures = [picture_1_fields, picture_2_fields] and so
Do you guys can help me?
Thanks :)
Edit: To be precise, Pictures are not nested form or some other models

This answer requires you to make an effort to understand the concepts and actually impliment it yourself. Make sure you read the documentation thoughly!
Setup accepts_nested_attributes_for in the parent model:
class Client < ApplicationRecord
has_many :pictures
accepts_nested_attributes_for :pictures
validates_associated :pictures
end
class Picture < ApplicationRecord
belongs_to :client
end
Then use fields_for to generate fields for the nested records:
<%= bootstrap_form_tag(url: import_save_client_pictures_path(#client), layout: :horizontal) do |f| %>
<%= f.fields_for :pictures do |ff| %>
<%= ff.text_field :title, value: picture.title, required: true %>
<%= ff.text_field :description_en, value: picture.description_en %>
# ...
<% end %>
<% end %>
You might notice that there are no inputs if a Client has no pictures. To solve this you need to seed the association with new records. This is usually done in in the action that presents the form:
def new
3.times { #client.pictures.new }
end
To permit the nested params you want to use a nested array of keys:
def import_save_client_pictures
if #client.update(picture_params)
redirect_to '/somewhere'
else
render :some_view
end
end
def picture_params
params.require(:client)
.permit(pictures_attributes: [:title, :description_en])
end
But consider using AJAX to save/update the child records in individual POST/PATCH requests behind the scenes. It usually gives a better user experience and only requires a standard CRUD controller.

Related

Cocoon gem - Nested form saves elements, asks for a new template file and shows empty fields

I know this question have asked countless times, but I didn't find anyone with the issue I have.
It may be important to note that this is a single resource.
models/basic.rb
class Basic < ApplicationRecord
has_many :social_networks
accepts_nested_attributes_for :social_networks, reject_if: :all_blank, allow_destroy: true
mount_uploader :logo, LogoUploader
end
models/social_network.rb
class SocialNetwork < ApplicationRecord
belongs_to :basic
end
controllers/basic_controller.rb
def show
end
def new
#basic = Basic.new
#basic.social_networks.build
end
def edit
end
private
# Use callbacks to share common setup or constraints between actions.
def set_basic
#basic = Basic.first
end
# Never trust parameters from the scary internet, only allow the white
list through.
def basic_params
params.require(:basic).permit(:base_title, :resume, :logo, social_networks_attributes: [:id, :name, :url])
end
end
views/basics/_form.html.erb
<%= form_with(model: #basic, local: true, url: basics_path) do |f| %>
<div id="social_networks">
<%= f.fields_for :social_networks do |sn| %>
<%= render 'social_networks', f: sn %>
<% end %>
<div class="links">
<%= link_to_add_association 'Ajouter', f, :social_networks %>
</div>
</div>
<%= end %>
_social_network_fields.html.erb
<div class="nested-fields">
<div class="field">
<%= f.label :name %>
<%= f.text_field :name, placeholder: 'Nom' %>
</div>
<div class="field">
<%= f.label :url %>
<%= f.text_field :url, placeholder: 'Lien' %>
</div>
<div class="links">
<%= link_to_remove_association 'Supprimer', f %>
</div>
</div>
As said in the question title, my form works fine and saves up the elements I fill in correctly. However, when I want to edit, I get an error saying that it can't find a template for the resource (it waits for a file named _social_networks.html.erb). When I add it (and let it blank), I get the form as if I want to create some new resources.
What did I do wrong?
Thank you in advance
You wrote
<%= render 'social_networks', f: sn %>
But your view is _social_networks_fields.html.erb
Cant you just rename _social_networks_fields.html.erb to _social_networks.html.erb?
Also change your add association link:
<div class="links">
<%= link_to_add_association 'Ajouter', f, :social_networks, :partial => 'social_networks' %>
</div>
Or just use
<%= render 'social_networks_fields', f: sn %>
And you don't need the social_networks.html.erb

Nested Form Unknown Attribute Error

I'm trying to build a small expense tracking app. Using the nested_form gem to add line items. There is an Expense model which accepts nested attributes. Items belong to expenses.
class Expense < ActiveRecord::Base
belongs_to :organization
belongs_to :department
has_many :expense_types
has_many :items
accepts_nested_attributes_for :items
end
The items model:
class Item < ActiveRecord::Base
belongs_to :expense
end
The controller create action action looks like:
class ExpensesController < ApplicationController
def new
#expense = Expense.new
end
def create
#expense = Expense.new(expense_params)
if #expense.save
flash[:notice] = "Expense Report Submitted"
redirect_to #expense
else
render 'new'
end
end
private
def expense_params
params.require(:expense).permit(:department_id, :expense_type_id, :notes, items_attributes: [:id, :description, :amount, :issue_date, :_destroy])
end
end
The new expense form looks like:
<%= nested_form_for (#expense) do |f| %>
<% if #expense.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#expense.errors.count, "error") %> prohibited
this expense from being saved:</h2>
<ul>
<% #expense.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class"row">
<div class="col-md-8">
<div class="form-group">
<%= f.label :department_id %><br>
<%= f.collection_select(:department_id, Department.all, :id, :department_name, prompt: true, class: "dropdown-menu") %>
</div>
<div class="form-group">
<%= f.label :expense_type_id %><br>
<%= f.collection_select(:expense_type_id, ExpenseType.all, :id, :expense_name, prompt: true, class: "form-control") %>
</div>
<%= f.fields_for :items do |i| %>
<div class="form-group">
<%= i.label :description%>
<%= i.text_field :description, class: "form-control" %>
</div>
<div class="form-group">
<%= i.label :amount%>
<%= i.text_field :amount, class: "form-control" %>
</div>
<div class="form-group">
<%= i.label :issue_date%>
<%= i.date_select :issue_date, class: "form-control" %>
</div>
<%= i.link_to_remove "Remove", class: "btn btn-default" %>
<% end %>
<div><p><%= f.link_to_add "Add Expense", :items, class: "btn btn-default" %></p></div>
<div class="form-group">
<%= f.label :notes %>
<%= f.text_area :notes, class: "form-control" %>
</div>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</div>
</div>
I was able to save expenses before adding the nested attributes. After doing that, whenever I hit the submit button, I get the ActiveRecord::UnknownAttributeError in ExpensesController#create error. It's weird to see unknown attribute: expense_id. Did I miss something here?
Nested Form
To the best of my knowledge, the nested_form gem basically just creates a fields_for instance for your form. If this is the case (it more or less has to be), then you've got several issues you need to consider
By the way, looking at the nested_form documentation, it seems it was primarily designed for Rails 3. When it mentioned about attr_accessible, it now means to focus on strong_params (if you're using Rails 4 of course)
--
Build
As mentioned by Mandeep, you need to ensure you're building the correct ActiveRecord objects for your form. form_for works by taking an ActiveRecord object & populating the various attributes etc with its own attributes.
This is how Rails creates the illusion of persistence with this functionality -- it takes the ActiveRecord & populates the same data recursively
In order to get fields_for to work (which is the basis of nested_attributes, you need to build the associative ActiveRecord object in the action which renders the form_for (in your case new):
#app/controllers/expenses_controller.rb
Class ExpensesController < ApplicationController
def new
#expense = Expense.new
#expense.items.build #-> required for fields_for to work
end
end
Remember, the nested_form gem is not magic - it is essentially just a javascript plugin to replicate the fields_for elements rendered in your form already, and then append them to the DOM.
It essentially uses the child_index: Time.now.to_i "trick" to surmount the incremental id isue
--
Attributes
Secondly, you need to appreciate the error you're receiving
expense_id could be an attribute in the items_attributes objects. I've not seen this with pure Rails, but perhaps the nested_form gem appends a particular attribute to the objects or something
Either way, I believe the problem will be how to associate your items objects with your parent Expense object. To do this, I would do the following:
Check your params hash (see where expense_id is being passed)
Update your strong_params to allow the expense_id
I KNOW THE PROBLEM
DATABASE - you likely don't have the expense_id column in the items table
Fix
You need to create a migration to put the expense_id foreign_key into your items table
To do this, you should open your CMD and perform the following:
$ rails generate migration AddExpenseIDToItems
Then you can change the migration to have the following line:
add_column :expense_id, :items
Then you just need to do:
$ rake db:migrate
This should resolve your issue
Your controllers new action should be like this
def new
#expense = Expense.new
#item = #expense.items.build
end
Also if you are using rails 4 then you don't need nested_form_for gem. Checkout nested forms. In your form you can simply use
<%= form_for #expense do |f| %>
// expense fields
<%= f.fields_for #item do |e| %>
// item fields
<% end %>
<%= f.submit %>
<% end %>

accepts_nested_attributes_for issue in Form

Before asking this, I have already tried with the solution detailed here, but it didn't work.
context: 2 models, Idea and Project. 1 idea belongs to 1 project, 1 projects has many ideas.
I want to create a form to create ideas that have fields of ideas but also to specify which Project they belong to, by indicating the project_id field. I'm doing it with accepts_nested_attributes_for
Issue: I'm not being able to grab the project_id when creating a new idea from a form. From the console I see that a new idea has been saved, but project_id for that idea always returns nil
code:
ideas_controller.rb
# GET /ideas/new
def new
#idea = Idea.new
#idea.build_project
respond_to do |format|
format.html # new.html.erb
format.json { render json: #idea }
end
models> idea.rb
class Idea < ActiveRecord::Base
belongs_to :project
accepts_nested_attributes_for :project
mount_uploader :picture, PictureUploader
validates :name, presence: true, allow_blank: false
end
_form.html.erb
<%= form_for(#idea) do |f| %>
<% f.fields_for :project do |project_fields| %>
<% if #idea.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#idea.errors.count, "error") %> prohibited this idea from being saved: </h2>
<ul>
<% #idea.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<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="field">
<%= f.label :picture %><br>
<%= f.file_field :picture %>
</div>
<div class="field">
<%= f.label :project %><br>
<%= f.number_field :project_id, :class=>"Number" %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<% end %>
"Nested attributes allow you to save attributes on associated records through the parent", according to the documentation. you are doing it through the child, which I don't think is possible.
So either move the accepts_nested_attributes_for to the parent model and use the parent's form to create the parent fields along with the child field. so you'll be creating project and idea through the projects_controller and projects/_form.html.erb
or opt out for something else.
without using accepts_nested_attributes_for, add project_ids to your ideas params attributes as an empty array:
def idea_params
params.require(:idea).permit(:name, :project_ids => [])
end
app/views/ideas/_form.html.erb:
<div>
<%= idea.label 'File under at least one project' %>
<% Project.all.order(name: :asc).each do |project| %>
<div>
<%= check_box_tag "idea[project_ids][]", project.id %>
<%= project.name %>
</div>
</div>
this code will give you checkboxes of your projects to select. this means first you have to create the projects separely in project's own controller and form. so you are not using nesting attributes.

fields_for helper: Need to render one text area in rails view

I'm having trouble with fields_for helper in rails. I have a Question model that has_many answers.
When I display the form for a user to fill in answers I only want one text area to render for a particular question. With my current loop construct, it renders 3 text areas per question. (They are 3 questions in my example).
Ok the code:
<div class="row">
<div class="large-12 columns panel">
<%= form_for(#evaluation_form) do |f| %>
<%= render 'application/errors', f: f %>
<%= f.fields_for :questions do |question_form| %>
<p><%= question_form.object[:title] %></p>
<%= question_form.fields_for :evaluation_form_answers do |answer_form| %>
<%= answer_form.text_area :answer, value: question_form.object.evaluation_form_answers.where(["student_id = ? and evaluation_form_question_id = ?", "#{current_student.id}", "#{question_form.object[:id]}"]).first.answer %>
<%= answer_form.hidden_field :user_id, :value => current_user.id%>
<%end%>
<%end%>
<div class="actions">
<%= f.submit "Submit Evaluation Form" %>
</div>
<% end %>
</div>
</div>
My controller code:
def answer
#evaluation_form.questions.each do |question|
question.evaluation_form_answers.build
end
end
Relationships between models involved:
Question: has_many :evaluation_form_answers
accepts_nested_attributes_for :evaluation_form_answers, allow_destroy: true
EvaluationFormAnswer : belongs_to :evaluation_form_question
Screenshot of problem:
Try this.
Controller
def answer
#evaluation_form.questions.each do |question|
#answer_form = question.evaluation_form_answers.build
end
end
View
<%= question_form.fields_for :evaluation_form_answers, #answer_form do |answer_form| %>
Check this railscasts episode Rails Casts Nested Model Form episode 196

create new object in rails3 using multimodel form

I am n00b as rails is concerned. I am trying yo create a single multimodel form in my first rails3 project. Details are given below:
class Item < ActiveRecord::Base
# attr_accessible :title, :body
has_many :item_reviews, :dependent => :destroy
accepts_nested_attributes_for :item_reviews
end
and
class ItemReview < ActiveRecord::Base
# attr_accessible :title, :body
belongs_to :item
end
So as clear, an item can have multiple reviews but when I am creating an item, I want at least 1 review for it. So I want to get item and first review in single form while item creation.
I am using following view:
<%provide(:title,'Create')%>
<h1> Add an Item review</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for (#item) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<% f.fields_for :item_reviews, #item.item_reviews do |ff| %>
<%= ff.label :shop_address %>
<%= ff.text_field :shop_address %>
<% end %>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
<% f.fields_for :item_reviews, #item.item_reviews do |ff| %> will not work because there is not item_review associated with #item currently (#item = Item.new) Until I save #item, I can't create new item_review. What should I do in that case.
I know one possibility is model independent form but can't I use something above to make life easy.
PS: I am using bootstrap, just in case if that helps.
There is some way to achieve an instance with item reviews. The key is to create an instance with some of nested instances without actual saving
#item = Item.new
#item.item_reviews.build
and then in your form
form_for #item do |f|
...
f.fields_for :item_reviews do |ff|
with this code an instance of review is present and you can render form

Resources