Correct usage of simple_form collection_check_boxes - ruby-on-rails

I have set up 2 models as following :
class Sector < ActiveRecord::Base
attr_accessible :summary, :title, :sector_ids
belongs_to :platform
end
and
class Platform < ActiveRecord::Base
attr_accessible :name, :url
has_many :sectors
end
and a form which tries to use example from here as follows :
<%= simple_form_for #platform, :html => { :class => 'form-vertical'} do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<div class="row-fluid">
<div class="span12">
<div class="span6">
<%= field_set_tag "General" do %>
<%= f.input :name %>
<%= f.input :url %>
<%= f.collection_check_boxes :sector_ids, Sector.all, :title, :title %>
<% end %>
</div>
</div>
</div>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
nevertheless, after I try to submit the form I get the following error :
Can't mass-assign protected attributes: sector_ids
What am I missing here? I successfully migrated the database after adding appropriate associations, but it seems like Rails doesnt really know what to do now wit the sector ids that are selected.
Solution :
class Sector < ActiveRecord::Base
attr_accessible :summary, :title
belongs_to :platforms
end
and
class Platform < ActiveRecord::Base
attr_accessible :name, :url, :platform_attributes, :sector_ids
has_many :sectors
end
and in the view :
<%= f.association :sectors, :as => :check_boxes %>
Ofcourse, do not forget to run "rake db"migrate", in case you havent done it yet. I was also required to restart the server in order for the changes to apply.
I hope this helps someone.

Try adding { :sector_ids => [] } to your Platform permits list.
https://github.com/rails/strong_parameters#nested-parameters

First I think you should remove :sectors_ids from the attr_accessible of Sector and try.
If the above doesn't works (which probably may not work), see the documentation of simple_form, specially read https://github.com/plataformatec/simple_form#associations and https://github.com/plataformatec/simple_form#collection-check-boxes; the last one makes what you are trying to do, but, in the associations they use has_and_belongs_to_many associations.
Finally, check this out https://github.com/plataformatec/simple_form/issues/341
Update
The solution is to make a has_and_belongs_to_many relationship, there is no way out, because, you want to associate some sectors to one platform, but, you want to associate the same sectors to other platforms, otherwise you won't need checkbox, only nested forms to keep adding new records. You have to make this change in your database design and structure.

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.

Adding empty record for nested attribute in 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 %>
...

collection_select not filtering (in nested_form w/nested resources)

