I've been ignoring this problem for a while but I can't any longer. My nested image model objects, product_images are not showing when I render a product's edit view. Due to this my application calls an error saying that the image is nil when I try to render the show view.
Why arent my nested product images appearing? I'm using paperclip and s3.
I need the nested images form to be editable, everything works when a new product is created, the editing is the only problem. The nil exception during the show action is just related to the edit action malfunction
Below is my code:
models ##
class ProductImage < ActiveRecord::Base
attr_accessible :product_id, :photo, :image_width, :image_height
belongs_to :product
has_attached_file :photo, :styles => { :small => "150x150>"}, :dependent => :destroy
validates_attachment_presence :photo
validates_attachment_size :photo, :less_than => 5.megabytes
validates_attachment_content_type :photo, :content_type => ['image/jpg', 'image/jpeg', 'image/png', 'image/gif']
after_post_process :save_image_dimensions
def save_image_dimensions
geo = Paperclip::Geometry.from_file(photo.queued_for_write[:original])
self.image_width = geo.width.to_i
self.image_height = geo.height.to_i
end
def sized_different?
if image_width < image_height
true
else
false
end
end
end
class Product < ActiveRecord::Base
attr_accessible :taxable, :shippable, :page_name, :tact_code, :manu_code, :body_code, :name, :alert, :price, :description, :colors_attributes, :sizes_attributes, :extras_attributes, :restraints_attributes, :product_images_attributes
validates_presence_of :page_name
validates_presence_of :price
validates_presence_of :description
validates_presence_of :name
has_many :product_images, :dependent => :destroy
has_many :restraints, :dependent => :destroy
has_many :colors, :dependent => :destroy
has_many :sizes, :dependent => :destroy
has_many :extras, :dependent => :destroy
accepts_nested_attributes_for :colors, :sizes, :extras, :restraints, :product_images
end
controller
def index
#products = Product.search(params)
unless current_cart.nil?
#cart = current_cart
else
#cart = nil
end
end
def new
#product = Product.new
#product.product_images.build
end
def create
#product = Product.new(params[:product])
if #product.save
render :index
else
render :new
end
end
def edit
#product = Product.find(params[:id])
#cart = current_cart
end
def show
#product = Product.find(params[:id])
#cart = current_cart
end
def update
#product = Product.find(params[:id])
if #product.update_attributes!(params[:product])
render :index
else
render :edit
end
end
end
_form view
<%= form_for #adminproduct, validate: true, html:{ multipart: true} do |f| %>
<div class="span3 field">
<ul class="unstyled">
<li><%= f.text_field :name, :placeholder => "Product name" %></li>
<li><%= f.text_field :manu_code, :placeholder => "Manufacturer code" %></li>
<li><%= f.text_field :alert, :placeholder => "Alert, example: Save 10%" %></li>
<li><%= f.text_field :price, :placeholder => "Base price" %></li>
<li><%= f.text_area :description, :placeholder => "Product description", :size => "30x10" %></li>
</ul>
</div>
<div class="span6 offset1">
<ul class="unstyled">
<li><%= f.check_box :taxable %>Item can be taxed</li>
<li><%= f.check_box :shippable%>Item can be standard shipped</li>
<li><br /><b>Choose product images:</b></li>
(the first image will be the product's main image)<br/>
<%= f.fields_for :product_images do |p|%><%= render 'product_image_fields', f: p %><% end %>
<li><%= link_to_add_fields "Add image", f, :product_images %></li>
Any insight is appreciated.
edit
product images partial
<fieldset>
<%= f.file_field :photo %>
<%= f.hidden_field :_destroy %>
<%= link_to "remove", '#', class: "remove_fields" %>
</fieldset>
On the products_controller, modify the edit action to include product_images so that accepts_nested_attributes_for knows which images to update.
def edit
#product = Product.includes(:product_images).find(params[:id])
# ...
end
I believe you need to call your partial like this:
<%= render partial: 'product_image_fields', locals: {f: p} %>
If it does not work, please paste in your original question your 'product_image_fields' file and the error message from your console
--
Ok this is your partial
<fieldset>
<%= f.file_field :photo %>
<%= f.hidden_field :_destroy %>
<%= link_to "remove", '#', class: "remove_fields" %>
</fieldset>
A your problem is empty product_image upload inputs. Well you using file_field, which produce input type=file, and HTML won't allow you to put a value in this kind of fields. In other words: you cannot load value from a database to a file_field.
This is my advise:
Change your partial to output what comes from the database. This is just to verify and confirm the data arrive at the right place
<fieldset>
<%= f.object.send :photo %>
<%= f.file_field :photo %>
<%= f.hidden_field :_destroy %>
<%= link_to "remove", '#', class: "remove_fields" %>
</fieldset>
You should get in the screen the path of your image. Also look at the HTML source, Rails should add hidden fields with your images records id.
If you got all right, then you need to display your image with something else than a file_field, try an image_tag or anything else you want to use
Have you tried you tried using the wonderful accepts_nested_attributes_for provided by Rails for working with Nested Attributes?
Read more http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
EDIT
I thought best to provide you an example rather.
class Product < ActiveRecord::Base
attr_accessible ..., :product_images_attributes # <======= Pay attention here. Mass Assignment attributes required
accepts_nested_attributes_for :product_images
....
end
Related
In my models I have
class Blog < ActiveRecord::Base
has_many :tags, :dependent => :destroy
accepts_nested_attributes_for :tags, :allow_destroy => true
end
class Tag < ActiveRecord::Base
belongs_to :blog
validates :blog, :name, presence: true
end
Blog Controller
def new
#blog = Blog.new
#blog.tags.build
end
_form.html.erb
<%= form_for #blog, html: { multipart: true } do |f| %>
<div class="form-group">
<%= f.text_field :title, placeholder: 'Title', class: ('form-control') %>
</div><br>
<%= f.fields_for :tags do |builder| %>
<div class="form-group">
<%= builder.text_field :name, placeholder: 'Tags' %>
</div><br>
<% end %>
<div class="actions text-center">
<%= f.submit 'Submit', class: 'btn btn-primary' %>
</div>
<% end %>
Blog Controller
def create
#blog = Blog.new(blog_params)
binding.pry
end
def blog_params
params.require(:blog).permit(:title, :author, :text, :avatar, :banner, :tags_attributes => [:id, :name])
end
At my binding, it says #blog's error message is that it can't be saved because the Tag object is missing a blog_id. I have looked everywhere and I have tried to replicate my code to match other solutions but to no success.
If it helps, in my params when I submit the form I get this
"tags_attributes"=>{"0"=>{"name"=>"dsfsf"}}
that's because your #blog is not persisted in the db yet, so you won't have the id.
In your Tag model, remove :id from validation.
You should be able to just do Blog.create(blog_params)
Rails should handle the rest for you.
I'm trying to create a form where users can see a list of their friends and add or remove friends from lists (sort of like facebook's groups). For the create list view, I can get the form working properly, but for the edit view I'm having trouble with check_box syntax and fields_for.
routes.rb
resources :friendships
resources :friend_lists
class User < ActiveRecord::Base
has_many :friendships, dependent: :destroy
has_many :friends, through: :friendships
has_many :friend_lists
has_many :flist_memberships, class_name: 'FlistMembership', foreign_key: 'member_id', dependent: :destroy
...
end
class FriendList < ActiveRecord::Base
belongs_to :user
has_many :flist_memberships
has_many :members, through: :flist_memberships
accepts_nested_attributes_for :members, allow_destroy: true
accepts_nested_attributes_for :flist_memberships, allow_destroy: true
end
class FlistMembership < ActiveRecord::Base
belongs_to :friend_list
belongs_to :member, class_name: 'User'
end
class FriendListsController < ApplicationController
def new
#friend_list = current_user.friend_lists.build
#friends = current_user.friends.paginate(page: params[:page])
#friend_list.flist_memberships.build
end
def create
#friend_list = current_user.friend_lists.build(friend_list_params)
respond_to do |format|
format.html {
if #friend_list.save
flash[:success] = "Friends list created!"
redirect_to friendships_path
else
render 'friend_lists/new'
end
}
end
end
def edit
#friend_list = FriendList.find(params[:id])
#friends = current_user.friends
end
def update
#friend_list = FriendList.find(params[:id])
if #friend_list.update_attributes(friend_list_params)
flash[:success] = "Friend list updated!"
redirect_to friend_list_path(#friend_list)
else
render 'edit'
end
end
private
def friend_list_params
params.require(:friend_list).permit(:name, flist_memberships_attributes: [:id, :member_id, :friend_list_id])
end
end
new.html.erb
<%= form_for #friend_list do |f| %>
<%= f.label :name, "Name for this friends list:" %>
<%= f.text_field :name %>
<% #friends.each do |friend| %>
<%= f.fields_for :flist_memberships do |m| %>
<%= m.check_box :member_id, {}, friend.id %>
<%= m.label :member_id, friend.name %>
<% end %>
<% end %>
<%= f.submit "Save", class: "btn btn-primary" %>
<% end %>
edit.html.erb
<%= form_for #friend_list do |f| %>
<%= f.label :name, "Name for this friends list:" %>
<%= f.text_field :name %>
<%= f.fields_for :flist_memberships do |m| %>
<%= m.collection_check_boxes(:member_id, #friends.all, :id, :name) %>
<% end %>
<%= f.submit "Save", class: "btn btn-primary" %>
<% end %>
new.html.erb renders and saves the selected items correctly, but I can't figure out how to write the edit form so that it doesn't iterate through the collection once for each item in the collection (both with collection_check_boxes and the form as written in new.html.erb). Trying
<%= f.fields_for :flist_memberships do |m| %>
<%= m.check_box :id %>
<%= m.label :id, :name %>
<% end %>
in the edit form just passes the string "id" as params rather than pulling the id of each member in the collection, and I'm not sure how to pull the individual item's name/id without iterating over the collection, which goes back to the multiple renders problem. I'm using cocoon elsewhere in the project, but for this I'd rather present the user a list of all options and allow them to check a box for each one, rather than having to manually add each item on the list. If there is a way to do it with cocoon, I'd be happy to hear it.
I'm using rails 3.2.14 and having trouble using has_many :through for the first time. I'm using a has_many :through relationship between Image and Design tables using a Design_Pictures table that will store the order ranking for design images. All Images are associated with one User. Later I want the flexibility to store other images in the Images table that are not associated with a particular design.
I can successfully add sample data to my database and show the image title (which is stored in the images table) and ranking (which is stored in the design_pictures table) in my show design pages. What I can't figure out is how to create a new design_picture. Once I can get this working I'm going to use CarrierWave or Paperclip to add images to the Image table.
Here are my models:
app/models/image.rb:
class Image < ActiveRecord::Base
attr_accessible :title
belongs_to :user
has_many :design_pictures, dependent: :destroy
has_many :designs, :through => :design_pictures
validates :user_id, presence: true
validates :title, length: { maximum: 80 }
end
app/models/design.rb:
class Design < ActiveRecord::Base
attr_accessible :title
has_many :design_pictures, dependent: :destroy
has_many :images, :through => :design_pictures
validates :title, presence: true, length: { maximum: 60 }
end
app/models/design_picture.rb:
class DesignPicture < ActiveRecord::Base
attr_accessible :ranking
belongs_to :image
belongs_to :design
validates :image_id, presence: true
validates :design_id, presence: true
default_scope order: 'design_pictures.ranking ASC'
end
app/models/user.rb:
class User < ActiveRecord::Base
attr_accessible :name
has_many :designs, dependent: :destroy
has_many :images, dependent: :destroy
validates :name, presence: true, length: { maximum: 50 }
end
Views:
app/views/designs/show.html.erb:
<div>
<% if #design.design_pictures.any? %>
<h3>Images (<%= #design.design_pictures.count %>)</h3>
<ul>
<%= render #design_pictures %>
</ul>
<% end %>
</div>
<div>
<%= render 'shared/design_picture_form' %>
</div>
app/views/design_pictures/_design_picture.html.erb
<li>
<%= design_picture.image.title %> - Ranking: <%= design_picture.ranking %>
</li>
app/views/shared/_design_picture_form.html.erb:
<%= form_for(#design_picture) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :title, "Title" %>
<%= f.text_field :title,
placeholder: "Name your image.",
class: "" %>
<%= f.label :ranking, "Ranking" %>
<%= f.text_field :ranking,
placeholder: "Rank your design picture.",
class: "" %>
<%= hidden_field_tag(:design_id, #design.id) %>
<%= f.submit "Add Image", class: "btn btn-large btn-primary" %>
<% end %>
Controllers:
app/controllers/designs_controller.rb
def show
#design = Design.find(params[:id])
#design_pictures = #design.design_pictures.find(:all, :limit => 10)
#image = current_user.images.build
#design_picture = #design.design_pictures.build
end
app/controllers/design_pictures_controller.rb
def create
#current_design = Design.find(params[:design_id])
#image = current_user.images.new(params[:title])
#design_picture = #current_design.design_pictures.build(:image_id => #image.id, params[:ranking])
if #design_picture.save
flash[:success] = "Micropost created!"
redirect_to #current_design
else
redirect_to designs_url
end
end
Error when I visit the design_picture form partial:
undefined method `title' for #<DesignPicture image_id: nil, design_id: 1427, ranking: nil>
If I remove the title from the form I can submit form but nothing is created.
Any help would be greatly appreciated.
Your DesignPicture does not have a title attribute. When you do:
#design.design_pictures.build
It build a new DesignPicture instance. And the following do not work:
<%= form_for(#design_picture) do |f| %>
# ...
<%= f.label :title, "Title" %>
# ...
You might want to take a look at Nested Form
I'm not sure if this is the best way to go about this but this is how I was able to get my form to submit and create both an Image entry with it's title attribute and the Design_Picture entry with it's rank attribute. I ended up not needing to use accepts_nested_attributes_for.
app/views/shared/_design_picture_form.html.erb
<%= form_for(#design_picture) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= label_tag(:title, "Title") %>
<%= text_field_tag(:title) %>
<%= f.label :ranking, "Ranking" %>
<%= f.text_field :ranking,
placeholder: "Rank your design picture.",
class: "" %>
<%= hidden_field_tag(:design_id, #design.id) %>
<%= f.submit "Add Image", class: "btn btn-large btn-primary" %>
app/controllers/designs_controller.rb
def show
#design = Design.find(params[:id])
#design_pictures = #design.design_pictures.find(:all, :limit => 10)
#design_picture = #design.design_pictures.build
end
app/controllers/design_pictures_controller.rb
def create
#image = current_user.images.create(:title => params[:title])
#current_design = Design.find(params[:design_id])
#design_picture = #current_design.design_pictures.build(params[:design_picture])
#design_picture.image_id = #image.id
if #design_picture.save
flash[:success] = "Design Picture created!"
redirect_to #current_design
else
redirect_to designs_url
end
end
I am trying to edit a Topic which has many Posts.
Edit page for a Topic has Topic's name and Post's content that can be edited.
The mass-assignment error occurs in topics_controller.rb, update method, post.update_attributes(params[:post]).
How do I avoid mass-assignment error.
topic.rb
class Topic < ActiveRecord::Base
has_many :posts, :dependent => :destroy
belongs_to :forum
accepts_nested_attributes_for :posts, :allow_destroy => true
attr_accessible :name, :last_post_id, :posts_attributes
end
post.rb
class Post < ActiveRecord::Base
belongs_to :topic
attr_accessible :content
end
topics_controller.rb
def update
#topic = Topic.find(params[:id])
post = #topic.posts.first
if #topic.update_attributes(params[:topic]) && post.update_attributes(params[:post])
topic = Topic.find(#post.topic_id)
flash[:success] = "Success!"
redirect_to topic_posts_path(topic)
else
render 'edit'
end
end
views/topics/edit.html.erb
<%= form_for #topic do |f| %>
<!-- render 'shared/error_messages_topic' -->
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for #topic.posts.first do |post| %>
<%= render :partial => "posts/form", :locals => {:f => post} %>
<% end %>
<%= f.submit "Edit", class: "btn btn-large btn-primary" %>
<% end %>
views/posts/_form.html.erb
<%= f.label :content %>
<%= f.text_area :content %>
In update method you don't have to update attributes of both the models instead of if #topic.update_attributes(params[:topic]) && post.update_attributes(params[:post]) it should this only if #topic.update_attributes(params[:topic]) it will update the posts automatically.
And change your view from this <%= f.fields_for #topic.posts.first do |post| %> to <%= f.fields_for :posts, #topic.posts.first do |post| %> it will work fine.
For more information read this http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for
I am using the nested form gem and i add products dynamically to the form. When i do click "add", another product resource appears but on creation it ERASES the former ones from being created entirely. This is how the scenario goes:
Fill in Location
Choose Date
Fill in Product ( one is already on form)
Add 5 more products (Products 2, 3, 4, 5)
Fill in All Products
"click" Create
Created Product 5
This is how my nested form looks:
<%= nested_form_for #location, :url => products_path(#product) do |f| %>
<%= f.label :business %>
<%= f.text_field :business %>
<%= f.label :address %>
<%= f.text_field :address %>
<%= f.fields_for :product_dates, :url => products_path(#product) do |d| %>
<%= d.label :date %>
<%= d.date_select :date %>
<%= d.fields_for :products, :url => products_path(#product) do |p| %>
<%= p.text_field :name %>
<%= p.text_field :price %>
<%= p.text_field :tag_list %>
<%= p.link_to_remove "Remove Product" %>
<% end %>
<%= d.link_to_add "Add", :products %>
<% end %>
<%= f.submit "Finish" %>
<% end %>
Controller:
class ProductsController < ApplicationController
def new
#location = Location.new
#product = Product.new
product_date = #location.product_dates.build
product_date.products.build
end
def create
#location = Location.create(params[:location])
if #location.save
flash[:notice] = "Products Created."
redirect_to :action => 'index'
else
render :action => 'new'
end
end
Models:
class User < ActiveRecord::Base
devise
attr_accessible :email, :password, :password_confirmation, :remember_me
has_many :products, :dependent => :destroy
end
class Location < ActiveRecord::Base
attr_accessible :address, :business, :product_dates_attributes
has_many :products
has_many :product_dates
accepts_nested_attributes_for :product_dates
end
class ProductDate < ActiveRecord::Base
attr_accessible :date, :products_attributes
belongs_to :location
belongs_to :user
has_many :products
accepts_nested_attributes_for :products
end
class Product < ActiveRecord::Base
attr_accessible :name, :price, :tag_list
belongs_to :user
belongs_to :location
belongs_to :product_date
end
Any Suggestions?
First of all remove the url_for declarations on the fields_for declarations so you get
<%= nested_form_for #location, :url => products_path(#product) do |f| %>
<%= f.label :business %>
<%= f.text_field :business %>
<%= f.label :address %>
<%= f.text_field :address %>
<%= f.fields_for :product_dates do |d| %>
<%= d.label :date %>
<%= d.date_select :date %>
<%= d.fields_for :products do |p| %>
<%= p.text_field :name %>
<%= p.text_field :price %>
<%= p.text_field :tag_list %>
<%= p.link_to_remove "Remove Product" %>
<% end %>
<%= d.link_to_add "Add", :products %>
<% end %>
<%= f.submit "Finish" %>
<% end %>
What is really confusing is your whole routing and params approach. It's just not right. You have a form_for #location with a :url products_path(#product) This will right royally cause issues with the params that are sent back and there in lies the problem.
Stick with routing to location controller not the products controller by removing the products_path(#product) form your nested_form_for declaration and you will find that you will have all the necessary records saved but you will most likely need to change the redirect_to declaration in the locations_controller create action and the same for the update_action.
But why use the products controller at all when you are dealing with a location? Again this just isn't natural or intuitive.
One last thing. Your remove links won't work as you have not added the necessary :dependent => :destroy declaration to the has_many declarations and you are also missing the :reject_if procs and the :allow_destroy => true declarations on the accepts_nested_attributes declarations.
Can I strongly suggest that you
1) Use either the locations controller or the products controller not both
I mean link to get to this form link_to the locations controller and set everything up there or use form_for #product rather than #location and handle everything in the products controller
2) watch the railscasts that this gem is based on very closely
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2
3) Spend some time learning about how rails form view helpers arrange for the params hash to be organised in the controllers actions. In your case, have a close look at your log file for the parameters that come into the create action as things currently stand.
You will most likely see that the params are not nested as you would exect them to be which is why the nested attributes declaration is not behaving as expected