Adding empty record for nested attribute in rails - ruby-on-rails

I'm working in Rails 4/Ruby 2.0.0. I have a two models - Articles and Graphics. Articles has_many Graphics. So, in my code I am trying to add an empty record to the graphics collection on the article so that in the form, there will be an empty set of fields to let a new record be added. I cannot figure out why the fields do not show up on the form though.
I've tried multiple methods of building the graphics collection but none seem to do the trick. Surely I must be missing something insanely small.
Article.rb
class Article < ActiveRecord::Base
has_many :graphics, :dependent => :destroy, :foreign_key => 'article_id'
accepts_nested_attributes_for :graphics,
:allow_destroy => true,
:reject_if => :all_blank
end
Graphic.rb
class Graphic < ActiveRecord::Base
belongs_to :article
validates_presence_of :path, :caption
end
_form.html.erb
...
<% f.fields_for :graphics do |g| %>
<div class="clear clearfix pad-b-20">
<div class="w-1-2 left f-left">
<div class="field">
<%= g.label :path %><br>
<%= g.text_field :path %>
</div>
</div>
<div class="w-1-2 left f-left">
<div class="field">
<%= g.label :caption %><br>
<%= g.text_field :caption %>
</div>
</div>
</div>
<% end %>
...
Building it in a form helper method
articles/_form.html.erb
<%= form_for(setup_article(#article)) do |f| %>
form_helper.rb
module FormHelper
def setup_article(article)
article.graphics.build
article
end
end
Using an ActiveRecord callback
Article.rb
...
after_initialize :build_graphics
private
def build_graphics
self.graphics.build
end
Building it in the controller
ArticleController.rb
...
def new
#article = Article.new
#article.graphics.build
end
...

The problem is that both for form_for and for fields_for you need to use <%=, because they render the contents of the form.
So, to solve your problem, you need to write
...
<%= f.fields_for :graphics do |g| %>
Your content
<% end %>
...

Related

Strong params issue in rails 6

I have a many-to-many model ProductCategory product_category (joint-table) and
I'm having issue with nesting the parameter in the ProductsController. The error I keep getting is that its unpermitted params category_ids but I have nested it in the strong product params.
I took a picture of the important parts of the code. Please take a look and let me know thank you. Here is the most important part of the code I think:
<%= form_with(model: [:user, #product], local: true) do |f|%>
<h4>Category</h4>
<div class="dropdown-trigger btn">
<%= f.collection_select(:category_ids, Category.all, :id, :name) %>
</div>
<h4>Product Name:</h4>
<%= f.text_field :name %><br/>
<h4>Product Price:</h4>
<%= f.number_field :price, value: #product.price ? '%.2f' % #product.price : nil, min: 0, step: 0.01 %>$<br/>
<h4>Product Description:</h4>
<%= f.text_field :description %><br/>
<h4>Product Image (recommended)</h4>
<%= f.file_field :image %><br/>
The require in ProductsController:
def product_params
params.require(:product).permit(:name, :price, :description, :image, category_ids: [])
end
And the relevant parts of Product and ProductCategory model.
class Product < ApplicationRecord
belongs_to :user
has_many :product_categories
has_many :categories, though: :product_categories
has_one_attached :image
end
class ProductCategory < ApplicationRecord
belongs_to :product
belongs_to :category
end
class Category < ApplicationRecord
belongs_to :user
has_many :product_categories
has_many :products, though: :product_categories
end
code screenshot
You are receiving an "unpermitted params category_ids" error, because you first need to declare in your Product model the following:
accepts_nested_attributes_for :categories , allow_destroy: true
Once that is done, you should start receiving all the category_ids info, really nested inside your params.
However, I fully recommend DO NOT perform on your views and partials an ActiveRecord query over your DB. For example:
<div class="dropdown-trigger btn">
<%= f.collection_select(:category_ids, Category.all, :id, :name) %>
</div>
That is not advisable. Instead, you should receive from your controller the whole set of categories. The only function on the view in this case is to fill the data by the user, to select the categories, and then after a submit to send all that information back to the controller. That's all. Not performing any kind of query. It's true that you can do it. I mean, it is physically possible to do it there on that view, or even to do it on a helper (also wrong, a helper is to perform additional actions over resources already loaded or received from controllers), but MVC means the separation of duties for several reasons.
Anyway, in your case I would choose to go more or less with something like this:
On products_controller.rb:
def edit
#categories_to_assign = product_service.get_categories_to_assign(#product)
end
def product_service
ProductService
end
def product_params
params.require(:product).permit(:name, :price, :description, :image, categories_to_assign: [])
end
On product_service.rb it gets the categories:
def self.get_categories_to_assign(product)
categories_scope.where.not(id: product.categories.map(&:id)).map do |category|
["#{category.name}", category.id]
end
end
def self.categories_scope()
Category
end
Then on the edit/new view:
<%
categories_to_assign = #categories_to_assign || []
%>
<% content_for :products_main_content do %>
<div id="edit_product_content">
<%= render partial: 'products/form', locals: {
product: product,
return_to: return_to,
categories_to_assign: categories_to_assign
} %>
</div>
<% end %>
Then on the _form.html.erb partial:
<%
categories_to_assign = local_assigns.fetch(:categories_to_assign, [])
%>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title"><%= t('products.categories.title') %></h2>
</div>
<div class="panel-body">
<div class="form-horizontal" id="categories_container" data-sjr-placeholder>
<%= render partial: 'products/categories', locals: {f: f, categories_to_assign: categories_to_assign} %>
</div>
</div>
</div>
And finally on the _categories.html.erb partial:
<%
categories_to_assign = local_assigns.fetch(:categories_to_assign, [])
%>
<% if categories_to_assign.present? %>
<%= select_tag "#{f.object_name}[categories_to_assign][]", options_for_select(categories_to_assign), {id: "#{f.object_name}_categories_to_assign", include_blank: true, multiple: true, class: 'form-control', data: {placeholder: t('products.form.select_category')}} %>
<% end %>
As you can see, the general idea is passing the pertinent information from the controller, after been properly retrieved on the product_service (you should add it), and then it goes to the edit/new view and then it finally goes down into the nested partials. That way everything is separated in its respective areas of responsibilities.

Unable to add rows to join table in rails 5 app

I have three tables - result, feedback_qs, and feedback_qs_results (join table). Given an existing result and feedback_qs, I want to populate the join table with the result_id, feedback_q_id and the subsequent answer (text_area).
I believe the relationships are correct....
# result.rb
has_many :feedback_q_results
has_many :feedback_qs, :through => :feedback_q_results
# feedback_q.rb
has_many :feedback_q_results
has_many :results, :through => :feedback_q_results
# feedback_q_result.rb
belongs_to :result
belongs_to :feedback_q
I have access to the result_id (result/result.id/feedback) via config/routes.rb below...
resources :results do
member do
get 'feedback'
post 'feedback_create'
end
end
What would this form look like to populate the join table? Something similar to this (I realize this isn't correct)...? How do I take in the data through the post_controller to populate the join table? Or should it go through another controller?
<%= form_for :result, url: feedback_create_result_path(result), method: :post do |form| %>
<% for q in FeedbackQ.all %>
<div class="col field">
<p>
<%= form.label q.question_text %>
</p>
<%= text_area_tag id: q.id %>
</div>
<% end %>
<div class="actions">
<%= form.submit "Submit", class: "btn btn-primary" %>
</div>
<% end %>
What would my feedback_create method look like? While I've seen several questions regarding rails' many to many relationships and populating join tables, none of those use cases seems to suit my need. Thanks in advance.
Update - My use case is this: user takes an exam (called a result), and they then review the proctor by answering feedback questions. I want to record the feedback in the join table
If your has_many associations are working correctly, then in the rails console you should be able to do:
> result.feedback_qs
=> []
and
> feedback_q.results
=> []
Where result and feedback_q are are Result and FeedbackQ objects, respectively.
To associate a feedback_q to a result, simply:
> result.feedback_qs << feedback_q
This should create a record in the feedback_q_result table.
Now, when you do
> result.feedback_qs
It should return the feedback_q record that you just added.
I altered my original models. One issue I ran into was simple naming convention in rails, so I simplified the table names to eliminate that from the equation. New models:
# result.rb
has_many :feedbacks
has_many :questions, :through => :feedbacks
# question.rb
has_many :feedbacks
has_many :results, :through => :feedbacks
# feedback.rb
belongs_to :question
belongs_to :result
validates_uniqueness_of :question_id, scope: :result_id
form in my view:
<%= form_for :result, url: feedback_create_result_path(result), method: :post do |form| %>
<% Question.all.each do |question| %>
<div class="col field">
<p>
<%= form.label :question, question.question_text %>
</p>
<%= hidden_field_tag 'question[][id]', question.id %>
<%= text_area_tag 'question[][answer]', "", id: "question_" + question.id.to_s, class: "stretch_textarea" %>
</div>
<% end %>
<div class="actions">
<%= form.submit "Submit", class: "btn btn-primary" %>
</div>
This SO page helped quite a bit as well.

unpermitted attributes even after whitelisting in rails 5

project details -> rails 5.1 and ruby 2.4.1
I'm trying to create a simple todo app. my problem is with nested model forms.
if i create a project without any task and i save it. then if i want to edit the project and add some tasks, tasks fields are not showing up. If i add tasks while creating the projects everything works as expected.in edit page i can see both project and tasks and i can edit as well.
below are my 2 models. I didnt use nested routes. just using the nested model forms.
class Project < ApplicationRecord
has_many :tasks, inverse_of: :project, dependent: :destroy
validates :name, presence: :true
validates :description, presence: :true
accepts_nested_attributes_for :tasks, reject_if: proc { |attributes| attributes[:name].blank? }
end
class Task < ApplicationRecord
belongs_to :project, inverse_of: :tasks
end
Below is my _form partial for new and edit.
<%= form_for #project do |form| %>
<% if #project.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#project.errors.count, "error") %> prohibited this project from being saved:</h2>
<ul>
<% #project.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name, id: :project_name %>
</div>
<div class="field">
<%= form.label :description %>
<%= form.text_field :description, id: :project_description %>
</div>
<%= form.fields_for :tasks do |task_form| %>
<div class="field">
<%= task_form.label :task_name %>
<%= task_form.text_field :name, id: :task_name %>
</div>
<div class="field">
<%= task_form.label :task_description %>
<%= task_form.text_field :description, id: :task_description %>
</div>
<% end %>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
white listing of the project controller is given below.
def project_params
params.require(:project).permit(:name, :description, tasks_attributes: [:id, :name, :description])
end
Any help is appreciated
-Ajith
if i create a project without any task and i save it. then if i want to edit the project and add some tasks, tasks fields are not showing up.
... is how it is actually supposed to be. So your problem most likely is not related to Strong Parameters problem, as I do not see any obvious problems in your view file and your project_params method. Now since you don't want that this behaviour, you'll probably want something like the following which builds up 1 or more task objects by default if there's no task object associated to theproject yet, so that there will always be at least one group of task fields when you're creating or editing a project.
app/controllers/projects_controller.rb
class ProjectsController < ApplicationController
def new
#project = Project.new
#project.tasks.build
# the above code just above is the same as below
# #project.tasks << Task.new
# now you can call this again like below, if you want 2 groups of `tasks` fields when you're creating a Project
#project.tasks.build
end
def edit
#project = Project.find(params[:id])
# this project may not yet have a `Task` object associated to it; if so we build a `Task` object like so
# this builds 3 `Task` objects; you can just use one below if you just want to show one in your edit form.
if #project.tasks.count == 0
#project.tasks.build
#project.tasks.build
#project.tasks.build
end
end
end
P.S. you may be interested in cocoon gem if you want to have a form that can build dynamic nested attributes automatically for you (like for example you want 2 buttons like the following) '(Add more task)' and '(Remove this task)`

How to update child record in nested form in rails

How would I update a collection of child 'Product' rows when submitting the form below.
Many thanks
class User < ActiveRecord::Base
has_many :products, :class_name => 'Product', :inverse_of => :user
accepts_nested_attributes_for :products
end
class Product < ActiveRecord::Base
belongs_to :user, :inverse_of => :products
end
The View
<%= form_for(#user) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<%= f.fields_for :products do |builder| %>
<% builder.text_field :name %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Here is somewhat minimal update action:
# app/controller/users_controller.rb
def update
# Get the user in param
#user = User.find(params[:id])
# Update attributes
if #user.update_attribute(params[:user])
# Redirect to :show for this user.
redirect_to :show
else
# update_attriubtes failed. Render the edit view.
render :edit
end
end
Secondly, you also need to ensure the attributes can be mass assigned such that the call to #user.update_attributes succeeds. Since you've not tagged your question with the appropriate Rails version, following is what you can do for Rails 3 and Rails 4:
Rails 3 - Define each attribute you want to mass assign as attr_accessible
Rails 4 - Permit parameters in controllers params.require(:user).permit(...).
Note that in your view you are not outputting the result of the helper text_field on this line:
<% builder.text_field :name %>
You need to use <%=...%> (note the equals = sign) in order to output the result of expressions within <%=...%>. This might have just been a typo here as you've done this correctly on all other elements. Replace the line with:
<%= builder.text_field :name %>
Also, suggest reading the "Action Controller Overview" of guides for details on Controllers.

Rails - Display Nested Attributes in an "Update" Form

I am having a bit of difficulty getting my "update form" to display the nested attributes. Specifically, images (e.g., "choices") to display. All other data fields are showing. Just not this is my form:
<%= bootstrap_form_for #template, :url => {:action => 'update', :id => #template.id } do |f| %>
<fieldset>
<legend>Update Your Template</legend>
<%= f.text_field :prompt, :class => :span6, :placeholder => "Which one is running?", :autocomplete => :off %>
<%= f.select 'group_id', options_from_collection_for_select(#groups, 'id', 'name', selected: #template.group.id) %>
<div class="row-fluid">
<ul class="thumbnails">
<%= f.fields_for :template_assignments do |builder| %>
<li class="span3" id="">
<div class="thumbnail">
<%= builder.text_field :choice_id %>
<%= image_tag #template.template_assignments.builder.choice.image %>
</div>
</li>
<% end %>
</ul>
</div>
<% end %>
The main line I am having trouble with is:
<%= image_tag #template.template_assignments.builder.choice.image %>
I cannot get it to iterate through each of the 4 nested attributes for the image. It iterates through the 4 nested attributes pertaining to :choice_id, which displays correctly in the text_field.
If i change it to:
<%= image_tag #template.template_assignments.first.choice.image %>, it displays the first image no problem.
However, I need it to iterate and display the "first", "second", "third", and "fourth" images.
Any help on how to display these images, just as the image_id's are being displayed?
EDIT:
Here are my models
# app/models/template.rb
class Template < ActiveRecord::Base
belongs_to :group
has_many :template_assignments, dependent: :destroy
has_many :choices, :through => :template_assignments
accepts_nested_attributes_for :template_assignments, allow_destroy: true
end
# app/models/template_assignment.rb
class TemplateAssignment < ActiveRecord::Base
belongs_to :template
belongs_to :choice
end
# app/models/choice.rb
class Choice < ActiveRecord::Base
has_many :template_assignments
has_many :templates, :through => :template_assignments
end
You'll probably want to just use builder directly, just like you're doing in the text_field.
<%= image_tag builder.choice.image %>
[UPDATE] after some trial and error the correct form would be :
<%= image_tag builder.object.choice.image %>
What's happening is that when f.fields_for :template_assignments do |builder| is used to render the nested items, the builder object that is yielded to the block is not the object itself (in this case a TemplateAssignment), but is a FormBuilder object, which is what supplies the convenience methods like builder.text_field. (If you tried to do template_assignment.text_field you'd get an error.) The builder stores the object that it is representing in the form as object, so you can get a hold of your template_assignment object by using builder.object. From there you can deal the the template_assignment like normal. I hope that helps.

Resources