fail to manage nested attributes - ruby-on-rails

I've stuck managing nested attributes. I'm a new guy to rails and would be very grateful for any advise or pointing on my mistakes. So I have a model Ticket like this( it also has enum integer field status, references stuff_id body and so on )
class Ticket < ActiveRecord::Base
include Generator
has_paper_trail only:[:stuff_id, :status], on:[:update]
has_many :replies
accepts_nested_attributes_for :replies
end
Also I have Reply model
class Reply < ActiveRecord::Base
belongs_to :ticket
validates :ticket_id, :body, presence: true
end
My target is to give an opportunity for stuff to create a reply for a ticket and optionally change status for a ticket. Currently I try to manage it this way:
routes.rb
resources :tickets do
patch 'stuff_update', on: :member
resources :replies
end
dashboards_controller.rb
class DashboardsController < ApplicationController
#before_action :authorize
before_action :method
def opened
end
protected
def method
#tickets = Ticket.send(action_name.to_sym).includes(:replies)
end
def ticket_params
params.require(:ticket).permit(:status, replies_attributes: [:body])
end
end
tickets_controller.rb
class TicketsController < ApplicationController
#before_action :authorize, only:[:stuff_update]
before_action :load_ticket, only:[:show, :update, :stuff_update]
respond_to :js
def stuff_update
#ticket.update(ticket_params)
end
protected
def ticket_params
params.require(:ticket).permit(:name, :subject, :email, :status, :body, :department, :stuff_id, replies_attributes: [:id, :body])
end
def load_ticket
#ticket = Ticket.find(params[:id])
end
end
And finally view
-#tickets.each do |t|
.panel.panel-default
p=t.subject
=form_for t,{ url: "/tickets/#{t.id}/stuff_update"}, {method: :patch} do |f|
= f.label :status, class: 'label_hidden'
= f.select :status, Ticket.statuses.keys, {}, {class:'form-control'}
= f.fields_for t.replies.build do |ff|
= ff.label :body
= ff.text_field :body
= f.submit "Submit"
I've got an error for unpermitted parameter reply. Also it seems to me there are plenty mistakes in my code besides this error. I desperately need help. Please help me to get through this.

Ok, the solution I've found is pretty simple
-#tickets.each do |t|
-t.replies.build
.panel.panel-default
p=t.subject
=form_for t, url: "/tickets/#{t.id}/stuff_update", method: :patch do |f|
p
= f.label :status, class: 'label_hidden'
= f.select :status, Ticket.statuses.keys, {}, {class:'form-control'}
p
= f.fields_for :replies do |ff|
= ff.label :body
= ff.text_field :body
= f.submit "Submit"
everything else is pretty identic.

Related

has_one nested association nullifies foreign key on edit route

I am building a nested form in ruby on rails.
The addition of a nested has_one association works fine. However, when I load the edit page, the foreign key company_id of the nested association is nullified.
I have tried update_only: true in accepts_nested_attributes_for and including :id in strong params as suggested in other similar answered questions on stackoverflow but nothing works for me.
Could anyone tell me what is actually causing the nested association to update and nullify its foreign key itself? My codes are as shown below. Thanks!
# company.rb
class Company < ApplicationRecord
has_one :mission
accepts_nested_attributes_for :mission, update_only: true
end
# mission.rb
class Mission < ApplicationRecord
belongs_to :company, optional: true
validates :description, presence: true, length: { maximum: 100 }
end
# companies_controller.rb
class CompaniesController < ApplicationController
def edit
#company = Company.find(params[:id])
#company.build_mission if #company.build_mission.nil?
end
def update
#company = Company.find(params[:id])
#company.assign_attributes(company_params)
if #company.valid?
#company.save
redirect_to companies_path
end
end
private
def company_params
params.require(:company).permit(mission_attributes: [:id, :description, :_destroy])
end
end
# edit.html.erb
<%= form_for #company, :url => company_path(#company), :html => {class: 'ui form', method: :put} do |f| %>
<%= f.fields_for :mission do |mission| %>
<div class="field">
<%= mission.label :mission %>
<%= mission.text_field :description %>
</div>
<% end %>
<%= f.button :submit => "", class: "ui button" %>
<% end %>
Hey I manage to solve the problem after a good sleep. Turns out i just have to play around with the if else condition at the companies controller level. The edit method should be amended to such:-
def edit
#company = Company.find(params[:id])
if #company.mission
else
#company.build_mission
end
end

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")

