Rails nested form: update creates multiple records - ruby-on-rails

I have menu model and photos model where the menu has has_many relationship with photo. For the image upload, I'm using paperclip. I was able to build a nested_form that creates photo and other attributes in photos table. However, when I update, the record gets duplicated in photos table and the new photo selected in the update form will not be uploaded. Appreciate your help.
menu model
class Menu < ActiveRecord::Base
has_many :photos, :dependent => :destroy
accepts_nested_attributes_for :photos, reject_if: :all_blank, allow_destroy: true
end
photo model
class Photo < ActiveRecord::Base
belongs_to :menu
has_attached_file :image,
:styles => { :thumb => "100x100#", :medium => "300x300#", :large => "600x400>" },
:url => "/assets/menus/photos/images/:id/:style/:basename.:extension",
:path => "#{Rails.root}/public/assets/menus/photos/images/:id/:style/:basename.:extension"
validates_attachment :image, content_type: { content_type: ["image/jpg", "image/jpeg", "image/png", "image/gif"] }
end
form.html.haml
= simple_form_for #menu do |f|
= f.simple_fields_for :photos do |photo|
= render 'photo_fields', f: photo
_photo_field.html.haml
.nested-fields
= f.file_field :image
= f.input :main_flag, as: :hidden, input_html: { value: "true" }
= f.input :user_id, as: :hidden, input_html: { value: "1"}
menus_controller.rb
class MenusController < ApplicationController
...
def update
#menu = Menu.find(params[:id])
if #menu.update(menu_params)
if params[:image]
#menu.photos.destroy
#menu.photos.build(menu_params)
end
flash[:success]= 'Menu was successfully updated'
redirect_to brand_menus_path(#menu.brand_id)
else
render 'index'
end
end
private
def menu_params
params.require(:menu).permit(:name, :price, :brand_id, :category_id, :description,
photos_attributes: [:user_id, :image, :main_flag])
end

This is very common problem with the strong_parameters when using with nested_params. Whitelisting the :id in the photos_attributes should fix your problem
def menu_params
params.require(:menu).permit(:name, :price, :brand_id, :category_id, :description,
photos_attributes: [ :id, :user_id, :image, :main_flag])
end

Related

Polymorpic paperclip working strange with Rails 5

view:
<div class="kitchen_settings">
<%= form_for #kitchen, :html => { :multipart => true, "data-ajax" => false} do |f| %>
<%= f.text_field :title,placeholder: "#{current_user.fullname}'s Kitchen", autofocus: true %>
<%= f.text_field :bio, placeholder: 'something about your kitchen' %>
<%= f.fields_for :picture do |photo| %>
<%= photo.file_field :avatar %>
<% end %>
<%= f.submit %>
<% end %>
kitchen_controller.rb
class KitchensController < ApplicationController
before_action :authenticate_user!
def new
#kitchen = Kitchen.new
#kitchen.build_picture
end
def create
#kitchen = current_user.build_kitchen(kitchen_params)
respond_to do |format|
if #kitchen.save
format.html { redirect_to dashboard_path, notice: 'Kitchen was successfully created.' }
format.json { render :show, status: :created, location: dashboard_path }
else
format.html { render :new }
format.json { render json: #kitchen.errors, status: :unprocessable_entity }
end
end
end
def show
#kitchen = Kitchen.find (params[:id])
end
private
def kitchen_params
params.require(:kitchen).permit(:title,:bio, picture_attributes: [:avatar])
end
end
kitchen.rb
class Kitchen < ApplicationRecord
belongs_to :user
has_one :picture, as: :imageable, dependent: :destroy
accepts_nested_attributes_for :picture
end
picture.rb
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
has_attached_file :avatar, styles: { medium: "300x300>", thumb: "100x100"}, default_url: "/assets/:style/missing.png"
validates_attachment :avatar, :presence => true,
:content_type => { :content_type => ["image/jpeg", "image/gif", "image/png"] },
:size => { :in => 0..500.kilobytes }
end
And its giving me this error
I wanted polymorphic picture model. I decided to deal with polymorphic picture association but its just rollbacks always... Stuck. I've attached the image from the console. Thanks!!
Debugged it using binding.pry
In Rails 5, whenever we define a belongs_to association, it is required to have the associated record present by default after this change.To change this behavior i managed to do it this way:
picture.rb
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true, optional: true
has_attached_file :avatar, styles: { medium: "300x300>", thumb: "100x100"}, default_url: "/assets/:style/missing.png"
validates_attachment :avatar, :presence => true,
:content_type => { :content_type => ["image/jpeg", "image/gif", "image/png"] },
:size => { :in => 0..500.kilobytes }
end
There is a problem with you validation of imageable in image.rb
When you store kitchen alongwith image in single go. Imageable validation is failed for image as kitchen is not created yet. That's why it's rollback.
To confirm it you can remove the validation temporarily.
You need to use inverse_of to make this work
in image.rb
belongs_to :imageable, polymorphic: true , inverse_of: :image
in kitchen.rb
has_one :picture, as: :imageable, dependent: :destroy,inverse_of: :imageable
I added inverse_of in both the associations. It tells rails that these associations are inverse to each other so if any of it's set then don't make a query to fetch the other.
In this way if you set any association then other will be set automatically and validation won't fail.
Here is a nice blog for inverse_of usage.

ActiveAdmin: Not saving association between my two models

How can the association between my two models be saved in ActiveAdmin?
I have two models a room and a photo model. In the ActiveAdmin I want to update the association between the photo and the room.
room.rb (app/models)
class Room < ApplicationRecord
has_many :photos
accepts_nested_attributes_for :photos
end
photo.rb (app/models)
class Photo < ApplicationRecord
belongs_to :room
has_attached_file :image, styles: { medium: "300x300>", thumb: "100x100>" }
validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
end
photo.rb (app/admin)
ActiveAdmin.register Photo do
permit_params :image, , rooms_attributes: [:id, :listing_name, :room_id, :photo_id, :images]
index do
selectable_column
id_column
column :image_file_name
column :listing_name
column :room
column :updated_at
column :created_at
actions
end
show do |image|
attributes_table do
row :image do
photo.image? ? image_tag(photo.image.url, height: '100') : content_tag(:span, "No photo yet")
end
end
active_admin_comments
end
form :html => { :enctype => "multipart/form-data" } do |f|
f.inputs do
f.input :image, hint: f.photo.image? ? image_tag(f.photo.image.url, height: '100') : content_tag(:span, "Upload JPG/PNG/GIF image")
end
f.inputs do
f.collection_select :room, Room.all,:id,:listing_name
end
f.actions
end
The form seems to work but it does not save it to the database when I check the record(last room) in the rails console it always returns me room_id: nil? I have tried everything nothing seems to work. Please help.
UPDATE
I have added, "rooms_attributes: [:id, :listing_name, :room_id, :photo_id, :images]" to the params in the photo.rb(admin). I have also have added :photo_id to the room.rb(admin) file.
But it still does not work! Any hints welcomed! If someone needs further information just let me know.
Try
permit_params :image, room_id
f.collection_select :room_id, Room.all, :id, :listing_name

Paperclip will delete current image even though it's not changed on update

I'm using Paperclip to let my users upload images.
It works pretty well with separated single resources. I can add, edit, and delete all images without any extra effort using a RESTFUL structure.
But I have some difficulties while working with nested attributes.
I have 2 models called Article and Headline.
I'm using an Article form in my view to create an associated instance of Headline. So I'm using accepts_nested_attributes_for with ActiveRecord. I've set up everything related to this. I'm using fields_for in my form view. Strong parameters settings are all done. Everything works great with adding a NEW article with its headline. I also have a title field which updates appropriately.
<%= f.fields_for :headline do |r| %>
<%= r.label :title %>
<%= r.text_field :title %>
<%= r.label :image, "Headline image" %>
<%= r.file_field :image %>
<% end %>
But when I UPDATE an article without any change of the Headline's image, the Headline's image disappears.
As I know and experience, Paperclip doesn't update image files unless they are changed. That's the expected behavior. But in this case, it overwrites the nested model's attribute even though it shouldn't.
I think there must be a workaround for this?
Article.rb:
class Article < ActiveRecord::Base
after_commit :expire_caches
scope :desc, ->{ order(created_at: :desc)}
scope :with_images, -> {where.not(image_file_name: nil)}
before_validation :set_default_category
acts_as_taggable
attr_accessor :is_headline
has_attached_file :image, default_url: "default_:style.png",
:styles => { :large=>"700", :medium => "296", :thumb => "150", mini_thumb: "50x50>", :absolute_thumb=>"150x150#" },
:s3_host_name => 's3-eu-west-1.amazonaws.com',
:path => "articles_images/:id/:style/:basename.:extension"
#validates :title, presence: true
belongs_to :category
belongs_to :user
validates :title, :content, presence: true
validates :content, length: {minimum: 10}
validates :category_id, presence: true
validates :cihan_url, uniqueness: true, allow_nil: true
has_many :galleries, dependent: :destroy
has_one :headline, dependent: :destroy
has_one :ad, dependent: :destroy
has_many :videos
accepts_nested_attributes_for :headline
def self.main_page
where(main_page: true).with_images.desc
end
def to_param
"#{self.id}-#{self.title.parameterize}"
end
private
def set_default_category
self.category = Category.uncategorized if self.category.blank?
end
def expire_caches
Rails.cache.clear
end
end
Headline.rb:
class Headline < ActiveRecord::Base
after_commit :expire_caches
has_attached_file :image, default_url: "default_:style.png",
:styles => { :large=>"600", :medium => "300x300>", :thumb => "150x150>", :absolute_thumb=>"150x150#" },
:s3_host_name => 's3-eu-west-1.amazonaws.com',
:path => "headline_images/:id/:style/:basename.:extension"
belongs_to :article
private
def expire_caches
Rails.cache.clear
end
end
From articles_controller:
def set_article
#article = Article.find(params[:id])
end
def update
if #article.update(article_params)
redirect_to [:admin, #article], flash: {success: "<i class=\"icon-ok\"></i> Haber başarıyla düzenlendi.".html_safe}
else
render action: 'edit'
end
end
def article_params
params.require(:article).permit(:title, :content, :publish_date, :category_id, :active, :tag_list, :main_page, :extra_title, :image, :is_headline, headline_attributes: [:title, :counter, :order, :image])
end

rails 3 polymorphic association with paperclip and multiple models

I want to make polymorphic associations with paperclip, and allow my user to have one avatar and multiple images.
Attachment model:
class Attachment < ActiveRecord::Base
belongs_to :attachable, :polymorphic => true
end
class Avatar < Attachment
has_attached_file :image, :styles => { :thumb => "150x150>", :view => "260x180>" },
end
class Image < Attachment
has_attached_file :image, :styles => { :thumb => "150x150>", :view => "260x180>" },
end
User Model:
has_one :avatar, :as => :attachable, :class_name => 'Attachment', :conditions => {:type => 'avatar'}
accepts_nested_attributes_for :avatar
User Controller:
def edit
#user.build_avatar
end
User View form:
<%= form_for #user, :html => { :multipart => true } do |f| %>
<%= f.fields_for :avatar do |asset| %>
<% if asset.object.new_record? %>
<%= asset.file_field :image %>
<% end %>
<% end %>
when I attempt to save the changes I get the error => unknown attribute: avatar
if I remove the :class_name => 'attachment' in the has_one association I get the error =>
uninitialized constant User::Avatar
I need to also attach avatars to blog posts, so I need the association to be polymorphic (or atleast i think so)
I am stumped and any help would be greatly appreciated.
I do have a project in the works that is successfully using Paperclip and polymorphic associations. Let me show you what I have, and maybe you can apply it to your project:
class Song < ActiveRecord::Base
...
has_one :artwork, :as => :artable, :dependent => :destroy
accepts_nested_attributes_for :artwork
...
end
class Album < ActiveRecord::Base
...
has_one :artwork, :as => :artable, :dependent => :destroy
accepts_nested_attributes_for :artwork
...
end
class Artwork < ActiveRecord::Base
belongs_to :artable, :polymorphic => true
attr_accessible :artwork_content_type, :artwork_file_name, :artwork_file_size, :artwork
# Paperclip
has_attached_file :artwork,
:styles => {
:small => "100",
:full => "400"
}
validates_attachment_content_type :artwork, :content_type => 'image/jpeg'
end
the songs form and the albums form include this as a partial:
<div class="field">
<%= f.fields_for :artwork do |artwork_fields| %>
<%= artwork_fields.label :artwork %><br />
<%= artwork_fields.file_field :artwork %>
<% end %>
don't forget to include :html => { :multipart => true } with the form
artworks_controller.rb
class ArtworksController < ApplicationController
def create
#artwork = Artwork.new(params[:artwork])
if #artwork.save
redirect_to #artwork.artable, notice: 'Artwork was successfully created.'
else
redirect_to #artwork.artable, notice: 'An error ocurred.'
end
end
end
and finally, an excerpt from songs_controller.rb:
def new
#song = Song.new
#song.build_artwork
end
I'm not sure you really need to be polymorphic. How about this approach, which uses has_many :through? In plain English, the user has one avatar which has multiple images, and through this association you can call User.images to get the collection of images associated with the avatar.
http://guides.rubyonrails.org/association_basics.html
class User < ActiveRecord::Base
has_one :avatar
has_many :images, :through => :avatar
end
class Avatar < ActiveRecord::Base
belongs_to :user
has_many :images
end
class Image < ActiveRecord::Base
belongs_to :avatar
has_attached_file :image, :styles => { :thumb => "150x150>", :view => "260x180>" },
end
Having said all of this, I am left to wonder why you need to go through all this anyway. Why not just do
class User < ActiveRecord::Base
has_many :avatars
end
which would give you as many images (avatars) as you want.

How do I bypass validation rules set for a nested attribute in ruby on rails?

I have a micropost form which allows a user to upload a photo and type some content to go with it. The image file field is the nested attribute from my photo model.
It has a validation rule "presence => true". This is not required for microposts. User are allowed to post microposts without images/photos.
How ever I use the same photo model for the users image gallery and a photo is required at the time of form submission so I can't disable this rule.
Is there any way to bypass the validation rule set in my photo model for when I post form the micropost form?
Controller:
def new
#user = User.new
#micropost = Micropost.new(:user_id => users_id)
#micropost.build_photo(:photo_album_id => current_user.photo_albums.find_by_album_title("microposts album").id)
end
Form:
= form_for #micropost, :html => { :multipart => true }, :remote => true do |f|
= f.fields_for :photo do |p|
= p.hidden_field :photo_album_id
= p.text_field :photo_title
= p.file_field :image, :id => "micropost_image"
= f.hidden_field :user_id
= f.text_area :content
= f.submit "Post"
Micropost model:
class Micropost < ActiveRecord::Base
belongs_to :user
has_many :comments, :dependent => :destroy
has_one :photo, :dependent => :destroy
accepts_nested_attributes_for :photo
attr_accessor :username
attr_accessible :content, :user_id, :poster_id, :username, :remote_image_url, :photo_attributes
validates :content, :presence => true, :length => { :maximum => 10000 }
validates :user_id, :presence => true
end
Photo model:
class Photo < ActiveRecord::Base
belongs_to :photo_album
attr_accessible :photo_album_id, :photo_title, :image, :remote_image_url
mount_uploader :image, ImageUploader
alpha_num_non_word_char = /^[a-zA-Z0-9\D_ -]*$/
validates :image, :presence => true
validates :photo_title, :length => { :minimum => 2, :maximum => 50 },
:format => { :with => alpha_num_non_word_char,
:message => "error"
}, :if => :photo_title?
validate :picture_size_validation, :if => "image?"
def picture_size_validation
errors[:image] << "Your photo should be less than 1MB" if image.size > 1.megabytes
end
end
Kind regards
There's an option, :reject_if, you can pass to accepts_nested_attributes_for so that it won't try to create a new photo under certain conditions. It would work like this:
accepts_nested_attributes_for :photo, :reject_if => proc { |attributes| attributes['image'].blank? }
Since you specified the :id of the image field as being 'micropost_image', you might have to reference it within the proc like this instead:
attributes['micropost_image']
One of those two should work.

Resources