Paperclip: Stay put on edit - ruby-on-rails

When a user edits something in my application, they're forced to re-upload their image via paperclip even if they aren't changing it. Failing to do so will cause an error, since I validate_presence_of :image. This is quite annoying.
How can I make it so Paperclip won't update its attributes if a user simply doesn't supply a new image on an edit?
The photo controller is fresh out of Rails' scaffold generator. The rest of the source code is provided below.
models/accommodation.rb
class Accommodation < ActiveRecord::Base
attr_accessible :photo
validates_presence_of :photo
has_one :photo
has_many :notifications
belongs_to :user
accepts_nested_attributes_for :photo, :allow_destroy => true
end
controllers/accommodation_controller.rb
class AccommodationsController < ApplicationController
def index
#accommodations = Accommodation.all
end
def show
#accommodation = Accommodation.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:error] = "Accommodation not found."
redirect_to :home
end
def new
#accommodation = current_user.accommodations.build
#accommodation.build_photo
end
def create
#accommodation = current_user.accommodations.build(params[:accommodation])
if #accommodation.save
flash[:notice] = "Successfully created your accommodation."
redirect_to #accommodation
else
#accommodation.build_photo
render :new
end
end
def edit
#accommodation = Accommodation.find(params[:id])
#accommodation.build_photo
rescue ActiveRecord::RecordNotFound
flash[:error] = "Accommodation not found."
redirect_to :home
end
def update
#accommodation = Accommodation.find(params[:id])
if #accommodation.update_attributes(params[:accommodation])
flash[:notice] = "Successfully updated accommodation."
redirect_to #accommodation
else
#accommodation.build_photo
render :edit
end
end
def destroy
#accommodation = Accommodation.find(params[:id])
#accommodation.destroy
flash[:notice] = "Successfully destroyed accommodation."
redirect_to :inkeep
end
end
models/photo.rb
class Photo < ActiveRecord::Base
attr_accessible :image, :primary
belongs_to :accommodation
has_attached_file :image,
:styles => {
:thumb=> "100x100#",
:small => "150x150>" }
end

You shouldn't need #accommodation.build_photo anywhere else than in new action.

Related

How do I create a page without a model?

