Can't mass-assign protected attributes: _destroy - ruby-on-rails

I'm getting this really weird error when I submit my nested form.
Can't mass-assign protected attributes: _destroy
Any idea why this may be? It's a bit of a concern as I'm having to remove the 'destroy' hidden_field with javascript temporarily until i figure out what it is, meaning i can't delete anything!
_form.html.erb
<%= nested_form_for(#post, :html=> {:multipart => true, :class=> "new_blog_post", :id=> "new_blog_post"}) do |f| %>
<%= field do %>
<%= f.text_field :title, placeholder: "Give your post a title", :class=>"span12" %>
<% end %>
<%= field do %>
<%= f.text_area :body, placeholder: "Write something here...", :id=>"blog-text", :class=>"span12" %>
<% end %>
<%= f.label :search_locations, "Add locations to your post" %>
<%= text_field_tag :name,"",:class=>"localename", :id=>"appendedInput", :placeholder=> "Name of the location", :autocomplete => "off" %>
<%= f.link_to_add "Add a location", :locations %>
<%= actions do %>
<%= f.submit "Submit", :class=>"btn", :disable_with => 'Uploading Image...' %>
<% end end%>
_posts_controller.rb_
class PostsController < ::Blogit::ApplicationController
...
def new
#post = current_blogger.blog_posts.new(params[:post])
#location = #post.locations.build
end
def edit
#post = Post.find(params[:id])
##post = current_blogger.blog_posts.find(params[:id]) removed so any use can edit any post
#location = #post.locations.build
end
def create
location_set = params[:post].delete(:locations_attributes) unless params[:post][:locations_attributes].blank?
#post = current_blogger.blog_posts.new(params[:post])
#post.locations = Location.find_or_initialize_location_set(location_set) unless location_set.nil?
if #post.save
redirect_to #post, notice: 'Blog post was successfully created.'
else
render action: "new"
end
end
def update
#post = current_blogger.blog_posts.find(params[:id])
if #post.update_attributes(params[:post])
redirect_to #post, notice: 'Blog post was successfully updated.'
else
render action: "edit"
end
end
def destroy
#post = current_blogger.blog_posts.find(params[:id])
#post.destroy
redirect_to posts_url, notice: "Blog post was successfully destroyed."
end
location.rb
class Location < ActiveRecord::Base
after_save { |location| location.destroy if location.name.blank? }
has_many :location_post
has_many :posts, :through => :location_post
has_many :assets
attr_accessible :latitude, :longitude, :name, :post_id, :notes, :asset, :assets_attributes
accepts_nested_attributes_for :assets, :allow_destroy => true
include Rails.application.routes.url_helpers
def self.find_or_initialize_location_set(location_set)
locations = []
locations = locations.delete_if { |elem| elem.flatten.empty? }
location_set.each do |key, location|
locations << find_or_initialize_by_name(location)
end
locations
end
end
EDIT:
Snippet of rendered form in new.html.erb
<div class="row span locsearch">
<div class="input-append span3">
<input autocomplete="off" class="localename" id="appendedInput" name="name" placeholder="Name of the location" type="text" value="">
<span class="add-on"><input id="post_locations_attributes_0__destroy" name="post[locations_attributes][0][_destroy]" type="hidden" value="false"><i class="icon-trash"></i></span> </div>
<div class="latlong offset3 span4"> <p class="help-block">Enter the name of the town or city visited in this blog entry.</p>
</div>
<input class="LegNm" id="post_locations_attributes_0_name" name="post[locations_attributes][0][name]" type="hidden" value="Dresden">
<input class="long" id="post_locations_attributes_0_longitude" name="post[locations_attributes][0][longitude]" type="hidden" value="13.7372621">
<input class="lat" id="post_locations_attributes_0_latitude" name="post[locations_attributes][0][latitude]" type="hidden" value="51.0504088">
</div>
</div>
EDIT2:
post.rb
class Post < ActiveRecord::Base
require "acts-as-taggable-on"
require "kaminari"
acts_as_taggable
self.table_name = "blog_posts"
self.paginates_per Blogit.configuration.posts_per_page
# ==============
# = Attributes =
# ==============
attr_accessible :title, :body, :tag_list, :blogger_id, :coverphoto, :locations_attributes
# ===============
# = Photo Model =
# ===============
has_attached_file :coverphoto,
:styles => {
:coverbar => "600x300>", :medium => "250x250^" , :thumb => "100x100^"},
#:source_file_options => {:all => '-rotate "-90>"'},
:storage => :s3,
:s3_credentials => "#{Rails.root}/config/s3.yml",
:bucket => "backpackbug",
:path => "/:style/:id/:filename"
# ===============
# = Validations =
# ===============
validates :title, presence: true, length: { minimum: 6, maximum: 66 }
validates :body, presence: true, length: { minimum: 10 }
validates :blogger_id, presence: true
# =================
# = Associations =
# =================
belongs_to :blogger, :polymorphic => true
has_many :location_post
has_many :locations, :through => :location_post
belongs_to :profile
accepts_nested_attributes_for :locations, :allow_destroy => true, :reject_if => proc { |attributes| attributes['name'].blank? }
end end

This was solved with a combination of this and another answer, found here:
How can I fix this create function?
A short term fix is to add attr_accessible :_destroy and attr_accessor :_destroy.
Thanks both!

Add :_destroy to your attr_accessible list
attr_accessible ...., :_destroy

you should write to post.rb,
attr_accessible: locations_atributes
and
accepts_nested_attributes_for :locations, :allow_destroy => true
as you are updating the location object via post object.

replace f.hidden_field it must stay thus(line 2)
module ApplicationHelper
def link_to_remove_fields(name, f)
text_field_tag(:_destroy) + link_to_function(name, "remove_fields(this)")
end

Related

Rails 5 - n+1 query issue inside simple_form gem

I'm trying to resolve every n+1 issue with Bullet gem in my Rails, but I can't figure out this particular problem:
_form.html.erb
<%= simple_form_for([:admin, #course, #lesson]) do |f| %>
<div class="form-inputs">
<div class="col-md-6">
<%= f.input :start_at, minute_step: 5, start_year: Date.today.year %>
</div>
<div class="col-md-6">
<%= f.input :end_at, minute_step: 5, start_year: Date.today.year %>
</div>
<div class="col-md-6">
<!--Here is the problem-->
<%= f.association :room, :label_method => lambda { |room| "#{room.title} (#{room.building.title})"}%>
<!--Here is the problem-->
</div>
<div class="col-md-6">
<%= f.association :teacher, :label_method => lambda { |teacher| "#{teacher.first_name} #{teacher.last_name}"},
collection: #course.teachers%>
</div>
</div>
<div class="col-md-12">
<div class="form-actions text-center">
<%= f.submit class: "btn btn-primary btn-lg" %>
</div>
</div>
<% end %>
Form is rendered to new.html.erb and edit.html.erb. Problem occurs on line with room association where I'm trying to get room.building.title.
Here's the screenshoot:
As you can see, I only want to display room name with its centre name, but Bullet throws an error. Problem is that I'm using that lambda function so creating a variable in a controller doesn't help (or at least I don't know how to create it properly) since problem occurs inside select box.
Here I'm including my controller:
lessons_controller.rb
module Admin
class LessonsController < Admin::AdminController
helper_method :convert_time, :convert_day
before_action :set_course
before_action :set_lesson, only: [:show, :edit, :update, :destroy]
def index
#lessons = Lesson.includes(:teacher,:course, :room => :building).where(course_id: #course).paginate(page: params[:page], per_page: 10)
# #course_lessons = #course.lessons.paginate(page: params[:page], per_page: 10)
end
def new
##room = Room.all.includes(:building)
#lesson = Lesson.new
end
def create
#lesson = Lesson.new(lesson_params)
#lesson.course = #course
if #lesson.save
flash[:success] = "Lesson was created"
redirect_to admin_course_lessons_path(#course)
else
render 'new'
end
end
def edit
end
def update
if #lesson.update(lesson_params)
flash[:success] = "Lesson was updated"
redirect_to admin_course_lessons_path(#course)
else
render 'edit'
end
end
def show
end
def destroy
#lesson.destroy
flash[:danger] = "Lesson was deleted"
redirect_to admin_course_lessons_path(#course)
end
private
def set_course
#course = Course.find(params[:course_id])
end
def set_lesson
#lesson = Lesson.find(params[:id])
end
def lesson_params
params.require(:lesson).permit(:start_at,:end_at, :room_id, :teacher_id)
end
end
end
(Actions relevant for us in this situation are new and edit)
Here are the relevant models:
room.rb
class Room < ApplicationRecord
belongs_to :building
has_many :lessons, dependent: :destroy
before_save {self.title = title.upcase_first}
before_save {self.code = code.upcase}
validates :title, presence: true,
length: { minimum: 3, maximum: 50},
uniqueness: {case_sensitive: false}
validates :code, presence: true,
length: {minimum: 2},
uniqueness: {case_sensitive: false}
validates :building_id, presence: true
end
building.rb
class Building < ApplicationRecord
has_many :rooms, dependent: :destroy
before_save {self.title = title.upcase_first}
before_save {self.code = code.upcase}
validates :title, presence: true,
length: { minimum: 3, maximum: 50},
uniqueness: true
validates :code, presence: true,
length: {minimum: 3},
uniqueness: {case_sensitive: false}
end
Sorry for this long post and my lack of knowledge. I will appreciate any feedback.
You can specify the collection option using includes in the f.association. Like this:
f.association :room, :label_method => lambda { |room| "#{room.title} (#{room.building.title})", :collection => Room.includes(:building).all }

Relationship models in rails

Im associating models and im stuck i have my Restaurant controller
before_action :set_restaurant, only: [:show, :edit, :update, :destroy]
def new
#restaurant = Restaurant.new
#restaurant.build_chef
end
def create
#restaurant = Restaurant.new(restaurant_params)
respond_to do |format|
if #restaurant.save
format.html { redirect_to #restaurant, notice: 'Restaurant was successfully created.' }
format.json { render action: 'show', status: :created, location: #restaurant }
else
format.html { render action: 'new' }
format.json { render json: #restaurant.errors, status: :unprocessable_entity }
end
end
end
def set_restaurant
#restaurant = Restaurant.find(params[:id])
end
def restaurant_params
params.require(:restaurant).permit(:avatar, :name, :description,
chef_attributes: [ :avatar, :name ]
)
end
And this is my _form that im rendering in create.html.erb
<%= form_for(#restaurant, multipart: true) do |f| %>
<div class="field">
<%= f.label :restaurant_name %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<div class="field">
<%= f.label :image %>
<%= f.file_field :avatar %>
</div>
<%= f.fields_for :chef do |f| %>
<div class="field">
<%= f.label :chef_name %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :image %>
<%= f.file_field :avatar %>
</div>
<% end %>
<div class="actions">
<%= f.submit 'Add new restaurant' %>
<%= link_to 'Nevermind', restaurants_path, class: 'button' %>
</div>
<% end %>
then this is my Chef model
class Chef < ApplicationRecord
belongs_to :restaurant
validates :name, presence: true
validates :avatar,
attachment_content_type: { content_type: /\Aimage\/.*\Z/ },
attachment_size: { less_than: 5.megabytes }
has_attached_file :avatar, styles: {
thumb: '100x100>',
square: '200x200#',
medium: '300x300>'
}
end
and this is my Restaurant model
class Restaurant < ApplicationRecord
has_one :chef
accepts_nested_attributes_for :chef
validates :name, presence: true
validates :avatar,
attachment_content_type: { content_type: /\Aimage\/.*\Z/ },
attachment_size: { less_than: 5.megabytes }
has_attached_file :avatar, styles: {
thumb: '100x100>',
square: '200x200#',
medium: '300x300>'
}
end
My problem is that when i save a new restauran, nothing happens just reloads and stay in the form, i dont know whats happening im stuck here, probably its just a dumb thing to do but im learning, thanks for the support.
It looks like your problem is related to this question. In Rails 5, there is an implicit validation on belongs_to associations, so you need to add required: false in order to build it from nested attributes.
class Chef < ApplicationRecord
belongs_to :restaurant, required: false
...
end

Save successful on nested attributes on has many through but doesn't add to database

I and order and item system with Rails 4 with a has many through association. When I select create order the webpage says that the order was created successfully however the link is not made in the OrderItems linking tables meaning that the items relating to that order do not appear on the show page or edit page for an order.
The order is also linked to an employee. The current employee ID is linked to that order. I have just not been able to figure out how to add each item to the database.
p.s. I am using a gem called nested_form to handle all of the jQuery on the front end of dynamically adding and removing new items on the _form.html.erb for Orders.
orders_controller.rb
class OrdersController < ApplicationController
before_action :logged_in_employee, only:[:new, :show, :create, :edit, :update, :index]
before_action :admin_employee, only:[:destroy]
before_action :set_order, only: [:show, :edit, :update, :destroy]
def new
#order = Order.new
#items = Item.all
end
def edit
#items = Item.all
end
def create
#order = current_employee.orders.build(order_params)
if #order.save
flash[:success] = "Order successfully created"
redirect_to #order
else
render 'new'
end
end
def update
if #order.update_attributes(order_params)
flash[:success] = "Order updated!"
redirect_to current_employee
else
render 'edit'
end
end
....
private
def set_order
#order = Order.find(params[:id])
end
def order_params
params.require(:order).permit(:table_number, :number_of_customers, :status, :comment, order_items_attributes: [:id, :order_id, :item_id, :_destroy])
end
end
order.rb
class Order < ActiveRecord::Base
belongs_to :employee
has_many :order_items
has_many :items, :through => :order_items
default_scope { order('status DESC') }
validates :employee_id, presence: true
validates :table_number, numericality: { only_integer: true, greater_than: 0, less_than_or_equal_to: 20 }, presence: true
validates :number_of_customers, numericality: { only_integer: true, greater_than: 0, less_than_or_equal_to: 50 }, presence: true
validates :status, inclusion: { in: %w(Waiting Ready Closed), message: "%{value} is not a status" }
accepts_nested_attributes_for :order_items, :reject_if => lambda { |a| a[:item_id].blank? }
end
item.rb
class Item < ActiveRecord::Base
belongs_to :menu
has_many :order_items
has_many :orders, :through => :order_items
before_save { name.capitalize! }
VALID_PRICE_REGEX = /\A\d+(?:\.\d{0,2})?\z/
validates :name, presence: true, length: { maximum: 100 }
validates :price, format: { with: VALID_PRICE_REGEX }, numericality: { greater_than: 0, less_than_or_equal_to: 100}, presence: true
validates :course, inclusion: { in: %w(Starter Main Dessert Drink), message: "%{value} is not a course" }
validates :menu_id, presence: true
end
order_item.rb
class OrderItem < ActiveRecord::Base
belongs_to :item
belongs_to :order
validates :order_id, presence: true
validates :item_id, presence: true
end
orders/_form.html.erb
<% provide(:title, "#{header(#order)} #{#order.new_record? ? "order" : #order.id}") %>
<%= link_to "<< Back", :back, data: { confirm: back_message } %>
<h1><%= header(#order) %> <%= #order.new_record? ? "order" : #order.id %></h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= nested_form_for #order do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="row">
<div class="col-xs-6">
<%= f.label :table_number %>
<%= f.number_field :table_number, class: 'form-control' %>
</div>
<div class="col-xs-6">
<%= f.label :number_of_customers %>
<%= f.number_field :number_of_customers, class: 'form-control' %>
</div>
</div>
<%= f.fields_for :order_items do |oi| %>
<%= oi.grouped_collection_select :item_id, Menu.where(active: true).order(:name), :items, :name, :id, :name, { include_blank: 'Select Item' }, class: 'items_dropdown' %>
<%= oi.hidden_field :item_id %>
<%= oi.hidden_field :order_id %>
<%= oi.link_to_remove "Remove item" %>
<% end %>
<p><%= f.link_to_add "Add an item", :order_items %></p>
<br>
<% if !#order.new_record? %>
<%= f.label "Status" %>
<%= f.select(:status, options_for_select([['Waiting', 'Waiting'], ['Ready', 'Ready'], ['Closed', 'Closed']], #order.status), class: 'form-control') %>
<% end %>
<%= f.label "Comments - how would you like your steak cooked? Or feedback" %>
<%= f.text_area :comment, size: "20x5", class: 'form-control' %>
<%= f.submit submit_label(#order), class: "btn btn-success col-md-6" %>
<% end %>
<% if !#order.new_record? && current_employee.try(:admin?) %>
<%= link_to "Cancel", :back, data: { confirm: back_message }, class: "btn btn-warning col-md-offset-1 col-md-2" %>
<%= link_to "Delete", #order, method: :delete, data: { confirm: "Are you sure? The employee will be deleted! "}, class: "btn btn-danger col-md-offset-1 col-md-2" %>
<% else %>
<%= link_to "Cancel", :back, data: { confirm: back_message }, class: "btn btn-warning col-md-offset-1 col-md-5" %>
<% end %>
</div>
</div>
Issue: Let's say your order has got many items and you want the items to be saved just when the order is created.
order = Order.new(order_params)
item = Item.new(name: 'item-name', product_info: 'etc etc')
if order.save
item.create
order.items << item
end
You need to follow similar approach in your case. just get the item_params properly and apply above rule.
Try below approach, hope that helps. :)
def create
#order = current_employee.orders.build(order_params)
if #order.save
item = params["order"]["order_items_attributes"]
#please debug above one and try to get your items from params.
order_item = Item.create(item)
#makes sure above item hold all item attributes then create them first
#order.items << item
redirect_to #order
end
end

Rails and devise - assign group when user signs up

I have an app with multiple plans. For the "team" plan, the user will enter a team name and license count. When the user is created, a new team will be saved. I think I have the database part correct but I am confused with how to create the team. More specifically, my questions are: 1) How do I add the team fields to the view? Do I use form_for and nest that inside the user form? 2) How do I handle the team creation in the controller? Thanks for any help.
migration:
class CreateTeams < ActiveRecord::Migration
def change
create_table :teams do |t|
t.string :team_name
t.integer :license_count
t.timestamps
end
add_column :users, :team_id, :integer, :null => true
add_column :users, :team_admin, :boolean
end
end
controllers:
class RegistrationsController < Devise::RegistrationsController
def new
return if plan_missing
build_resource({})
self.resource.plan = params[:plan]
respond_with self.resource
end
protected
#after user has signed up
def after_inactive_sign_up_path_for(resource)
new_user_session_path
end
#after user has updated their profile, keep them on the same page
def after_update_path_for(resource)
edit_user_registration_path
end
private
def plan_missing
if params[:plan].nil?
redirect_to plans_path
true
else
false
end
end
def log
logger.debug #user.to_yaml
logger.debug params.to_yaml
end
end
models:
class User < ActiveRecord::Base
include KeyCreator
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable, :trackable, :validatable
belongs_to :teams
has_many :document_users
has_many :documents, :through => :document_users
has_many :user_billing_events, dependent: :destroy
before_create :before_create
before_destroy :before_destroy
after_destroy :after_destroy
#scopes for activeadmin
scope :new_users, -> { where("created_at >= ? and created_at <= ?", Date.today.beginning_of_day.utc, Date.today.end_of_day.utc) }
scope :admin, -> { where(:admin => true) }
validates_inclusion_of :plan, in: %w( basic pro ), message: "Plan is not valid"
attr_accessor :stripe_token, :coupon, :to_skip
def is_admin?
self.admin
end
def paid_plan
["pro", "team"].include? self.plan
end
def self.save_event(customer_id, event_type)
user = User.find_by(customer_id: customer_id)
UserBillingEvent.create!(user: user, event_type: event_type)
end
def update_card(user)
c = Stripe::Customer.retrieve(customer_id)
c.card = user.stripe_token
c.coupon = user.coupon unless user.coupon.blank?
c.save
self.customer_id = c.id
self.last_4_digits = c.cards.data.first["last4"]
self.stripe_token = nil
self.save!
true
rescue Stripe::StripeError => e
logger.error "Stripe Error: " + e.message
errors.add :base, "#{e.message}."
self.stripe_token = nil
false
end
def update_plan(user)
self.stripe_token = user.stripe_token
self.coupon = user.coupon
if self.paid_plan && !user.paid_plan
cancel_stripe
end
if !self.paid_plan && user.paid_plan
setup_stripe
end
self.plan = user.plan
self.save!
true
end
def before_create
self.api_key = create_key
self.admin = false
setup_stripe if self.paid_plan
end
def before_destroy
cancel_stripe if self.paid_plan
end
def after_destroy
self.documents.where(:document_users => {:role => "owner"}).destroy_all
self.document_users.destroy_all
end
private
def setup_stripe
return if to_skip && to_skip.include?("stripe")
logger.debug '-- setting up stripe... --'
raise "Stripe token not present. Can't create account." if !stripe_token.present?
if coupon.blank?
customer = Stripe::Customer.create(:email => self.email, :description => self.name, :card => self.stripe_token, :plan => self.plan)
else
customer = Stripe::Customer.create(:email => self.email, :description => self.name, :card => self.stripe_token, :plan => self.plan, :coupon => self.coupon)
end
self.last_4_digits = customer.cards.data.first["last4"]
self.customer_id = customer.id
self.stripe_token = nil
customer
rescue Stripe::StripeError => e
logger.error "Stripe Error: " + e.message
errors.add :base, "#{e.message}."
self.stripe_token = nil
nil #return nil
end
def cancel_stripe
return if to_skip && to_skip.include?("stripe")
logger.debug '-- cancelling stripe... --'
customer = Stripe::Customer.retrieve(customer_id)
return false if customer.nil?
subscription = customer.subscriptions.data[0]
customer.cancel_subscription if subscription.status == 'active'
self.last_4_digits = nil
self.customer_id = nil
self.stripe_token = nil
true
rescue Stripe::StripeError => e
logger.error "Stripe Error: " + e.message
errors.add :base, "#{e.message}."
self.stripe_token = nil
false
end
def trace
logger.debug '-- tracing self in user.rb'
logger.debug self.to_yaml
logger.debug '--------------------------'
end
end
class Team < ActiveRecord::Base
has_many :users
end
sign up view:
<% content_for :head do %>
<%= tag :meta, :name => "stripe-key", :content => ENV["STRIPE_PUBLIC_KEY"] %>
<%= javascript_include_tag "https://js.stripe.com/v2/" %>
<%= javascript_include_tag "stripe/application", "data-turbolinks-track" => true %>
<% end %>
<div class="container">
<h2>Sign up for <%= #user.plan.titleize %></h2>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name),
:html => {:role => "form", class: #user.paid_plan ? "card-form" : ""} ) do |f| %>
<%= devise_error_messages! %>
<%= f.hidden_field :plan, :value => #user.plan %>
<% if #user.plan == "team" %>
<h3 class="vpad-top5">Team Administrator - User Account</h3>
<% end %>
<div class="form-group">
<label for="name">Display Name</label>
<%= f.text_field :name, :autofocus => true, :class =>"form-control" %>
</div>
<div class="form-group">
<label for="email">Email</label>
<%= f.email_field :email, :class =>"form-control" %>
</div>
<div class="form-group">
<label for="password">Password</label>
<%= f.password_field :password, :class =>"form-control" %>
</div>
<div class="form-group">
<label for="password_confirmation">Password confirmation</label>
<%= f.password_field :password_confirmation, :class =>"form-control" %>
</div>
<% if #user.plan == "team" %>
<h3 class="vpad-top5">Team Information</h3>
<!-- I need to add fields here! -->
<% end %>
<% if #user.paid_plan %>
<h3 class="vpad-top5">Payment Information</h3>
<%= render :partial => "shared/payment_fields", :locals => {:f => f} %>
<% end %>
<%= f.submit "Sign up",:class=>'btn btn-primary' %>
<% end %>
<%= render "devise/shared/links" %>
</div>
Use Rolify gem to manage user roles.
https://github.com/RolifyCommunity/rolify
You can set a default role on user Sign Up.

Nested forms with three models through another one?

I have trying to create a nested form with three models associated with through in one.
This is the schema:
These are the models:
../app/models/alimento.rb:
class Alimento < ActiveRecord::Base
attr_accessible :calorias, :nome, :refeicaos_attributes
validates :nome, :calorias, :presence => { :message => "nao pode ficar em branco" }
has_many :controles, :dependent => :destroy
has_many :refeicaos, through: :controles
has_many :diarios, through: :controles
accepts_nested_attributes_for :controles
end
../app/models/refeicao.rb:
class Refeicao < ActiveRecord::Base
attr_accessible :nome, :alimentos_attributes, :controles_attributes, :diario_attributes
validates :nome, :presence => { :message => "nao pode ficar em branco" }
has_many :controles, dependent: :destroy
has_many :alimentos, through: :controles
has_many :diarios, through: :controles
accepts_nested_attributes_for :controles
end
../app/models/diario.rb:
class Diario < ActiveRecord::Base
has_many :controles, dependent: :destroy
has_many :refeicaos, through: :controles
has_many :alimentos, through: :controles
accepts_nested_attributes_for :controles
attr_accessible :data, :controles_attributes, :refeicaos_attributes, :alimentos_attributes
end
../app/models/controle.rb:
class Controle < ActiveRecord::Base
belongs_to :alimento
belongs_to :refeicao
belongs_to :diario
attr_accessible :quantidade, :alimento_id, :refeicao_id, :diario_id
accepts_nested_attributes_for :alimento
accepts_nested_attributes_for :refeicao
accepts_nested_attributes_for :diario
end
I created many alimentos (food) and refeições (meals), now I need create a
daily control of diet through the model Diario that can contain many alimentos and refeições through the Controle (control) model.
../app/controllers/diarios_controller.rb:
class DiariosController < ApplicationController
# GET /diarios
# GET /diarios.json
def index
#diarios = Diario.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #diarios }
end
end
# GET /diarios/1
# GET /diarios/1.json
def show
#diario = Diario.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #diario }
end
end
# GET /diarios/new
# GET /diarios/new.json
def new
#diario = Diario.new
end
# GET /diarios/1/edit
def edit
#diario = Diario.find(params[:id])
end
# POST /diarios
# POST /diarios.json
def create
#diario = Diario.new(params[:diario])
respond_to do |format|
if #diario.save
format.html { redirect_to #diario, notice: 'Diario was successfully created.' }
format.json { render json: #diario, status: :created, location: #diario }
else
format.html { render action: "new" }
format.json { render json: #diario.errors, status: :unprocessable_entity }
end
end
end
# PUT /diarios/1
# PUT /diarios/1.json
def update
#diario = Diario.find(params[:id])
respond_to do |format|
if #diario.update_attributes(params[:diario])
format.html { redirect_to #diario, notice: 'Diario was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #diario.errors, status: :unprocessable_entity }
end
end
end
# DELETE /diarios/1
# DELETE /diarios/1.json
def destroy
#diario = Diario.find(params[:id])
#diario.destroy
respond_to do |format|
format.html { redirect_to diarios_url }
format.json { head :no_content }
end
end
end
I'm trying to do the "Nested Model Form (revised)" example.
Views:
../app/views/diarios/new.html.erb:
<%= form_for #diario do |f| %>
<div class="field">
<%= f.label :data %>
<%= f.date_select :data %>
<br>
</div>
<%= f.fields_for :refeicaos do |builder| %>
<%= render 'refeicao_fields', f: builder %>
<% end %>
<%= link_to_add_fields "Adicionar Refeição", f, :refeicaos %>
</br>
</br>
<div class="actions">
<%= f.submit "Cadastrar Controle" %>
</div>
<% end %>
</br>
<%= link_to 'Voltar', root_path %>
../app/views/diarios/_refeicao_fields.html.erb:
<fieldset>
<strong>Refeição: </strong></br>
<%= f.label :nome, "Nome da Refeição", :style => 'margin-left: 5px;' %>
<%= collection_select(:refeicao, :id, Refeicao.order(:nome), :id, :nome) %>
<%= f.check_box :_destroy %>
<%= f.label :_destroy, "Remover Refeição" %>
</br>
</br>
<strong>Alimentos:</strong></br>
<%= f.fields_for :alimentos do |builder| %>
<%= render 'alimento_fields', f: builder %>
<% end %>
<%= link_to_add_fields "Adicionar Refeição", f, :alimentos %>
</fieldset>
../app/views/diarios/_alimento_fields.html.erb:
<fieldset>
<%= f.label :alimento, "Nome do Alimento:" %>
<%= collection_select(:alimento, :id, Alimento.order(:nome), :id, :nome) %>
<%= f.hidden_field :_destroy %>
<%= f.fields_for :controles do |builder| %>
<%= render 'controle_fields', f: builder %>
<% end %>
<%= link_to "Remover alimento", '#', class: "remove_fields" %></br>
</fieldset>
../app/views/diarios/_controle_fields.html.erb:
<fieldset>
<%= f.label :alimento, "Quantidade:", :style => 'margin-left: 42px;' %>
<%= f.number_field :quantidade, :style => 'width: 50px;' %>
</fieldset>
Custom helper created:
module ApplicationHelper
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
end
I get this error:
NoMethodError in Diarios#new
Showing D:/aplicacoes_indie/AppDieta/app/views/diarios/_refeicao_fields.html.erb where line #13 raised:
undefined method `alimentos' for nil:NilClass
Extracted source (around line #13):
10: <%= f.fields_for :alimentos do |builder| %>
11: <%= render 'alimento_fields', f: builder %>
12: <% end %>
13: <%= link_to_add_fields "Adicionar Refeição", f, :alimentos %>
14: </fieldset>
I've trying a lot of ways, but I can't solve this. The idea is: A Diario > with 1 or more refeicoes > with 1 or more alimentos and save each association through controle model.
Where am I wrong?
EDITED*
thanks for the answer, now the form appears
But should appear the number field 'quantidade' from controle below the select from alimento like this:
http://uploaddeimagens.com.br/images/000/123/917/full/model2.jpg?1385480010
../app/view/diarios/_alimento_fields.html.erb
<fieldset>
<%= f.label :alimento, "Nome do Alimento:" %>
<%= collection_select(:alimento, :id, Alimento.order(:nome), :id, :nome) %>
<%= f.hidden_field :_destroy %>
<%= f.fields_for :controles do |builder| %>
<%= render 'controle_fields', f: builder %>
<% end %>
<%= link_to "Remover alimento", '#', class: "remove_fields" %></br>
</fieldset>
../app/view/diarios/_controle_fields.html.erb
<fieldset>
<%= f.label :controle, "Quantidade:", :style => 'margin-left: 42px;' %>
<%= f.number_field :quantidade, :style => 'width: 50px;' %>
</fieldset>
One more thing, how can i mount the create action of Diario to get all the values of selects of alimentos and refeicaos and save each in controle model? Because I save and only create a Diario object with none association.
../app/controllers/diarios_controller.rb action create
def create
#diario = Diario.new(params[:diario])
respond_to do |format|
if #diario.save
format.html { redirect_to #diario, notice: 'Diario was successfully created.' }
format.json { render json: #diario, status: :created, location: #diario }
else
format.html { render action: "new" }
format.json { render json: #diario.errors, status: :unprocessable_entity }
end
end
end
I think these are the only accepts_nested_attributes_for statements that you should need:
class Diario < ActiveRecord::Base
accepts_nested_attributes_for :refeicaos
And:
class Refeicao < ActiveRecord::Base
accepts_nested_attributes_for :alimentos
Right now the <%= f.fields_for :refeicaos do |builder| %> statement is setting up the builder but builder.object is nil.
Edit - Changed field names to plural forms since this was a has_many relationship.
try:
class Alimento < ActiveRecord::Base
attr_accessible :calorias, :nome, :refeicaos_attributes
validates :nome, :calorias, :presence => { :message => "nao pode ficar em branco" }
has_many :controles, :dependent => :destroy
has_many :refeicaos, through: :controles
has_many :diarios, through: :controles
accepts_nested_attributes_for :controles
end
class Refeicao < ActiveRecord::Base
attr_accessible :nome, :alimentos_attributes, :controles_attributes, :diario_attributes
validates :nome, :presence => { :message => "nao pode ficar em branco" }
has_many :controles, dependent: :destroy
has_many :alimentos, through: :controles
has_many :diarios, through: :controles
accepts_nested_attributes_for :alimentos
end
class Diario < ActiveRecord::Base
has_many :controles, dependent: :destroy
has_many :refeicaos, through: :controles
has_many :alimentos, through: :controles
accepts_nested_attributes_for :refaicoas
attr_accessible :data, :controles_attributes, :refeicaos_attributes, :alimentos_attributes
end
class Controle < ActiveRecord::Base
belongs_to :alimento
belongs_to :refeicao
belongs_to :diario
attr_accessible :quantidade, :alimento_id, :refeicao_id, :diario_id
end

Resources