I've got an application that uses nested resources (see routes.rb below) to completely segregate users. It works great until I use collection_select to allow users to select objects from other models. For example, if I visit the store index view as user A, I only see stores created by user A. However, if I visit the store_group view and try to select a store to add to the group from the collection_select menu under the fields_for :store_group_details, I see all stores created by all users.
As far as I can tell, the problem might happen because there is no filter for stores in the store_group controller. store_group_details doesn't have a controller, but from what I've read, that seems to be correct since the model can only be accessed through a nested form in the store_group view. I have another situation where another view for another resource has several collection_select menus for selecting objects from other models, and all of those have the same problem (they display all objects in that model, regardless of which user created them).
How can I filter the objects shown in the collection_select menus? Is it a problem with what I'm passing into collection_select, or is it because the controllers don't do anything to filter the other models before those models' objects are displayed? I've looked at the docs for collection_select, and couldn't make it work based on that.
Thanks for your help, I've spent quite a bit of time trying to get this to work.
user.rb
class User < ActiveRecord::Base
has_many :store_groups
has_many :stores
has_many :store_group_details
end
store.rb
class Store < ActiveRecord::Base
belongs_to :user
has_many :store_group_details
has_many :store_groups, :through => :store_group_details
end
store_group.rb
class StoreGroup < ActiveRecord::Base
belongs_to :user
has_many :store_group_details, :inverse_of => :store_group
has_many :stores, :through => :store_group_details
accepts_nested_attributes_for :store_group_details
attr_accessible :store_group_details_attributes
end
store_group_detail.rb
class StoreGroupDetail < ActiveRecord::Base
belongs_to :store
belongs_to :store_group
belongs_to :user
attr_accessible :store_id
delegate :store_name, :to => :store
end
_store_group_form.html.erb
<div class="container">
<div class="span8">
<%= nested_form_for([#user, #store_group]) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label "Store Group Name (required)" %>
<%= f.text_field :store_group_name %>
<%= f.label "Store Group Description" %>
<%= f.text_area :store_group_description %>
<%= f.fields_for :store_group_details %>
<p><%= f.link_to_add "Add store to group", :store_group_details %></p>
<br>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
_store_group_detail_fields.html.erb
<p>
<%= f.label "Select Store:" %>
<%= f.collection_select :store_id, Store.order(:store_name),
:id, :store_name, include_blank: true %>
<%= f.link_to_remove "remove" %>
</p>
routes.rb
resources :users do
resources :stores
resources :store_groups
resources :store_group_details
end
There must be a problem with your controller. Did you ever solve this issue?
Look at this answer as it might lead to a solution. Or add you stores_controller.rb.

Nested model form not submitting everything

I heard about this community while listening to Hypercritical and I am excited to join in with my first question. I am working on my first rails App and I have run into an issue that I cannot seem to crack. I have been watching Railscast, Lynda.com, and Googling for days but I still cannot comprehend how to create a form that that will update my has_many :through associations at once. Allow me to try an explain what I am doing.
My Goal:
The firm I work for provides many "Service Offerings" and I want to be able to create a new service offering on one page and have it create the contacts and other information that is associated with it. The additional information such as "contacts" will live in their own tables because they may need to be referenced by many "Service Offerings."
Problem:
When I submit the form the "Service Offering" fields submit and are entered into the database, but the fields for the "Business Developer" do not. Obviously, I would like everything to be entered into its appropriate table and the for the IDs to be linked in the join table. I would really appreciate any insight that you could provide.
What I Have So Far: What you see below is Service Offerings and Business Developers. Eventually I will be adding Contacts, Photos, and Files but I thought I would start simply and work my way up.
Models:
class ServiceOffering < ActiveRecord::Base
attr_accessible :name, :description
has_many :business_developer_service_offerings
has_many :business_developers, :through => :business_developer_service_offerings
accepts_nested_attributes_for :business_developer_service_offerings
end
class BusinessDeveloper < ActiveRecord::Base
attr_accessible :first_name, :last_name
has_many :business_developer_service_offerings
has_many :service_offerings, :through => :business_developer_service_offerings
end
class BusinessDeveloperServiceOffering < ActiveRecord::Base
belongs_to :business_developer
belongs_to :service_offering
end
Controller:
def new
#service_offering = ServiceOffering.new
#service_offering.business_developers.build
end
def create
#service_offering = ServiceOffering.new(params[:service_offering])
if #service_offering.save
redirect_to(:action => 'list')
else
render('new')
end
end
View:
<%= form_for((#service_offering), :url => {:action => 'create'}) do |f|%>
<p>
<%= f.label :name%>
<%= f.text_field :name %>
<%= f.label :description%>
<%= f.text_field :description %>
</p>
<%= f.fields_for :business_developer do |builder| %>
<p>
<%= builder.label :first_name%>
<%= builder.text_field :first_name %>
<%= builder.label :last_name%>
<%= builder.text_field :last_name %>
</p>
<%end%>
<%= f.submit "Submit" %>
<%end%>
I figured it out. It turns out a few things were wrong and needed to be changed in addition to the two suggestions #Delba made.
The Form:
I took a look at RailsCasts #196 again and noticed that my form looked different than the one used there, so I tried to match it up:
<%= form_for #service_offering do |f|%>
<p>
<%= f.label :name%>
<%= f.text_field :name %>
<%= f.label :description %>
<%= f.text_field :description %>
</p>
<%= f.fields_for :business_developers do |builder| %>
<p>
<%= builder.label :first_name %>
<%= builder.text_field :first_name %>
<%= builder.label :last_name %>
<%= builder.text_field :last_name %>
</p>
<%end%>
<%= f.submit "Submit" %>
<%end%>
Initially, this presented an error:
undefined method `service_offerings_path'
Routes:
This lead me to learn about RESTful Routes because I was using the old routing style:
match ':controller(/:action(/:id(.:format)))'
So I updated my routes to the new RESTful Routes style:
get "service_offerings/list"
resource :service_offerings
resource :business_developers
attr_accessible:
That got the form visible but it was still not working. So I did some searching around on this site and found this post that talked about adding "something_attributes" to your parent objects model under attr_accessible. So I did:
class ServiceOffering < ActiveRecord::Base
has_many :business_developer_service_offerings
has_many :business_developers, :through => :business_developer_service_offerings
accepts_nested_attributes_for :business_developers
attr_accessible :name, :description, :business_developers_attributes
end
That change along with #Delba's suggestion shown in the above model and controller listed below solved it.
def new
#service_offering = ServiceOffering.new
#business_developer = #service_offering.business_developers.build
end
You just forgot to assign #business_developper.
def new
#service_offering = ServiceOffering.new
#business_developper = #service_offering.business_developpers.build
end
-
#business_developer = #service_offering.business_developers.build
initializes an instance of biz_dev which is then available in the view.
fields_for :biz_dev isn't really tied to this instance but to the many-to-many relationship btw serv_off and biz_dev.
In this way, you can add multiple input for additional biz_dev if you initialize another biz_dev instance in your controller. For instance:
5.times { #service_offering.biz_dev.build }
will add additional fields in your form without you having to declare them in your view.
I hope it helped.

get association values in rails forms

I'm building an app where a User has tasks and a task has a location.
The tasks and locations are in a nested_form using formtastic_cocoon, which is the formtastic gem with a jQuery extension.
The location.address field is an autocomplete text field searching on addresses that already exist in the database. So when the user selects the address, a hidden location_id in the form is populated.
What I am trying to do is that when the user goes to edit the task, I want it to display the currently selected address, but I don't see anywhere that I can retrieve that value. I can get the location_id, as that is in the task model, but I can't seem to get the associated location.address.
the models are
class Task < ActiveRecord::Base
attr_accessible :user_id, :date, :description, :location_id
belongs_to :user
has_one :location
end
class Location < ActiveRecord::Base
attr_accessible :address, :city, :state, :zip
has_many :tasks
end
then in my form, I have
<div class="nested-fields">
<%= link_to_remove_association "remove task", f %>
<div class="searchAddress">
< input type="text" value="HERE IS WHERE I WANT TO SHOW THE ADDRESS" >
</div>
<%= f.hidden_field :location_id %>
<%= f.inputs :date, description %>
</div>
----------- edited to include all formtastic code ---------------
form.html.erb
<%= semantic_form_for #user, :html=>{:multipart=true} do |form| %>
<%= form.inputs :username, :photo %>
<%= form.semantic_fields_for :tasks do |builder | %>
<%= render 'task_fields', :f => builder %>
<% end %>
<% end %>
------- end edit --------------
I have tried outputing different manner of 'f', but don't see any reference to the associated locations, yet if I debug User.Task[0].Location outside of the form, I get the correct location details. How do I get that inside the form??
--------- update ------------
getting a bit closer on this. It turns out I can output
<%= debug f.object %>
I get the task object returned. Unfortunately it does not include the location object, just the value of the location_id field.
Did you try #task.location.address ?
Your approach is right. But there is some slide miss-conception declaring association. I'ld like to change as below:
# app/models/task.rb
class Task < ActiveRecord::Base
attr_accessible :user_id, :location_id, :date, :description
belongs_to :user
belongs_to :location
end
As we are storing location foreign key in the tasks table, it's better to declare belongs_to association here.
# app/models/location.rb
class Location < ActiveRecord::Base
attr_accessible :address, :city, :state, :zip
has_many :tasks
# this method will return full address of a location
def full_address
[address, city, state, zip].reject(&:blank?).join(", ")
end
end
Then you need to add the full_address of a selected location.
# app/views/users/_task_fields.html.erb
<div class="nested-fields">
<%= link_to_remove_association "remove task", f %>
<div class="searchAddress">
<input type="text" value="<%= f.object.location&.full_address %>">
</div>
<%= f.hidden_field :location_id %>
<%= f.inputs :date, description %>
</div>

Resources