I'm working on an app which has many 'Activities'. Each 'Activity' has many 'Ranks'. I'd like each 'Activity' to have a page called grading, where the user can see a list of all of that activity's ranks and conveniently update them. I imagine the URL would be something like http://localhost:3000/activities/21/grading
I'm already using http://localhost:3000/activities/21/edit for its intended purpose.
I don't need a model for gradings, as I don't need to save any grading records.
I know exactly what to put in the view, I'm just unsure what to add to the controller and routes files. Other people have worked on this app but I'm unable to contact them.
Routes
resources :activities do
collection do
get 'scheduled_classes'
end
end
resources :ranks
end
activities_controller
class ActivitiesController < ApplicationController
def new
#activity = Activity.new
#activity.timeslots.build
#activity.ranks.build
end
def create
#activity = current_club.activities.new(activity_params)
if #activity.save
flash[:success] = "New class created!"
redirect_to activity_path(#activity)
else
render 'new'
end
end
def edit
#activity = current_club.activities.find_by(id: params[:id])
#active_ranks = #activity.ranks.where(active: true)
if !#activity.active?
redirect_to activities_path
else
#activity.timeslots.build
end
end
def update
#activity = current_club.activities.find_by(id: params[:id])
if #activity.update_attributes(activity_params)
flash[:success] = "Class updated!"
redirect_to edit_activity_path(#activity)
else
render 'edit'
end
end
def show
#activity = current_club.activities.find_by(id: params[:id])
#active_ranks = #activity.ranks.where(active: true)
if #activity.nil?
redirect_to root_url
elsif !#activity.active?
redirect_to activities_path
end
end
def index
#activities = current_club.activities.all
end
def destroy
#activity = current_club.activities.find_by(id: params[:id])
if #activity.nil?
redirect_to root_url
else
#activity.destroy
flash[:success] = "Class deleted"
redirect_to activities_path
end
end
end
private
def activity_params
params.require(:activity).permit(:name, :active,
:timeslots_attributes => [:id,
:time_start,
:time_end,
:day,
:active,
:schedule],
:ranks_attributes => [:id,
:name,
:position,
:active])
end
end
activity
class Activity < ApplicationRecord
belongs_to :club
has_many :timeslots, dependent: :destroy
accepts_nested_attributes_for :timeslots,:allow_destroy => true
has_many :ranks, dependent: :destroy
has_many :attendances, dependent: :destroy
accepts_nested_attributes_for :ranks
validates :club_id, presence: true
validates :name, presence: true, length: { maximum: 50 }
end
Your routes don't need to have an associated model or resource.
resources :activities do
collection do
get 'scheduled_classes'
end
member do
get :grading
end
end
will match to activities#grading
See https://guides.rubyonrails.org/routing.html#adding-member-routes for more info.
As you want to add a route on a particular activity, you should add member route on the activity like below,
resources :activities do
collection do
get 'scheduled_classes'
end
get :grading, on: :member
end
Apart from this, you have to add method in ActivitiesController for this route like below,
def grading
#activity = Activity.find_by(id: params[:id])
# do more here
end
In view files, you can create grading.html.erb under activities resources and put your view code there.

Why is paperclip upload giving Trying to link error?

So I have these files
deal.rb
class Deal < ApplicationRecord
has_many :images, as: :imageable, dependent: :destroy
#there is more code after this
end
image.rb
class Image < ApplicationRecord
belongs_to :imageable, polymorphic: true
belongs_to :deal
has_attached_file :attachment, styles: { thumb: "100x100!", medium: "200x200!" }
validates_attachment_content_type :attachment, content_type: /\Aimage\/.*\z/
end
deals_controller.rb
module Admins
class DealsController < BaseController
before_action :find_deal, only: [:edit, :update, :destroy]
def index
#deals = Deal.includes(:images)
end
def new
#deal = Deal.new
end
def edit
end
def create
#deal = Deal.new(deal_params.merge(created_by: current_user.id))
if #deal.save
flash[:success] = t('.success')
redirect_to admins_deals_url
else
flash.now[:warning] = t('.failure')
render :new
end
end
def update
if #deal.update(deal_params)
flash[:success] = t('.success')
redirect_to admins_deals_url
else
flash.now[:warning] = #deal.errors[:base].to_sentence
render :edit
end
end
def destroy
if #deal.destroy
flash[:success] = t('.success')
redirect_to admins_deals_url
else
flash.now[:warning] = t('.failure')
render :index
end
end
private
def deal_params
params.require(:deal).permit(:title, :description, :price, :discounted_price, :quantity, :publish_date, images_attributes: [:id, :attachment, :_destroy])
end
def find_deal
#deal = Deal.find_by(id: params[:id])
unless #deal
flash[:warning] = t('deals.not_found')
redirect_to admins_deals_path
end
end
end
end
application_controller.rb
class ApplicationController < ActionController::Base
helper_method :current_user, :current_cart
def current_user
#current_user ||= User.find_by(id: current_user_id)
end
def current_user_id
cookies.signed[:user_id] || session[:user_id]
end
def current_cart
#current_cart ||= (current_user.addressed_cart || current_user.cart) if current_user
end
end
EDIT:
Although I don't think application_controller has anything to do with the error
I am creating a deal with nested image attributes. I am using paperclip to upload the images. But I am getting these errors. I don't have any idea what the errors even mean. Here is an image to show the errors.
Here is the pastebin link
errors on terminal on creating deal
This appears to be a validation error. Try this for your validation:
validates_attachment_content_type :attachment, :content_type => /image/
Or for other variations you can see Validate Attachment Content Type Paperclip
UPDATE after testing your code seems this was a validation error because Paperclip creates an image but doesn't know about the belongs_to association. You can make it optional because by default rails 5 requires the belongs_to id field.
class Image < ApplicationRecord
belongs_to :imageable, polymorphic: true
belongs_to :deal, optional: true
has_attached_file :attachment, styles: { thumb: "100x100!", medium: "200x200!" }
validates_attachment_content_type :attachment, content_type: /\Aimage\/.*\z/
end

"undefined method `slug'" when trying to save nested route using Friendly_id 5

My App allows a user to create an Event and people can RSVP to the event. The app worked well before adding the Friendly_id gem, it works well as far as creating a new Event, but the RSVPs do not work as when you click submit you get an error. Please look at my code and see if there is anything that I am missing. Thank you in advance.
Event Model
class Event < ApplicationRecord
extend FriendlyId
friendly_id :eventname, use: [:slugged, :finders]
belongs_to :user
def should_generate_new_friendly_id?
eventname_changed?
end
has_attached_file :image, styles: { medium: "300x300>", thumb: "100x100>" }, default_url: "/images/:style/placeholder.png"
validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
validates :eventname, presence: true
has_many :rsvps, dependent: :destroy
end'
Rsvp Model
class Rsvp < ApplicationRecord
extend FriendlyId
friendly_id :eventname, use: [:slugged, :finders]
belongs_to :event
end
Events Controller
class EventsController < ApplicationController
before_filter :authenticate_user!, except: [:show]
def index
#events = current_user.events.all
end
def show
#event = Event.friendly.find(params[:id])
end
def new
#event = current_user.events.build
end
def create
#event = current_user.events.build(event_params)
#event.user = current_user
respond_to do |format|
if #event.save
format.html { redirect_to #event, notice: "Successfully created" }
else
format.html { render "new" }
end
end
end
def edit
#event = Event.friendly.find(params[:id])
end
def update
#event = Event.friendly.find(params[:id])
if #event.update(event_params)
redirect_to #event
else
render 'edit'
end
end
def destroy
#event = Event.friendly.find(params[:id])
#event.destroy
redirect_to events_path
end
private
def event_params
params.require(:event).permit(:eventname, :date, :time, :venue, :description, :image)
end
end
Rsvp Controller
class RsvpsController < ApplicationController
def index
event = Event.friendly.find(params[:event_id])
#rsvps = event.rsvps
end
def new
event = Event.friendly.find(params[:event_id])
#rsvp = event.rsvps.friendly.build
respond_to do |format|
format.html
end
end
def create
event = Event.friendly.find(params[:event_id])
#rsvp = event.rsvps.build(rsvp_params)
respond_to do |format|
if #rsvp.save
format.html { redirect_to "/thanks" }
format.js
else
format.html { render :new }
format.js
end
end
end
def thanks
render params[:page]
end
private
def rsvp_params
params.require(:rsvp).permit(:status, :name, :message)
end
end
Routes
resources :events do
resources :rsvps
end
show event:
http://127.0.0.1:3000/events/birthday
create rsvp for event:
http://127.0.0.1:3000/events/3/rsvps/new
After submit error shows on:
http://127.0.0.1:3000/events/birthday/rsvps
Screenshot of Error I get
I managed to resolve this issue by updating the following:
class Rsvp < ApplicationRecord
belongs_to :event
end
changed this:
new_event_rsvp_path(event_id: #event.id)
to this:
new_event_rsvp_path(#event)
Creating new RSVP now shows in the Friendly_id way:
http://127.0.0.1:3000/events/birthday/rsvps/new

Rails correctly deleting database item but still raising error

I have an issue with my database in a Rails app.
The problem is when I press delete on an uploaded image that it raises the error in the browser, shown below.
What is confusing me is:
A) When I check the items using the Rails console, it shows the item has been deleted.
B) The error refers to items that never existed, ie, id=3 when there were only ever two items present.
Error
ActiveRecord::RecordNotFound in PostsController#destroy
Couldn't find Post with id=4
Extracted source (around line #24):
def destroy
#post = Post.find params[:id]
#post.destroy
redirect_to 'posts/'
end
Posts Controller
class PostsController < ApplicationController
def index
#posts = Post.all
end
def new
authenticate_user!
#post = Post.new
end
def create
#post = Post.new(post_params)
if #post.save
redirect_to '/posts'
else
render 'new'
end
end
def destroy
#post = Post.find params[:id]
#post.destroy
redirect_to 'posts/'
end
private
def post_params
params.require(:post).permit(:description, :picture)
end
end
Post Model
class Post < ActiveRecord::Base
has_attached_file :picture, styles: { medium: "300x300>", thumb: "100x100>" }
validates :description, presence: true
validates_attachment_content_type :picture, content_type: ["image/jpg", "image/jpeg", "image/png"]
has_many :comments
end
Create Posts Migrate
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.text :description
t.timestamps
end
end
end
Not sure what exactly is going wrong, but it seems to be raising an error on a method that is to all intents and purposes working just fine and also something appears to be going wrong with the id assignment in the database.
Try this:--
In Posts Controller
def destroy
#post = Post.find params[:id]
#post.destroy
redirect_to '/posts'
end
If above doesnot work then try the below
def destroy
#post = Post.find params[:id]
#post.destroy
redirect_to posts_path
end

customize gallery so that it belongs to one user

I am using CarrierWave and as of now the gallery is open to the public with no ownership. I want to setup so that for one, the user does not have to create a Gallery. The only option should be to upload photos to their account and I want to limit each user photo uploads to 5 maximum. So if User 16 signs in, they have option to upload up to 5 photos to their profile. Once that limit is reach, if the user tries to upload more it should say "Maximum photos uploaded, delete to upload more". I'm not sure exactly how to pull this off.
photo.rb model:
class Photo < ActiveRecord::Base
attr_accessible :title, :body, :gallery_id, :name, :image, :remote_image_url
has_many :user, :through => :gallery
has_many :gallery
mount_uploader :image, ImageUploader
LIMIT = 5
validate do |record|
record.validate_photo_quota
end
def validate_photo_quota
return unless self.user
if self.gallery.user(:reload).count >= LIMIT
errors.add(:base, :exceeded_quota)
end
end
end
photo controller:
class PhotosController < ApplicationController
def new
#photo = Photo.new(:gallery_id => params[:gallery_id])
end
def create
#photo = Photo.new(params[:photo])
if #photo.save
flash[:notice] = "Successfully created photos."
redirect_to #photo.gallery
else
render :action => 'new'
end
end
def edit
#photo = Photo.find(params[:id])
end
def update
#photo = Photo.find(params[:id])
if #photo.update_attributes(paramas[:photo])
flash[:notice] = "Successfully updated photo."
redirect_to #photo.gallery
else
render :action => 'edit'
end
end
def destroy
#photo = Photo.find(params[:id])
#photo.destroy
flash[:notice] = "Successfully destroyed photo."
redirect_to #photo.gallery
end
end
galleries controller:
class GalleriesController < ApplicationController
def index
#galleries = Gallery.all
end
def show
#gallery = Gallery.find(params[:id])
end
def new
#gallery = Gallery.new
end
def create
#gallery = Gallery.new(params[:gallery])
if #gallery.save
flash[:notice] = "Successfully created gallery."
redirect_to #gallery
else
render :action => 'new'
end
end
def edit
#gallery = Gallery.find(params[:id])
end
def update
#gallery = Gallery.find(params[:id])
if #gallery.update_attributes(params[:gallery])
flash[:notice] = "Successfully updated gallery."
redirect_to gallery_url
else
render :action => 'edit'
end
end
def destroy
#gallery = Gallery.find(params[:id])
#gallery.destroy
flash[:notice] = "Successfully destroy gallery."
redirect_to galleries_url
end
end
Restricting user access
To restrict user access to certain models I would use something like CanCan.
It would let you do stuff like this:
## ability.rb
# Allow user to CRUD pictures belonging to own gallery
can :manage, Picture, gallery: {user: user}
In the controller you can then do stuff like this:
# picture_controller.rb
# assuming a nested route, e.g. galleries/1/pictures
load_and_authorize_resource :gallery
load_and_authorize_resource :picture, through: :gallery
This will make sure that each user only sees his or her own pictures.
Restricting number of pictures in gallery
I think your approach with the validation is okay.
I would simplify it thus:
validate :quota
private
def quota
return unless user
if gallery.pictures.count > 4
errors[:base] << "Maximum photos uploaded, delete to upload more"
end
end
The error message should probably go into a locale file.
Creating Gallery automatically for each user
To do this, make sure that the Gallery model has a belong_to association to User. Then create the gallery in a callback in the User model:
# models/user.rb
after_create :setup_gallery
private
def setup_gallery
Gallery.create(user: self)
end
General notes
When you define your has_many relations, you should use plural names, like has_many :users or has_many :galleries.

Resources