PaperClip No handler found Error - ruby-on-rails

Hello I have 2 models Products and ProductImage. Product has_many ProductImages. I am using paperclip gem.
When i submit the Product Form i get this error.
No handler found for ["0", {"product_image"=>#<ActionDispatch::Http::UploadedFile:0x007f8cb78b29c8 #tempfile=#<Tempfile:/var/folders/yx/znmx6qfj0c507bvkym6lvhxh0000gn/T/RackMultipart20151221-46388-dqxfk7.jpg>, #original_filename="780069_black_l.jpg", #content_type="image/jpeg", #headers="Content-Disposition: form-data; name=\"product[product_images_attributes][0][product_image]\"; filename=\"780069_black_l.jpg\"\r\nContent-Type: image/jpeg\r\n">}]
def create_product_images
params["product"]["product_images_attributes"].each do |image|
ProductImage.create(product_image: image, product_id: #form.product.id)
end
end
Anyone know how to fix this? Im trying to create a ProductImage. So when i go product.product_images i have an array of all the images for the product.
Here is the form
<%= javascript_include_tag "custom" %>
<div class="container">
<div class=“row”>
<div class="col-md-6 col-md-offset-3">
<div class="panel panel-primary">
<div class="panel-body">
<%= simple_form_for #product do |f| %>
<%= f.fields_for :product_images do |product_image| %>
<% if product_image.object.new_record? %>
<%= product_image.file_field(:product_image) %>
<% else %>
<%= image_tag(product_image.url(:thumb)) %>
<%= product_image.hidden_field :_destroy %>
<%= product_image.link_to_remove "X" %>
<% end %>
<% end %>
<%= f.collection_select :category_id, #categories, :id, :name, include_blank: true, prompt: "Select One Category" %>
<% #categories.each do |category| %>
<div class='sizes_container' id ='sizes_container_for_<%= category.id %>'>
<% category.sizes.each do |size| %>
<%= label_tag "product_form[sizes_by_id][#{size.id}]", size.title %>
<%= text_field_tag "product_form[sizes_by_id][#{size.id}]" %>
<% end %>
</div>
<% end %>
<%= f.input :title, label:"Title"%>
<%= f.input :price, label:"Price"%>
<%= f.input :description,label:"Description" %>
<%= f.input :size_description, label:"Size Details"%>
<%= f.input :shipping_description, label:"Shipping Details"%>
<%= f.input :tag_list,label:"Tags - Seperate tags using comma ','. 5 tags allowed per product" %>
<%= f.button :submit, "Create new product", class: "btn-lg btn-success" %>
<% end %>
</div>
</div>
</div>
</div>
</div>
This is what the params look like
69: def create_product_images
70: params["product"]["product_images_attributes"].each do |image|
=> 71: binding.pry
72: ProductImage.create(product_image: image, product_id: #form.product.id)
73: end
74: end
[1] pry(#<ProductsController>)> params["product"]["product_images_attributes"]
=> {"0"=>
{"product_image"=>
#<ActionDispatch::Http::UploadedFile:0x007f8cacd89b28
#content_type="image/jpeg",
#headers="Content-Disposition: form-data; name=\"product[product_images_attributes][0][product_image]\"; filename=\"780069_black_l.jpg\"\r\nContent-Type: image/jpeg\r\n",
#original_filename="780069_black_l.jpg",
#tempfile=#<File:/var/folders/yx/znmx6qfj0c507bvkym6lvhxh0000gn/T/RackMultipart20151221-46388-d4pub9.jpg>>},
"1"=>
{"product_image"=>
#<ActionDispatch::Http::UploadedFile:0x007f8cacd897b8
#content_type="image/jpeg",
#headers="Content-Disposition: form-data; name=\"product[product_images_attributes][1][product_image]\"; filename=\"20090a.jpg\"\r\nContent-Type: image/jpeg\r\n",
#original_filename="20090a.
Product Controller
class ProductsController < ApplicationController
before_action :set_product, only: [:edit, :show, :update]
before_action :correct_user_edit, only: [:edit, :update, :destroy]
before_action :logged_in_user, only: [:new, :edit, :update, :destroy]
def index
#products = Product.all
end
def new
#product = Product.new
#categories = Category.preload(:sizes).order(:name)
#product.product_images.build
end
def home
#products = Product.paginate(page: params[:page])
end
def edit
end
def show
end
def update
if #product.update(product_params)
redirect_to #product
flash[:success] = 'Item was successfully updated.'
else
render "edit"
end
end
def create
#form = ProductForm.new(
product_image: product_params[:product_image],
title: product_params[:title],
price: product_params[:price],
size_description: product_params[:size_description],
shipping_description: product_params[:shipping_description],
description: product_params[:description],
tag_list: product_params[:tag_list],
category_id: product_params[:category_id],
sizes_by_id: product_params[:sizes_by_id],
user: current_user
)
if #form.save
create_product_images
redirect_to #form.product
flash[:success] = "You have created a new product"
else
flash[:danger] = "Your product didn't save"
new
render "new"
end
end
def destroy
#product.destroy
flash[:success] = "Product deleted"
redirect_to user_products_path
end
private
def create_product_images
params["product"]["product_images_attributes"].each do |index, image|
ProductImage.create(product_image: image, product_id: #form.product.id)
end
end
def set_product
#product = Product.find(params[:id])
end
def product_params
params.require(:product).permit(
:product_images_attributes,
:title,
:price,
:description,
:tag_list,
:category_id,
:size_description,
:shipping_description,
).merge(sizes_by_id: params[:product_form][:sizes_by_id]) # TODO
end
def correct_user_edit
if #product = current_user.products.find_by(id: params[:id])
else
redirect_to root_url if #product.nil?
end
end
end
product_form.rb
class ProductForm
attr_reader :quantity_by_size_id, :product
def initialize(product_image:, title:, price:, description:, tag_list:, category_id:, sizes_by_id:, user:, size_description:, shipping_description:)
#quantity_by_size_id = sizes_by_id # TODO
#product_images = product_image
#product = Product.new(
title: title,
price: price,
description: description,
tag_list: tag_list,
category_id: category_id,
size_description: size_description,
shipping_description: shipping_description,
user: user
)
build_product_sizes
end
def save
product.save
end
private
def build_product_sizes
quantity_by_size_id.map do |size_id, quantity|
if quantity.to_i.nonzero?
product.product_sizes.build(product: product, size_id: size_id, quantity: quantity)
end
end
end
end
Product.rb
class Product < ActiveRecord::Base
acts_as_taggable
belongs_to :user
belongs_to :category
has_many :product_sizes
has_many :product_images, :dependent => :destroy
validates :title, presence: true, length: { maximum: 30 }
validates :description, presence: true, length: { maximum: 2000 }
validates :category, :user, :price, presence: true
accepts_nested_attributes_for :product_images, allow_destroy: true
end
schema
create_table "product_sizes", force: :cascade do |t|
t.integer "quantity"
t.integer "product_id"
t.integer "size_id"
end
product_size.rb
class ProductSize < ActiveRecord::Base
belongs_to :product
belongs_to :size
validates :quantity, presence: true
end
params
From: /Users/jo/Documents/Safsy/Website/Safsy/Safsy/app/controllers/products_controller.rb # line 36 ProductsController#create:
35: def create
=> 36: binding.pry
37: #form = ProductForm.new(
38: product_image: product_params[:product_image],
39: title: product_params[:title],
40: price: product_params[:price],
41: size_description: product_params[:size_description],
42: shipping_description: product_params[:shipping_description],
43: description: product_params[:description],
44: tag_list: product_params[:tag_list],
45: category_id: product_params[:category_id],
46: sizes_by_id: product_params[:sizes_by_id],
47: user: current_user
48: )
49: if #form.save
50: create_product_images
51: redirect_to #form.product
52: flash[:success] = "You have created a new product"
53: else
54: flash[:danger] = "Your product didn't save"
55: new
56: render "new"
57: end
58: end
[1] pry(#<ProductsController>)> params
=> {"utf8"=>"✓",
"authenticity_token"=>"3MZESoSUgrO2Bc3vl8/M0pTfTna9rjeXfXG23znsy3VGCEBcHMh+4ZC5rqSxGkQOmz4kl1JN9FFMH4lniDjFQg==",
"product"=>
{"product_images_attributes"=>
{"0"=>
{"product_image"=>
#<ActionDispatch::Http::UploadedFile:0x007f8c9ceb22f8
#content_type="image/jpeg",
#headers="Content-Disposition: form-data; name=\"product[product_images_attributes][0][product_image]\"; filename=\"780069_black_l.jpg\"\r\nContent-Type: image/jpeg\r\n",
#original_filename="780069_black_l.jpg",
#tempfile=#<File:/var/folders/yx/znmx6qfj0c507bvkym6lvhxh0000gn/T/RackMultipart20151222-46388-pgfg4b.jpg>>,
"_destroy"=>"false"},
"1450783221367"=>
{"product_image"=>
#<ActionDispatch::Http::UploadedFile:0x007f8c9ceb2140
#content_type="image/jpeg",
#headers="Content-Disposition: form-data; name=\"product[product_images_attributes][1450783221367][product_image]\"; filename=\"20090a.jpg\"\r\nContent-Type: image/jpeg\r\n",
#original_filename="20090a.jpg",
#tempfile=#<File:/var/folders/yx/znmx6qfj0c507bvkym6lvhxh0000gn/T/RackMultipart20151222-46388-3vamt4.jpg>>,
"_destroy"=>"false"}},
"category_id"=>"3",
"title"=>"new product",
"price"=>"352.40",
"description"=>"test",
"size_description"=>"test",
"shipping_description"=>"test",
"tag_list"=>"test"},
"product_form"=>{"sizes_by_id"=>{"1"=>"3", "2"=>"34", "3"=>""}},
"commit"=>"Create new product",
"controller"=>"products",
"action"=>"create"}
[2] pry(#<ProductsController>)> product_params
Unpermitted parameter: product_images_attributes
=> {"title"=>"new product",
"price"=>"352.40",
"description"=>"test",
"tag_list"=>"test",
"category_id"=>"3",
"size_description"=>"test",
"shipping_description"=>"test",
"sizes_by_id"=>{"1"=>"3", "2"=>"34", "3"=>""}}
[3] pry(#<ProductsController>)>
Update
After trying Rich Peck's answer i have the product images persisting and product.product_images is now working
My product_params look like this.
def product_params
params.require(:product).permit(
:title,
:price,
:description,
:tag_list,
:category_id,
:size_description,
:shipping_description,
product_images_attributes: [:product_image, :_destroy],
product_sizes_attributes: [:size_id, :quantity]
)
end

Why are you looping through your params and performing a create method?
--
You should just be able to pass the images through to your nested model with accepts_nested_attributes_for:
#app/models/product.rb
class Product < ActiveRecord::Base
has_many :product_images
accepts_nested_attributes_for :product_images, allow_destroy: true
end
#app/models/product_image.rb
class ProductImage < ActiveRecord::Base
belongs_to :product
has_attached_file :product_image
end
This should pass the nested image object from your #product form to your ProductImage model.
As long as you permit the right parameters, it should work fine for you:
#app/controllers/products_controller.rb
class ProductsController < ApplicationController
def new
#product = Product.new
#product.product_images.build
end
def create
#product = Product.new product_params
#product.save
end
private
def product_params
params.require(:product).permit(:product, :params, product_images_attributes: [:product_image] %>
end
end
Your form looks okay.
Update
Okay, in regards to saving ProductSize along with the image, you can use accetps_nested_attributes_for to save them at the same time...
I would always advocate using this instead of trying to create your own classes in an after_create call or whatever -- keeping the flow consistent is the most important thing you can do.
Anyway...
#app/models/product.rb
class Product < ActiveRecord::Base
has_many :product_images
has_many :product_sizes
accepts_nested_attributes_for :product_images, :product_sizes
end
#app/models/product_size.rb
class ProductSize < ActiveRecord::Base
belongs_to :product
end
#app/models/product_image.rb
class ProductImage < ActiveRecord::Base
belongs_to :product
has_attached_image :image
end
This will allow you to use the following controller setup:
#app/controllers/products_controller.rb
class ProductsController < ApplicationController
def new
#product = Product.new
#product.product_images.build
#product.product_sizes.build
end
def create
#product = Product.new product_params
#product.save
end
private
def product_params
params.require(:product).permit(:x, :y, :z, product_images_attributes: [:image], product_sizes_attributes: [:a, :b, :c])
end
end
This will allow you to use a similar setup to what you have currently:
#app/views/products/new.html.erb
<%= form_for #product do |f| %>
<%= f.fields_for :product_images do |p| %>
<%= p.file_field :image %>
<% end %>
<%= f.fields_for :product_sizes do |s| %>
<%= s.text_field :a %>
<% end %>
<%= f.submit %>
<% end %>
This will allow you to use all the validations you require etc, and if you use Cocoon, you'll be able to add extra sizes as you require.

You don't have an Array, it seems a Hash there.
Your Hash looks like {"ID" => IMAGE}
Try to iterate over an id and image like this:
params["product"]["product_images_attributes"].each do |index, image|

Related

Rails: "Validation failed: Class must exist" in a Form_with

I've a multiple relation table named Order which belongs_to a Relai(to avoid singular/plurials complications), a Customer and a Composition. I set my Order model accordingly with nested_attributes as mentioned below. Before adding the customer part, I want to send a mail with just the #composition and a #relai chose with a dropdown.
class Order < ApplicationRecord
belongs_to :relai
belongs_to :composition
accepts_nested_attributes_for :relai
accepts_nested_attributes_for :composition
end
I set my OrdersController to get the :composition_id from my params
def new
#order = Order.new
#composition = Composition.find(params[:composition_id])
#relais = Relai.all
end
def create
#florist = Florist.first
#composition = Composition.find(params[:composition_id])
##relai = Relai.find(params[:relai_id]) # If I add this line it returns "Couldn't find Relai without an ID"
#order = Order.new(order_params)
if #order.save!
raise
OrderMailer.order_mail(#order).deliver
redirect_to thanks_purchase_path
else
render :new
end
end
private
def order_params
params.require(:order).permit(
florist_attributes: [:id],
relai_attributes: [:id, :name, :address],
composition_attributes: [:id, :name]
)
end
My View:
<%= form_with(model: #order, url: composition_orders_path(#composition), local: true) do |compo| %>
<%= compo.collection_select :relai, #relais, :id, :name %>
<%= compo.fields_for :composition do |fc| %>
<%= fc.collection_select :composition, #compositions, :id, :name %>
<% end %>
# Start Block that doesn't display the Relai.all
#<%#= compo.fields_for :relai do |fr| %>
#<%#= fr.label :relai, 'Ici la liste des relais :' %>
#<%#= fr.association :relai, collection: #relais %>
#<%#= fr.association :relai, #relais, :id, :name %>
#<%# end %>
# End Block
<%= compo.submit "MAIL", class: "app-form-button" %>
<% end %>
And the routes:
resources :compositions, only: [:show, :index] do
resources :orders, only: [:show, :new, :create, :index]
end
I also tried:
"nested_attributes" which seems to not works (I can't see my Relai collection in view.
=> https://www.pluralsight.com/guides/ruby-on-rails-nested-attributes)
the "optional: true" in model which throw me an error of:
PG::NotNullViolation: ERROR: null value in column "relai_id" of relation "orders" violates not-null constraint
Can someone explain me why I got a "Validation failed: Relai must exist, Composition must exist" whereas these appears in my params?
{"authenticity_token"=>"[FILTERED]", "order"=>{"relai"=>"2"}, "commit"=>"MAIL", "composition_id"=>"3"}
I'm on Rails 6.1.4 ; Ruby 2.6.6
accepts_nested_attributes_for works from parent to children. You are using it on the child side (Order).
If you just need to assign an existing Relai and Composition to Order just use a select for both of them:
class Order < ApplicationRecord
belongs_to :relai
belongs_to :composition
end
def new
#order = Order.new
#compositions = Composition.all
#relais = Relai.all
end
def create
#order = Order.new(order_params)
if #order.save!
OrderMailer.order_mail(#order).deliver
redirect_to thanks_purchase_path
else
render :new
end
end
private
def order_params
params.require(:order).permit(:relai_id, :composition_id)
end
<%= form_with(model: #order, url: composition_orders_path(#composition), local: true) do |compo| %>
<%= compo.collection_select :relai_id, #relais, :id, :name %>
<%= compo.collection_select :composition_id, #compositions, :id, :name %>
<%= compo.submit "MAIL", class: "app-form-button" %>
<% end %>
EDIT: Setting Composition on the controller.
def new
composition = Composition.find(params[:composition_id])
#order = Order.new(composition: composition)
#relais = Relai.all
end
def create
#order = Order.new(order_params)
if #order.save!
OrderMailer.order_mail(#order).deliver
redirect_to thanks_purchase_path
else
render :new
end
end
private
def order_params
params.require(:order).permit(:relai_id, :composition_id)
end
<%= form_with(model: #order, url: composition_orders_path(#composition), local: true) do |compo| %>
<%= compo.collection_select :relai_id, #relais, :id, :name %>
<%= compo.hidden_field :composition_id %>
<%= compo.submit "MAIL", class: "app-form-button" %>
<% end %>

The edit feature on my rails app is duplicating my data

I am building a recipe book for my rails app. I have ingredients nested under recipes. I can create a recipe and add the ingredients in just fine. However, when I go to edit a recipe, it will duplicate all the ingredients in the form. It will then show all the ingredients duplicated in the views pages and when I delete one of the duplicated ingredient items to only have one, it deletes both items. We are also not allowed to use accepts_nested_forms_for for this project, hence why I have the custom writer
Recipe Model
class Recipe < ApplicationRecord
belongs_to :user, required: false
has_many :recipe_ingredients
has_many :ingredients, through: :recipe_ingredients
validates :name, presence: true
validates :instructions, length: {minimum: 5}
validates :cooktime, presence: true
def self.alphabetical
self.order(name: :asc)
end
def ingredients_attributes=(ingredients_attributes)
ingredients_attributes.values.each do |ingredients_attribute|
if !ingredients_attribute.empty? &&
new_ingredient =
Ingredient.find_or_create_by(ingredients_attribute)
self.ingredients << new_ingredient
end
end
end
end
Ingredient Model
class Ingredient < ApplicationRecord
has_many :recipe_ingredients
has_many :recipes, through: :recipe_ingredients
def self.alphabetical
self.order(name: :asc)
end
end
Recipe_Ingredient Model
class RecipeIngredient < ApplicationRecord
belongs_to :recipe, required: false
belongs_to :ingredient, required: false
end
Recipe Controller
class RecipesController < ApplicationController
before_action :authenticate_user!
def new
#recipe = Recipe.new
3.times do
ingredient = #recipe.ingredients.build
end
end
def index
#recipes = current_user.recipes.alphabetical
end
def create
#recipe = current_user.recipes.new(recipe_params)
if #recipe.save
redirect_to recipe_path(#recipe)
else
render :new
end
end
def show
#recipe = Recipe.find(params[:id])
#ingredients = #recipe.ingredients.alphabetical
end
def edit
#recipe = Recipe.find(params[:id])
end
def update
#recipe = Recipe.find(params[:id])
if #recipe.update(recipe_params)
redirect_to #recipe
else
render :edit
end
end
def destroy
#recipe = Recipe.find(params[:id])
#recipe.delete
redirect_to recipes_path
end
private
def recipe_params
params.require(:recipe).permit(:name, :instructions, :cooktime,
:ingredient_ids => [], ingredients_attributes: [:recipe_id, :name])
end
end
Recipe Show Page
<h1> Recipe for <%=#recipe.name%></h1>
<h4>Recipe Instructions: <%=#recipe.instructions%></h4>
<h4>Cook Time: <%=#recipe.cooktime%></h4>
</ul>
<h4> Ingredients: </h4>
<% #ingredients.each do |ingredient|%>
<li><%=ingredient.name %></li>
<%end%>
</ul>
<%=link_to "Ingredients", recipe_ingredients_path(#recipe,
#ingredients)%>
<br>
<%=link_to "Delete Recipe", recipe_path(#recipe), :method => "delete"
%>
<br>
<%=link_to "Edit Recipe", edit_recipe_path(#recipe) %>
<br>
<%=link_to "All Recipes", recipes_path %>
<br>
Here are the edit and new form
<%= form_for #recipe do |f| %>
<%=f.label :name, "Name" %>
<%=f.text_field :name %>
<br>
<%=f.label :instructions, "Instructions" %>
<%=f.text_area :instructions, :rows => 3 %>
<br>
<%=f.label :cooktime, "Cook Time" %>
<%=f.text_field :cooktime %>
<br>
<%#= f.collection_check_boxes :ingredient_ids, Ingredient.all, :id,
:name %>
<%= f.fields_for :ingredients, #ingredient do |ingredient_fields| %>
<br>
<div class = >
<%= ingredient_fields.label :name, "Ingredient" %>
<%= ingredient_fields.text_field :name %>
<% end %>
<%=f.submit%>
Any help is greatly appreciated.
This code adds all the ingredients on the form to the recipe's ingredients relationship EVERY time.
def ingredients_attributes=(ingredients_attributes)
ingredients_attributes.values.each do |ingredients_attribute|
if !ingredients_attribute.empty? &&
new_ingredient =
Ingredient.find_or_create_by(ingredients_attribute)
self.ingredients << new_ingredient
end
end
end
You should clear down the ingredients relationship before you start pushing in the returned attributes
def ingredients_attributes=(ingredients_attributes)
self.ingredients.destroy_all # <- this will clear down ingredients
ingredients_attributes.values.each do |ingredients_attribute|
if !ingredients_attribute.empty? &&
new_ingredient =
Ingredient.find_or_create_by(ingredients_attribute)
self.ingredients << new_ingredient
end
end
end
The only thing you need to do is check if the new_ingredient is already included in the recipe's ingridients before (re)adding it.
You could do this like that:
#recipe.rb
def ingredients_attributes=(ingredients_attributes)
ingredients_attributes.values.each do |ingredients_attribute|
if !ingredients_attribute.empty? &&
new_ingredient =
Ingredient.find_or_create_by(ingredients_attribute)
self.ingredients << new_ingredient if !self.ingredients.include?(new_ingredient)
end
end
end

How to use accepts_nested_attributes_for with rails?

I have read these documents:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for
http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html
My source:
Model
app/models/product.rb
class Product < ActiveRecord::Base
has_many :product_items, foreign_key: :product_id, :dependent => :destroy
accepts_nested_attributes_for :product_items
end
app/models/product_item.rb
class ProductItem < ActiveRecord::Base
belongs_to :product
end
Controller
app/controllers/products_controller.rb
class ProductController < ApplicationController
def new
#product = Product.new
end
def create
#product = Product.new(product_params)
respond_to do |format|
if #product.save
format.html { redirect_to root_path }
else
format.html { render :new }
end
end
end
private
def product_params
params.require(:product).permit(:name, product_items_attributes: [ :description ])
end
end
View
app/views/products/_form.html.erb
<%= form_for(product, url: path) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :product_items do |item_form| %>
<%= item_form.label :description %>
<%= item_form.text_field :description %>
<% end %>
<%= f.submit 'Submit' %>
<% end %>
I can save the product data to the database, but it can't save product_items.
Edit
Add my post data after submit my form:
utf8:✓
authenticity_token:c5sdmoyLoN01Fxa55q6ahTQripx0GZvWU/d27C]asfdawofX9gw==
product[name]:apple
product[product_items][description]:good
commit:Submit
Edit 2
Add rails log:
Started POST "/products" for 10.0.2.2 at 2015-09-15 13:00:57 +0900
Processing by ProductController#create as HTML
I, [2015-09-15T13:00:58.039924 #28053] INFO -- : Parameters: {"utf8"=>"✓", "authenticity_token"=>"bilBzgOLc2/ZRUFhJORn+CJvCHkVJSsHQTg1V/roifoHxi9IRA==", "product"=>{"name"=>"apple", "product_items"=>{"description"=>"good"}}, "commit"=>"Submit"}
Your new method should like below in order to save product_items
def new
#product = Product.new
#product.product_items.build
end
You should also change your product_params like below for the update to work correctly in future.
def product_params
params.require(:product).permit(:name, product_items_attributes: [ :id, :description ])
end
Update your controller action product_params
def product_params
params.require(:product).permit(:name, product_items_attributes: [:id, :description, :_destroy ])
end
Either you can use nested_form gem which provide you complete demo and function control. Here is link https://github.com/ryanb/nested_form

Nested form won't save, Rails 4

I can't figure out what I am doing wrong, but my nested form will not save to my room table.
Here's my view
new.html.erb
<h1>Room Assignments</h1>
<p>
<%= form_for #member do |f| %>
<%= f.label :name, "Member name" %><br>
<%= f.text_area :name %><br>`enter code here`
<%= f.fields_for :room do |b| %>
<%= b.label :room_name %><br>
<%= b.text_area :room_name %><br>
<%= b.label :rent %><br>
<%= b.text_area :rent %><br>
<% end %>
<%= f.submit "Submit" %>
<% end %>
members_controller.rb
class MembersController < ApplicationController
def new
#member = Member.new
#member.purchases.build
#room = #member.build_room
end
def index
#member = Member.all
end
def create
#member = Member.create(member_params)
#room = #member.create_room
if #member.save
redirect_to new_purchase_url
else
render :new
end
end
def show
#member = Member.find(params[:id])
end
def member_params
params.require(:member).permit(:name, :room_id,
room_attributes: [:rent, :name, :id, :member_id],
purchase_attributes: [:description, :cost, :id, :member_id])
end
end
member.rb
class Member < ActiveRecord::Base
belongs_to :room
has_many :purchases
accepts_nested_attributes_for :purchases, :room
end
room.rb
class Room < ActiveRecord::Base
has_one :member, foreign_key: "member_id"
has_many :purchases
end
This is what prints out in the rails console when I check to see if it has saved:
2.0.0-p451 :070 > Member.last
Member Load (0.3ms) SELECT "members".* FROM "members" ORDER BY "members"."id" DESC LIMIT 1
=> #<Member id: 24, name: "Tom", room_id: 38>
2.0.0-p451 :071 > Room.last
Room Load (0.3ms) SELECT "rooms".* FROM "rooms" ORDER BY "rooms"."id" DESC LIMIT 1
=> #<Room id: 38, room_name: nil, rent: nil, member_id: nil>
The problem is that you're calling #room = #member.create_room in the create action. The previous line should have already pulled in the parameters and built the member and room instances. So when you call create_room you're actually replacing the room that was added with a new (blank) one. Here's what I would do:
def create
#member = Member.new(member_params)
if #member.save
redirect_to new_purchase_url
else
render :new
end
end

saving two Rails models with accepts_nested_attributes_for

I have two models: author and profile. Author accepts_nested_attributes_for profile. I can't figure out how to save the profile model (namely the :avatar attribute) through author.
author.rb
class Author < ActiveRecord::Base
has_one :profile
accepts_nested_attributes_for :profile
end
profile.rb
class Profile < ActiveRecord::Base
mount_uploader :avatar, AvatarUploader
belongs_to :author
end
authors_controller.rb
class AuthorsController < ApplicationController
before_action :set_author, only: [:show, :edit, :update, :destroy]
def index
#authors = Author.all.order("last_name ASC, first_name ASC")
end
def new
#author = Author.new
end
def create
#author = Author.new(author_params)
if #author.save
AuthorMailer.activate(#author).deliver
flash[:notice] = "Please check your email to activate your account."
redirect_to root_path
else
render :new
end
end
def edit
#profile = #author.build_profile
end
def update
if #author.update(author_params)
flash[:notice] = "Profile has been updated."
redirect_to #author
else
flash[:alert] = "Profile has not been updated."
render :edit
end
end
private
def author_params
params.require(:author).permit(:first_name, :last_name, :email, :password, :password_confirmation, :avatar)
end
def set_author
#author = Author.find(params[:id])
end
end
app/views/authors/edit.html.erb
<div class="center">
<h1>Edit Author</h1>
</div>
<div class="row">
<div class="col-xs-6 col-lg-4 center-block">
<%= simple_form_for(#author, :defaults => { :wrapper_html => {:class => 'form-group'}, :input_html => { :class => 'form-control' } }) do |f| %>
<%= f.input :first_name %>
<%= f.input :last_name %>
<%= f.fields_for [#author, #profile] do |p| %>
<%= p.file_field :avatar %>
<% end %>
<%= f.submit "Submit", class: "btn small btn-default" %>
<% end %>
</div>
</div>
This form doesn't save anything to my profile table.
EDIT1
updating the permit parameters didn't save anything to the profile table. But I did notice that adding the following to my authors_controller in the update action saves an incomplete record to the profile table (the avatar field is blank):
author_controller#update
if #author.update(author_params)
#profile = #author.build_profile()
#author.profile = #profile
flash[:notice] = "Profile has been updated."
redirect_to #author
else
flash[:alert] = "Profile has not been updated."
render :edit
end
I tried placing pry inside the update action and my params look like this:
{"utf8"=>"✓",
"_method"=>"patch",
"authenticity_token"=>"EROrMzOejPmMU/wzlnC5iaoTPu4pyBXelHAs3uiPA2U=",
"author"=>
{"first_name"=>"Mike",
"last_name"=>"Glaz",
"profile"=>
{"avatar"=>
#<ActionDispatch::Http::UploadedFile:0x007feb48127ab0
#content_type="image/jpeg",
#headers=
"Content-Disposition: form-data; name=\"author[profile][avatar]\"; filename=\"me_and_lekeziah.jpg\"\r\nContent-Type: image/jpeg\r\n",
#original_filename="me_and_lekeziah.jpg",
#tempfile=#<File:/tmp/RackMultipart20140504-15793-l4uu6l>>}},
"commit"=>"Submit",
"action"=>"update",
"controller"=>"authors",
"id"=>"3"}
so then I tried the following in my update action:
#profile = #author.build_profile(params[:author][:profile])
#author.profile = #profile
but then I get the following error:
ActiveModel::ForbiddenAttributesError in AuthorsController#update
when you use "fields_for" for a association model xxx, your params need to permit :xxx_attributes fields, which's value should be a array include it's attributes, so change your author_params method to something like this:
def author_params
params.require(:author).permit(:first_name, :last_name, :email, :password, :password_confirmation, profile_attributes: [:avatar])
end
First, you should update your params permitted fields as:
def author_params
params.require(:author).permit(:first_name, :last_name, :email, :password, :password_confirmation, :profile_attributes[:id, :avatar])
end
Second, you are mixing Rails form_for helper and Simple_form in your view.
change
<%= f.fields_for [#author, #profile] do |p| %>
to:
<%= f.simple_fields_for [#author, #profile] do |p| %>

Resources