"Create" Action for nested resource with multiple Associations in Rails

I have items, that each have multiple (threaded) comments.
The threading is done via a parent key, that points towards another comment.
I just can't get the create action to work properly, I got it far enough to submit into the database, but it didn't have item_id and parent_id set.
I tried form_for [#item, #comment] instead of form_for :comment, url: item_comments_path( #item ) but it didn't help either.
When I look at comment_params in the create action I get this:
Parameters: {"utf8"=>"✓", "comment"=>{"body"=>"examplecommentgoeshere"}, "parent_id"=>"4", "commit"=>"Create Comment", "item_id"=>"4"}
Can anyone help?
Models:
class Item < ActiveRecord::Base
belongs_to :user
has_many :comments, dependent: :destroy
has_many :images, as: :item,
dependent: :destroy
validates :title, presence: true,
allow_blank: false
validates :description, presence: true,
allow_blank: false
validates :user, presence: true,
allow_blank: false
validates :layout, presence: true
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :item
has_many :responses, class_name: "Comment",
foreign_key: "parent_id",
dependent: :destroy
has_one :image, as: :item,
dependent: :destroy
belongs_to :parent, class_name: "Comment"
validates :body, presence: true,
allow_blank: false,
length: { minimum: 10 }
end
Comment Controller:
class CommentsController < ApplicationController
before_filter :findParent
before_filter :find, only: [:update, :destroy, :show]
before_filter :is_logged_in?, only: [:create, :update, :destroy]
before_filter :has_permission?, only: [:update, :destroy]
def new
#comment = Comment.new
end
def create
logger.debug comment_params.inspect
#comment = current_user.comments.build(comment_params)
if #comment.save
redirect_to #item
else
logger.debug #comment.errors.inspect
session[:errorbj] = #comment
redirect_to #item
end
end
[...]
private
def comment_params
params.require(:comment).permit(:body, :parent)
end
private
def find
#comment = Comment.find(params[:id])
end
private
def findParent
#item = Item.find params[:item_id]
end
Item View:
<p>
<h3>Add a comment:</h3>
<%= render partial: 'comments/form', locals: { parent: #item } %>
</p>
Partial comments/form:
<%= form_for :comment, url: item_comments_path( #item ) do |f| %>
<p>
<%= f.label :body %><br>
<%= f.text_area :body %>
</p>
<p>
<%= f.label :image %><br>
<nope>
</p>
<%= hidden_field_tag :parent_id, parent.id %>
<p>
<%= f.submit %>
</p>
<% end %>
From another stackoverflow thread I concluded that the only way to create an object in a multi-association is to do something along the lines of this:
#comment = current_user.comments.build( comment_params.join( { parent_id: <parent.id> } ) )
Other stackoverflow posts however said not to do
#item = Item.new( item_params.join( { user_id: current_user.id } )
So is there really no better way?
i think there are a lot of errors in your code. i hope that i can put that into a form that might help you figure out the fix by yourself:
use form_for [#item, #comment]
findParent should be find_parent
findParent should set a #parent
findParent should find a Comment
the new action should initialize with a parent #comment = Comment.new parent: #parent
hidden_field_tag :parent_id, parent.id should be form.hidden_field :parent
the parent association should be set via comment_params
you don't need the Rails.logger statement, its all in logs/development.log
don't put objects into the session
why is there item and image?
learn more about debugging! http://nofail.de/2013/10/debugging-rails-applications-in-development/
It turns out this is much easier than it seemed.
This is how I ended up doing it:
class CommentsController < ApplicationController
before_filter :findParent, only: [:create]
[...]
def create
#comment = current_user.comments.build(comment_params)
#comment.item = #item
if #comment.save
redirect_to #item
else
session[:errorbj] = #comment
redirect_to #item
end
end

Add model/database association upon create

I have a model named Entry, which has many Categories. The page where I create/edit the Entry has checkboxes for every Category.
When I am editing an Entry, everything works okay. When I create the Entry, I get as an error for the #entry:
:entry_categories=>["is invalid"]
My thinking is that rails can't create the entry_categories because it doesn't know the id of the Entry ( which it shouldn't, it hasn't been assigned an id yet ).
I feel like this is a very common thing to try to do. I haven't been able to find an answer though. Here comes the code spamming, there must be something I am missing and hopefully some more experienced eyes can see it.
entry.rb
class Entry < ActiveRecord::Base
validates_presence_of :contents
validates_presence_of :title
has_many :entry_categories, dependent: :destroy
has_many :categories, through: :entry_categories
belongs_to :book
validates_presence_of :book
end
entry_category.rb
class EntryCategory < ActiveRecord::Base
belongs_to :entry
belongs_to :category
validates_presence_of :entry
validates_presence_of :category
end
category.rb
class Category < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name
has_many :entry_categories, dependent: :destroy
has_many :entries, through: :entry_categories
end
entries_controller.rb
class EntriesController < ApplicationController
before_action :find_entry, only: [ :show, :edit, :update, :destroy ]
before_action :find_book, only: [ :new, :create, :index ]
before_action :authenticate_admin!, only: [:new, :create, :edit, :update, :destroy ]
def new
#entry = Entry.new
end
def create
#entry = #book.entries.new( entry_params )
if #entry.save
redirect_to entry_path( #entry ), notice: 'Entry Created'
else
render :new
end
end
def show
#categories = Category.joins( :entry_categories ).where( "entry_categories.entry_id = #{#entry.id} " ).select( "name, categories.id " )
#category_class = #categories.first.name.downcase.gsub( / /, '_' ) if #categories.any?
end
def index
#entries = #book ? #book.entries : Entry.all
end
def edit
end
def update
if #entry.update( entry_params )
redirect_to entry_path( #entry ), notice: 'Entry Updated'
else
render :edit
end
end
def destroy
#book = #entry.book
#entry.destroy
redirect_to book_path( #book ) , notice: 'Entry Destroyed'
end
protected
def entry_params
params.require(:entry).permit( :title, :contents, :year, :month, :day, category_ids: [] )
end
def find_entry
#entry = Entry.find( params[:id] )
end
def find_book
#book = Book.find( params[ :book_id ] )
rescue
#book = nil
end
end
_form.html.erb
<%= form_for [ #book, #entry ] do | form | %>
<%= content_tag :p, title %>
<%= form.text_field :title, placeholder: 'Title', required: true %>
<%= form.number_field :year, placeholder: 'Year' %>
<%= form.number_field :month, placeholder: 'Month' %>
<%= form.number_field :day, placeholder: 'Day' %>
<%= form.text_area :contents %>
<fieldset>
<legend>Categories</legend>
<%= form.collection_check_boxes(:category_ids, Category.all, :id, :name ) %>
</fieldset>
<%= form.submit %>
<% end %>
So again, the entry_categories are invalid in the create method, in the update they are fine. It's the same html file.
There must be some way to tell rails to save the Entry before trying to save the EntryCategory?
Thanks.
I managed to get this working by taking the validations out of EntryCategory:
class EntryCategory < ActiveRecord::Base
belongs_to :entry
belongs_to :category
validates_presence_of :category
end
I'm not particularity happy about this solution, and would still appreciate other thoughts.
I think you can use any of the following approach:
You can use autosave functionality of Active Record association. By this, when you will save EntryCategory, it will automatically save Entry as well.
class EntryCategory < ActiveRecord::Base
belongs_to :entry , autosave: true
#rest of the code
end
You can also use before_save callback of active record. by this, whenever you will save EntryCategory, it will first call a specified method, than proceed with saving.
class EntryCategory < ActiveRecord::Base
before_save :save_associated_entries
#rest of the code
def save_associated_entries
# code to save associated entries here
end
end
Try this:
replace this code:
<%= form.collection_check_boxes(:category_ids, Category.all, :id, :name ) %>
with:
<% Category.all.order(name: :asc).each do |category| %>
<div>
<%= check_box_tag "entry[category_ids][]", category.id %>
<%= category.name %>
</div>
you can format it with fieldset instead of div

Rails has_many through form with checkboxes and extra field in the join model

I'm trying to solve a pretty common (as I thought) task.
There're three models:
class Product < ActiveRecord::Base
validates :name, presence: true
has_many :categorizations
has_many :categories, :through => :categorizations
accepts_nested_attributes_for :categorizations
end
class Categorization < ActiveRecord::Base
belongs_to :product
belongs_to :category
validates :description, presence: true # note the additional field here
end
class Category < ActiveRecord::Base
validates :name, presence: true
end
My problems begin when it comes to Product new/edit form.
When creating a product I need to check categories (via checkboxes) which it belongs to. I know it can be done by creating checkboxes with name like 'product[category_ids][]'. But I also need to enter a description for each of checked relations which will be stored in the join model (Categorization).
I saw those beautiful Railscasts on complex forms, habtm checkboxes, etc. I've been searching StackOverflow hardly. But I haven't succeeded.
I found one post which describes almost exactly the same problem as mine. And the last answer makes some sense to me (looks like it is the right way to go). But it's not actually working well (i.e. if validation fails). I want categories to be displayed always in the same order (in new/edit forms; before/after validation) and checkboxes to stay where they were if validation fails, etc.
Any thougts appreciated.
I'm new to Rails (switching from CakePHP) so please be patient and write as detailed as possible. Please point me in the right way!
Thank you. : )
Looks like I figured it out! Here's what I got:
My models:
class Product < ActiveRecord::Base
has_many :categorizations, dependent: :destroy
has_many :categories, through: :categorizations
accepts_nested_attributes_for :categorizations, allow_destroy: true
validates :name, presence: true
def initialized_categorizations # this is the key method
[].tap do |o|
Category.all.each do |category|
if c = categorizations.find { |c| c.category_id == category.id }
o << c.tap { |c| c.enable ||= true }
else
o << Categorization.new(category: category)
end
end
end
end
end
class Category < ActiveRecord::Base
has_many :categorizations, dependent: :destroy
has_many :products, through: :categorizations
validates :name, presence: true
end
class Categorization < ActiveRecord::Base
belongs_to :product
belongs_to :category
validates :description, presence: true
attr_accessor :enable # nice little thingy here
end
The form:
<%= form_for(#product) do |f| %>
...
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<%= f.fields_for :categorizations, #product.initialized_categorizations do |builder| %>
<% category = builder.object.category %>
<%= builder.hidden_field :category_id %>
<div class="field">
<%= builder.label :enable, category.name %>
<%= builder.check_box :enable %>
</div>
<div class="field">
<%= builder.label :description %><br />
<%= builder.text_field :description %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
And the controller:
class ProductsController < ApplicationController
# use `before_action` instead of `before_filter` if you are using rails 5+ and above, because `before_filter` has been deprecated/removed in those versions of rails.
before_filter :process_categorizations_attrs, only: [:create, :update]
def process_categorizations_attrs
params[:product][:categorizations_attributes].values.each do |cat_attr|
cat_attr[:_destroy] = true if cat_attr[:enable] != '1'
end
end
...
# all the rest is a standard scaffolded code
end
From the first glance it works just fine. I hope it won't break somehow.. :)
Thanks all. Special thanks to Sandip Ransing for participating in the discussion. I hope it will be useful for somebody like me.
use accepts_nested_attributes_for to insert into intermediate table i.e. categorizations
view form will look like -
# make sure to build product categorizations at controller level if not already
class ProductsController < ApplicationController
before_filter :build_product, :only => [:new]
before_filter :load_product, :only => [:edit]
before_filter :build_or_load_categorization, :only => [:new, :edit]
def create
#product.attributes = params[:product]
if #product.save
flash[:success] = I18n.t('product.create.success')
redirect_to :action => :index
else
render_with_categorization(:new)
end
end
def update
#product.attributes = params[:product]
if #product.save
flash[:success] = I18n.t('product.update.success')
redirect_to :action => :index
else
render_with_categorization(:edit)
end
end
private
def build_product
#product = Product.new
end
def load_product
#product = Product.find_by_id(params[:id])
#product || invalid_url
end
def build_or_load_categorization
Category.where('id not in (?)', #product.categories).each do |c|
#product.categorizations.new(:category => c)
end
end
def render_with_categorization(template)
build_or_load_categorization
render :action => template
end
end
Inside view
= form_for #product do |f|
= f.fields_for :categorizations do |c|
%label= c.object.category.name
= c.check_box :category_id, {}, c.object.category_id, nil
%label Description
= c.text_field :description
I just did the following. It worked for me..
<%= f.label :category, "Category" %>
<%= f.select :category_ids, Category.order('name ASC').all.collect {|c| [c.name, c.id]}, {} %>

Resources