Rails nested association & multiple collection_select - ruby-on-rails

I'm fairly new to rails and I'm having some issues updating my association tables when using multiple select.
I have three tables, portrait portrait_tags and tags
(The tags stores the names of my tag names (traditional, inspirational, community etc))
My desired outcome is that the 'multiple select field' will add the tags to the portrait_tag table based on the Tag.all tag_id value. Currently this seems to insert only one field and the tag_id in the portrait_tag table is NULL, then when I return to the edit page the multiple select is duplicated.
Params
Parameters: {"utf8"=>"✓", "authenticity_token"=>"j+Obhq9u+mvOKYnj4+TAGy+be8s3AbZlMvuyKiot5iyKqjMyFAcs23PjbQjOTjwl6aRBx1M5lmYRZzTjOeDTJA==", "portrait"=>{"portrait_tags_attributes"=>{"0"=>{"tag_id"=>["", "1", "2"]}}}, "commit"=>"Save changes", "id"=>"72"}
Tag.rb
class Tag < ActiveRecord::Base
has_many :portraits, through: :portrait_tags
accepts_nested_attributes_for :portraits
end
Portrait.rb
class Portrait < ActiveRecord::Base
has_many :portrait_tags
has_many :tags, through: :portrait_tags
accepts_nested_attributes_for :portrait_tags
end
Portrait_tag.rb
class PortraitTag < ActiveRecord::Base
belongs_to :portrait
belongs_to :tag
end
Edit.html.haml
%h1 Edit Portrait
= form_for [:admin, #portraits], :html => { :method => :put } do |f|
- if flash[:system].present?
- flash[:system].each do |e|
%div= e
- if flash[:notice].present?
%div= flash[:notice]
= f.fields_for :portrait_tags do |a|
= a.collection_select :tag_id, Tag.all, :id, :name, {}, {multiple: true}
= f.submit "Save changes", class: "btn btn-primary"
PortraitController
class Admin::PortraitsController < ApplicationController
def edit
#portraits = Portrait.where(artist_id: 33, id: params[:id]).take
#portraits.portrait_tags.build
end
def update
#portrait = Portrait.where(artist_id: 33, id: params[:id]).take
if #portrait.update(portrait_params)
p portrait_params
else
flash[:system] = #portrait.errors.full_messages
p #portrait.errors.full_messages
render :edit
end
end
private
def portrait_params
# Permit our attributes
params.require(:portrait).permit(:id, portrait_tags_attributes: [:id, :tag_id => [] ])
end
end
portrait_tags table
+----+-------------+--------+
| id | portrait_id | tag_id |
+----+-------------+--------+
portraits table
+----+-----------+--------------+
| id | artist_id | artist_image |
+----+-----------+--------------+
tags table
+----+-----------+--------------------+
| id | name | portrait_tag_id |
+----+-----------+--------------------+

<%= collection_select(:portrait_tag, :tag_ids,
Tag.all(:order=>"name ASC"),
:id, :name, {:selected => #portraits.tag_ids, :include_blank => true}, {:multiple => true}) %>
Hope this will work for you.

Related

Rails collection on form wanting extra (Tenant ID) field

I have a dilemma...I have put together a multi-tenant app and I'm having an issue with one form and action. The form (Profile) has a collection select where one selects the skills applicable. There is a many-to-many with Profile <- Taggings -> Tags. The Tag records appear in the collection for select, with multi-select enabled.
When you select some skills and save the profile record, in the profile update action, Rails throws:
Validation failed: Tenant must exist
This seems to be coming from the Taggings table, as when I remove the relationship with Tenant, no error and the profile record saves successfully. If I create a tagging record directly from Rails console, the tenant_id populates and the record saves.
respond_to do |format|
if #profile.update(profile_params) < fails here
format.html { redirect_to profiles_path, notice: 'Profile was successfully updated.' }
format.json { render :show, status: :ok, location: #profile }
else
Params (if I must insert tenant_id here, I expect within the tag id array, but how?):
{"utf8"=>"✓",
"_method"=>"patch",
"authenticity_token"=>"longobscurestring",
"profile"=>
{"description"=>"",
"tag_ids"=>
["",
"b39b38eb-a90b-434b-9457-1f3b67cee54e",
"08d90ee7-3194-4fec-acee-bcecfae1e8e7",
"ee8de96d-1206-4d73-bcf0-0b99f995569a",
"469ce954-b2bd-49d5-9dbc-0636b4da43c8",
"38b90691-d3f0-4c9d-8b5f-2c644a894d45",
"77a332d9-feed-4f88-8133-19066b5d33bc",
"05c145ce-a8ff-4105-a713-073da60184b5",
"8d6f98e3-9c3e-4f45-8c7d-36b177b557af"],
"contact_direct"=>"false"},
"commit"=>"Save",
"id"=>"1728fcc4-f2e2-49de-9a39-5c67502b8a85"}
Profile form:
<%= simple_form_for #profile, url: profile_path do |f| %>
<%= f.input :description, :as => :text, :input_html => {:cols=>100, :rows=>5} %>
<%= f.input :tag_ids, label: 'Skills', as: :select, collection: Tag.active.order(:name), label_method: :name, input_html: { multiple: true } %>
<div>
<span>People may contact me:</br></span>
<div class="radio-inline margin-10" style="text-align:left">
<%= f.input :contact_direct, label: '', as: :radio_buttons, collection: [['Directly', true], ['Through my manager', false]] %>
</div>
</div>
<br/>
<div class="actions">
<% if ( can? :manage, :all or current_user.id == #profile.user_id) %>
<%= f.button :submit, 'Save', class: "btn btn-success" %>
<% end %>
<%= link_to "Back", profiles_path(:search => {:column => 'First', :direction => 'Up', :name => ''}), class: "btn btn-primary link-as-button" %>
</div>
<% end %>
Profile model associations:
class Profile < ApplicationRecord
belongs_to :user
belongs_to :tenant
has_many :taggings, :dependent => :destroy
has_many :tags, through: :taggings
Taggings model associations:
class Tagging < ApplicationRecord
attribute :competence, :integer, default: 0
belongs_to :tenant
belongs_to :tag
belongs_to :profile, optional: true
has_one :user, through: :profile
has_many :endorsements, inverse_of: 'tagging'
has_many :endorsers, through: :endorsements
All tables have had RLS implemented through pg policies. Without tenant_id on the taggings record though, one can access the record through another tenant.
Please let me know anything else required here to debug. Thankyou in advance!
I managed to solve this by adding into the Tagging model an after_initialize callback to the following:
def set_tenant_id
if new_record?
self.tenant_id = self.tag.tenant_id
end
end
Feel free to say if this is bad practice...but I can't see any other way at present.

Simple form- select collection and set up has_many through

How to display products divided into three parts(three different filter like: product_1, product_2, product_3 ) and need choose only one product from each part
After submit. I should to save all that products for one order.
I have 4 tables:
Users
Orders
Order_details
Products
class Order < ActiveRecord::Base
has_many :order_details
belongs_to :user
has_many :products, through: :order_details
accepts_nested_attributes_for :order_details
end
class OrderDetail < ActiveRecord::Base
belongs_to :order
belongs_to :product
end
class Product < ActiveRecord::Base
has_many :order_details
has_many :orders, through: :order_details
def self.get_first_course
Product.where(product_type: "exem_product_1")
end
def self.get_main_course
Product.where(product_type: "exem_product_2")
end
def self.get_drink
Product.where(product_type: "exem_product_3")
end
end
I am not sure how to write strong params for that situation and how create that objects for save data.
class OrdersController < ApplicationController
before_action :authenticate_user!
def index
#order = Order.new
#I think need something like this..?!
##order.order_details.build
end
def create
end
private
def order_params
params.require(:order).permit(:date, :product_id => [])
end
end
You can do something like this in your controller:
class OrdersController < ApplicationController
before_action :authenticate_user!
def index
#order = Order.all
end
def new
#order = Order.new
end
def create
#order = current_user.orders.new(order_params)
if #order.save
#your actions here
else
#your actions to rescue error
end
end
private
def order_params
params.require(:order).permit(:date, :product_id => [])
end
end
And to use simple form for radio button collections, you have to do something like this:
= simple_form_for(#order, html: {:class => 'well form-horizontal', :method => :post, :action=> :create }) do |f|
.col-xs-12.col-sm-6.col-md-8
= render 'shared/error_messages', object: f.object
= f.collection_radio_buttons :product_ids, Product.get_first_course, :id, :product_name, :item_wrapper_class => 'inline'
%hr
= f.collection_radio_buttons :product_ids, Product.get_main_course, :id, :product_name, :item_wrapper_class => 'inline'
%hr
= f.collection_radio_buttons :product_ids, Product.get_drink, :id, :product_name,,:item_wrapper_class => 'inline'
%hr
= f.association :products, as: :radio_buttons
= f.button :submit, class: "btn btn-primary"
for select collection and get 3 different ids from form, that works for me..
post:
~ products_ids => {array ids}
= simple_form_for #order do |f|
= render 'shared/error_messages', object: f.object
= simple_fields_for :product_ids do |product|
= product.collection_select(nil, Product.get_first_course, :id, :product_name,
{prompt: "Select first course" },class: "form-control product-select")
= product.collection_select(nil, Product.get_main_course, :id, :product_name,
{prompt: "Select first course"},class: "form-control product-select")

Processing Many-To-Many Relationships with Collection Select

My goal is to add many-to-many relationships between the Viewer model and the Search model through ExlcudingViewers via a collection select field. No relationships are actually created when I submit the form. Why is that?
Search model:
class Search < ActiveRecord::Base
has_many :viewers, through: :excluding_viewers
has_many :excluding_viewers
...
def excluding_viewers_list
viewers.map(&:id)
end
def excluding_viewers_list=(ids)
self.viewers.clear
ids.each do |id|
viewer = Viewer.find(id)
self.viewers << viewer if viewer
end
end
...
end
Viewer model:
class Viewer < ActiveRecord::Base
has_many :excluding_viewers
has_many :searches, through: :excluding_viewers
end
ExcludingViewer model:
class ExcludingViewer < ActiveRecord::Base
belongs_to :viewer
belongs_to :search
end
And the form I'm using for the search:
<%= form_for #search do |f| %>
...
<%= f.collection_select :excluding_viewers_list,
Viewer.order(:name),
:id,
:name,
{},
multiple: true,
class: "chosen-select" %>
<%= f.submit "Search", class: "btn btn-primary search-cntrls" %>
<% end %>
The controller is working properly, and when I post the info from the form, the :excluding_viewers_list data is formatted like ["", "1", "3"] if I select viewers with the id of 1 and 3

Strange Ghost Object appearing

I currently have 3 Models, UserModel (Devise), an ArticleModel and an CommentModel.
I work with mongoid.
class Comment
include Mongoid::Document
field :body, type: String
field :created_at, type: Time, default: DateTime.now
belongs_to :article
belongs_to :user
end
class Article
include Mongoid::Document
field :title, type: String
field :body, type: String
field :created_at, type: DateTime, default: DateTime.now
has_many :comments
belongs_to :user
end
class User
include Mongoid::Document
has_many :articles
has_many :comments
end
my article/show.html.haml
.comments-form
.row
.span12
= form_for([#article, #article.comments.build]) do |f|
= f.text_area :body, :class => "span12", :rows => "3", :placeholder => "Your comment here..."
= f.submit nil, :class => "btn"
.comments
- #article.comments.each do |comment|
.comment{:id => "#{comment.id}"}
.row
.span2
= image_tag("260x180.gif", :class => "thumbnail")
.span10
= comment.body
my comments_controller
class CommentsController < ApplicationController
def new
#article = Article.find(params[:article_id])
#comment = #article.comments.create(params[:comment])
#comment.user = current_user
#comment.save
redirect_to article_path(#article, notice: "Comment posted.")
end
end
now in my articles/show appears an comment with an id but completely empty and i just can't figure out where this object comes from...
Your problem is with this line:
= form_for([#article, #article.comments.build]) do |f|
When you call #article.comments.build, you're buildling a new comment and adding it to #article.comments. Further down when you iterate over your comments, you're seeing the empty one you just created.
Instead of [#article, #article.comments.build], use [#article, Comment.new].

Rails3 Nested Attribute Validation and Control

I'm rolling with a legacy database unfortunately and am trying to build my rails3 app around it.
Thanks to this previous post, I've figured out where I'm going but still think I'm approaching incorrectly.
My basic problem is that main my main data is stored in a table with multiple rows, each with a different attribute value:
+-----+----------+----------------+----+---------------+------------+
| id | username | attribute_name | op | value | raduser_id |
+-----+----------+----------------+----+---------------+------------+
| 173 | jenny | User-Password | := | March 25 2011 | 33 |
| 172 | jenny | User-Password | := | 1234 | 33 |
+-----+----------+----------------+----+---------------+------------+
2 rows in set (0.00 sec)
I was using a nested form to enter this information but it's not really doing what I need. I can add the nested attributes and set a field thanks to the previous question now.
The issue I have is that I need some more control over my user's inputs. For instance, I need to restrict them to three distinct attributes:
User-Password, Expiration, Simultaneous-Use
I also need to validate the fields. I can't do so with the nested form.
My plan was to get the user to enter these in the parent model and propagate down but I do not have a clue how to do this and save out to separate rows, like I do with my nested atributes.
Can anyone shed any light on this?
--UPDATE--
raduser.rb
class Raduser < ActiveRecord::Base
has_many :radcheck, :dependent => :destroy
accepts_nested_attributes_for :radcheck, :reject_if => lambda { |a| a[:value].blank? }, :allow_destroy => true
end
radcheck.rb
class Radcheck < ActiveRecord::Base
set_table_name 'radcheck'
attr_accessible :attribute_name, :username, :value, :op, :groupname
belongs_to :raduser
has_many :radusergroup, :dependent => :destroy, :primary_key => :username, :foreign_key => :groupname
has_many :radgroupcheck, :through => :radusergroup
before_save :sync_usernames
private
def sync_usernames
self.username = self.raduser.username
end
end
Did you try placing the validations in the radcheck.rb model? Try this code:
radcheck.rb
class Radcheck < ActiveRecord::Base
set_table_name 'radcheck'
attr_accessible :attribute_name, :username, :value, :op, :groupname
belongs_to :raduser
validates :attribute_name, :inclusion => { :in => %w(User-Password Expiration Simultaneous-Use) }
before_save :sync_usernames
private
def sync_usernames
self.username = self.raduser.username
end
end
raduser.rb
class Raduser < ActiveRecord::Base
has_many :radcheck, :dependent => :destroy
accepts_nested_attributes_for :radcheck, :reject_if => lambda { |a| a[:value].blank? }, :allow_destroy => true
end
radusers_controller.rb
def new
#raduser = Raduser.new
#raduser.radcheck.build
end
def create
#raduser = Raduser.new(params[:raduser])
if #raduser.save
redirect_to(#raduser, :notice => 'Raduser was successfully created.')
else
render :action => "new"
end
end
and finally the form
<% if #raduser.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#raduser.errors.count, "error") %> prohibited this raduser from being saved:</h2>
<ul>
<% #raduser.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= form_for #raduser do |f| %>
<p>
<%= f.label :username %><br />
<%= f.text_field :username %>
</p>
<%= f.fields_for :radcheck do |builder| %>
<li>
<%= builder.label :attribute_name %>
<%= builder.text_field :attribute_name %>
</li>
<% end %>
<p><%= f.submit "Submit" %></p>
<% end %>
When I tried to save with attribute name other than User-Password, Expiration, Simultaneous-Use, it is giving
1 error prohibited this raduser from being saved:
- Attribute name is not included in the list
If you want to change the message, you can add :message to the validations. You can add other validations like this in the Radcheck model.
See these links RailsCasts, Complex form codes

Resources