I've been working on this all night and it makes no sense. I'm adapting an old photo web app to have albums in it. I made "fails" (basically images) a nested resource of albums. I am using carrierwave to upload files to an S3 bucket.
the weird thing is: the upload works perfectly fine for the album model (album image), but it doesn't upload for the fail model.
I don't see why it'd be a problem that it's a nested resource now. It's not a problem displaying it's that for some reason, it goes through the form fine, passes validations fine, no errors are thrown, it redirects to fails#index like it was successful, but there is nothing in the db or in S3.
Code is below. All code at https://github.com/spq24/failboard
Fail Model
class Fail < ActiveRecord::Base
attr_accessible :description, :image, :remote_image_url, :fail_title, :tag_list, :processed, :youtube_url, :album_id
make_voteable
acts_as_taggable
belongs_to :album
mount_uploader :image, ImageUploader
validates :description, length: { :maximum => 200 }
validates :album_id, presence: true
validates :image, presence: true
validates :fail_title, presence: true, length: { :maximum => 50 }
validate :maximum_amount_of_tags
def maximum_amount_of_tags
number_of_tags = tag_list_cache_on("tags").uniq.length
errors.add(:base, "Please only add up to 5 tags") if number_of_tags > 5
end
before_save :update_attachment_attributes
def update_attachment_attributes
if image.present? && image_changed?
self.content_type = image.file.content_type
self.file_size = image.file.size
end
end
def next
user.fails.where("id > ?", id).order("id ASC").first
end
def prev
user.fails.where("id < ?", id).order("id DESC").first
end
end
Album Model
class Album < ActiveRecord::Base
attr_accessible :name, :image, :image_url, :created_at
belongs_to :user
has_many :fails, dependent: :destroy
mount_uploader :image, ImageUploader
validates :user_id, presence: true
validates :image, presence: true
validates :name, presence: true, length: { :maximum => 50 }
before_save :update_attachment_attributes
def update_attachment_attributes
if image.present? && image_changed?
#self.content_type = image.file.content_type
#self.file_size = image.file.size
end
end
def next
user.fails.where("id > ?", id).order("id ASC").first
end
def prev
user.fails.where("id < ?", id).order("id DESC").first
end
end
Fails Controller
def new
#fail = Fail.new(:album_id => params[:album_id])
respond_to do |format|
format.html # new.html.erb
format.json { render json: #fail }
end
end
def create
#fail = Fail.new(params[:fail])
respond_to do |format|
if #fail.save
format.html { redirect_to #fail.album, notice: 'You added a new photo!' }
format.json { render json: #fail, status: :created, location: #fail }
else
format.html { render action: "new" }
format.json { render json: #fail.errors, status: :unprocessable_entity }
end
end
end
routes.rb
resources :albums do
get 'tags/:tag', to: 'fails#index', as: :tag
resources :fails do
member do
post :up_vote
end
end
Debug Hash (this turns red when I try to upload, but I don't see anything that would cause the error)
Here is the debug info:
{"utf8"=>"✓", "authenticity_token"=>"Hz6Gl95ultYDNIEjQioIckB8JXQwhiMxXIM9jrfqd5Q=", "fail"=>{"fail_title"=>"tester", "image"=>#<ActionDispatch::Http::UploadedFile:0x56195e8 #original_filename="pic19.jpg", #content_type="image/jpeg", #headers="Content-Disposition: form-data; name=\"fail[image]\"; filename=\"pic19.jpg\"\r\nContent-Type: image/jpeg\r\n", #tempfile=#<File:C:/Users/Kinertia/AppData/Local/Temp/RackMultipart20131125-10428-m2ktp2>>, "description"=>"", "tag_list"=>"test"}, "commit"=>"Create Fail", "controller"=>"fails", "action"=>"index"}
If there is anything else needed please let me know and I will put it here. Thank you for all the help!
Have you tried validating the integrity or processing of the fail image?
validates_integrity_of :avatar
validates_processing_of :avatar
validates_download_of :avatar
By default it fails silently, which kinda sucks.
I also recommend trying to create the record in the rails console, which can help to isolate the problem to either the model or view/controller layers. In your case this would look something like:
Fail.create!(
image: File.open('path/to/known/file.jpg'),
album_id: 1,
fail_title: 'Title'
)
Related
I have a model with a Logo as attachment.
When user uploads an image, the validation name and url validation should be skipped.
The validates :logo should be still validated
Is this possible, and if yes: How?
Thank you!
class MyModel < ApplicationRecord
belongs_to :user
has_one_attached :logo do |attachable|
attachable.variant :thumbnail, resize_to_fit: [100, 100]
end
validates :logo, content_type: %w[image/png image/jpg image/jpeg]
# following line should be skipped
validates_presence_of :name, :url # this should be skipped when attaching an image !!!
end
# MyModelController
# That is my current controller action
# PATCH /shops/update_logo
def update_logo
if logo_params[:logo].present?
logo = logo_params[:logo]
if logo && #current_user.my_model.logo.attach(logo)
render json: #current_user, status: :ok
else
render json: #current_user.my_model.errors, status: :unprocessable_entity, error: #current_user.my_model.errors.full_messages.join(', ')
end
else
render json: { error: "no image selected" }, status: :unprocessable_entity
end
end
Some hack with :on
class MyModel < ApplicationRecord
validates :name, :url, presence: true, on: :not_attachment
validates :logo, content_type: %w[image/png image/jpg image/jpeg]
end
my_model.name = my_model.url = nil
my_model.logo.attach(logo)
my_model.valid? # => true
my_model.name = my_model.url = nil
my_model.save(context: :not_attachment)
my_model.valid? # => false
I have multiple methods within my controller that takes in query parameters. How can I validate that I am being passed in valid parameters? For example, for the index method, how can I make sure that I am getting an array of authorIds.
def index
author_ids_array = params[:authorIds].to_s.split(',')
posts = Post
.get_posts_by_user_id(author_ids_array)
.order(sort_column => sort_direction)
if posts
render json: { posts: posts }, status: :ok
else
render json: {error: posts.errors}, status: :unprocessable_entity
end
end
Or in this update method. How can I validate that I am getting a valid postId
def update
post = current_user.posts.find_by(id: params[:id])
if post.update!(post_params)
post_hash = post.as_json
post_hash.merge!(authorIds: params[:authorIds])
render json: {post: post_hash}, status: :ok
else
render json: {error: post.errors}, status: :unprocessable_entity
end
end
Update:
Post Model:
class Post < ApplicationRecord
# Associations
has_many :user_posts
has_many :users, through: :user_posts, dependent: :destroy
# Validations
validates :text, presence: true, length: { minimum: 3 }
validates :popularity, inclusion: { in: 0.0..1.0 }
def tags
if super
super.split(",")
end
end
def tags=(value)
if value.kind_of? Array
super value.join(",")
else
super value
end
end
def self.get_posts_by_user_id(user_id)
Post.joins(:user_posts).where(user_posts: { user_id: user_id })
end
end
User Model:
class User < ApplicationRecord
has_secure_password
# Associations
has_many :user_posts
has_many :posts, through: :user_posts, dependent: :destroy
# Validations
validates :username, :password, presence: true
validates :password, length: { minimum: 6 }
validates :username, uniqueness: true
end
User_post Model:
class UserPost < ApplicationRecord
belongs_to :user
belongs_to :post
end
You can use specific render like below user this in any method like
def index
return render body: params.inspect
.
.
end
user below code
return render body: params.inspect
so when you use index it will give you params which is passing
OR you can user below code in your application.html.erb above <%= yield%>
<%= debug(params) if Rails.env.development? %>
After your clarifications, your question remains unclear to me and it is difficult to guess what you're doing. But I understood that you want to ensure that params[:authorIds] or anything else is an array.
You can see if a given variable is an array the following way:
a = ["1","2"]
if a.is_a?(Array)
puts "is an array"
end
With params: params[:authorIds].is_a?(Array)
You can use byebug (before Rails 7) or debugger (for Rails 7) to inspect what a param is. As an example:
(ruby#whatever: cluster worker 1: 42779 [MyApp]#42793) params[:ids].class
Array
I'm having an issue with my ROR 4 application. Here's a quick background:
The application has several classes, Users, Training_Events and Mission_Notes.
A Training event can be associated with multiple users from a multi-select drop-down which builds an array of user_ids which are then saved to the Training_Event, whilst Mission_Notes can only be associated with one User and one Training_Event. Models below:
MissionNote.rb
class MissionNote < ActiveRecord::Base
belongs_to :training_event
belongs_to :user
end
User.rb
class User < ActiveRecord::Base
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
belongs_to :group
has_many :ranks
has_many :mission_notes
has_and_belongs_to_many :training_events
validates :username, presence: true
validates :email, presence: true
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :group_id, presence: true
has_secure_password
validates :password, length: { minimum: 6 }, allow_blank: true
end
TrainingEvent.rb
class TrainingEvent < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :mission_notes
validates :title, presence: true
validates :date, presence: true
validates :mission, presence: true
validates_format_of :video, :with => /\A(https\:\/\/)?((www\.)?youtube\.com|youtu\.?be)\/.+$\Z/, :allow_blank => true, :message => "must be a valid YouTube URL"
validates_format_of :date, :with => /\A((19|20)\d\d+)-(0[1-9]|1[012]+)-(0[1-9]|[12][0-9]|3[01])\Z/
end
What I then want it to do is on the user's profile display a list of the events that the particular user has been associated with and the mission_notes for each event. The issue I have is when I save the training event the user_id field is not saved in the database however if I do TrainingEvent.all.each{|x| x.user_ids} then I get an array of the user_ids which were saved.
Can someone help point out what I am doing wrong here and maybe help clarify while the single user_id finds nothing but user_ids returns at least an array of items.
------------------- Edit ------------------------------------
Training_Events_Controller.rb
class TrainingEventsController < ApplicationController
before_action :set_training_event, only: [:show, :edit, :update, :destroy]
before_action :admin_user, only: [:new, :edit, :update]
# GET /training_events
# GET /training_events.json
def index
#training_events = TrainingEvent.all
end
# GET /training_events/1
# GET /training_events/1.json
def show
#user_ids = #training_event.user_ids
#user = User.find(#user_ids)
#mission_notes = MissionNote.find_by(user_id: #user)
byebug
end
# GET /training_events/new
def new
#training_event = TrainingEvent.new
#user_options = User.all.map{|u| [ u.username, u.id ] }
end
# GET /training_events/1/edit
def edit
#user_options = User.all.map{|u| [ u.username, u.id ] }
end
# POST /training_events
# POST /training_events.json
def create
#training_event = TrainingEvent.new(training_event_params)
respond_to do |format|
if #training_event.save
format.html { redirect_to #training_event, notice: 'Training event was successfully created.' }
format.json { render :show, status: :created, location: #training_event }
else
format.html { render :new }
format.json { render json: #training_event.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /training_events/1
# PATCH/PUT /training_events/1.json
def update
respond_to do |format|
if #training_event.update(training_event_params)
format.html { redirect_to #training_event, notice: 'Training event was successfully updated.' }
format.json { render :show, status: :ok, location: #training_event }
else
format.html { render :edit }
format.json { render json: #training_event.errors, status: :unprocessable_entity }
end
end
end
# DELETE /training_events/1
# DELETE /training_events/1.json
def destroy
#training_event.destroy
respond_to do |format|
format.html { redirect_to training_events_url, notice: 'Training event was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_training_event
#training_event = TrainingEvent.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def training_event_params
params.require(:training_event).permit(:title, :date, :training_objective, :mission, :video, :user_ids => [])
end
end
Also as an additional can someone suggest the best way to then put this on the view in the way I described above, I realise this part is a little vague but it has sent me round the bend as I seem to get a repeating list of events and notes. Will attach view code when I have second
Since you are using HABTM, you will not have an automatic attribute of "User_ID"... You will however have "user_ids" because you have told it that there is a join table describing a many to many relationship. I suspect that when you are saving the training event you are trying to update a "user_id" attribute.. You should instead be adding the current user_id to the user_ids array attribute that represents the relation. :)
Hope this helps!
The model TrainingEvent has the attribute user_ids which i assume is an array instead of user_id.
What I suggest is receive the array of user_ids not saved but accessed using attr_accessor :user_ids then on creating Training Events, iterate through the user_ids and save each TrainingEvent with the respective user_id.
Make sure user_id is an attribute and not user_ids.
Attachments saved using Paperclip (v4.2.0) are not being deleted from disk when the model is destroyed, is anybody else experiencing this issue? Everything is working as expected but the files just aren't getting deleted from disk. Any help or ideas would be super appreciated!
Models:
class Attachment < ActiveRecord::Base
belongs_to :article
has_attached_file :file, { :preserve_files => "false" }
do_not_validate_attachment_file_type :file
end
class Article < ActiveRecord::Base
belongs_to :topic
belongs_to :subtopic
belongs_to :author
has_many :attachments, :dependent => :destroy
accepts_nested_attributes_for :attachments, allow_destroy: true, reject_if: lambda { |a| a[:file].blank? }
validates :topic_id, presence: true
validates :title, presence: true, length: { maximum: 16 }
validates :subtitle, length: { maximum: 20 }
validates :content, presence: true
end
Destroy action in Articles controller:
def destroy
#article = Article.find(params[:id])
begin
# first delete the attachments from disk
#article.attachments.each do |a|
a.file.destroy
end
#article.destroy
rescue
flash[:danger] = "Unable to delete article"
else
flash[:success] = "Article deleted"
end
redirect_to admin_articles_url
end
You need to set the attachment 'file' attribute to nil before destroying it, in order to delete the uploaded file from the disk.
So your code should be like this
Destroy action in Articles controller:
def destroy
#article = Article.find(params[:id])
begin
# first delete the attachments from disk
#article.attachments.each do |a|
a.file = nil
a.save
end
#article.destroy
rescue
flash[:danger] = "Unable to delete article"
else
flash[:success] = "Article deleted"
end
redirect_to admin_articles_url
end
Try to add preserve_files option to "false" string
has_attached_file :file, :preserve_files => "false"
Come up with:
def destroy
self.transaction do
self.images.destroy_all
super
end
end
self.images is collection of records with attachments.
Most important there is self.transaction do ... because when for any reason super (original destroy) failed it wont remove files from hdd. It waits untils COMMIT
try something like this:
def destroy
#article = Article.find(params[:id])
#article.attachments.destroy
#article.destroy
respond_to do |format|
format.html { redirect_to admin_articles_url }
end
end
#article = Article.find(params[:id]) will find the article which you want to delete.
#article.attachments will collect all attachments related that particular article
Note: you must destroy #article after all attachments destroyed. If you write #article.destroy before #article.attachments.destroy then it will give an error as #article not found.
As you have mentioned in Article model has_many :attachments, :dependent => :destroy
then I think no need to write #article.attachments.destroy as destroying #article it will delete all attachments related to it.
You can also use a before_destroy callback
example
class User < ActiveRecord::Base
before_destroy :delete_image
has_attached_file :image,
path: "/attachments/:class/:attachment/:style/:filename"
validates_attachment_content_type :image, content_type: \Aimage\/.*\z/
private
def delete_image
self.image = nil
self.save
end
end
then any time an instance of the model class is destroyed, then the attachments will be deleted first.
I am brand new here. I have been fighting with a rails app for hours now and need an answer. I have searched and tried many suggestions related to what I am trying to accomplish, but to no avail. I got the paperclip gem running for my rails app yesterday, and it was a breeze to add an attachment to a single model. However, I defined an agnostic, polymorphic attachments table to hold attached files for all models that need this functionality.
My issue is that I cannot get the attached file to save through nested parameters. All my parameters are accepted, but the db rolls back and doesn't save (using guard). Message is: 'attachments.attachable_id'=>'can't be blank'. I need this to be the foreign key of the related table, and this has to be saved along with the attachment_type. Here's what I have:
class ReportsController < ApplicationController
def new
#report = Report.new
#report.attachments.build(attachable_id: #report.id)
end
def create
#report = Report.new(params)
#report.attachments.build
respond_to do |format|
if #report.save
format.html { redirect_to #report, notice: 'Report was successfully created.' }
format.json { render json: #report, status: :created, location: #report }
else
format.html { render action: "new" }
format.json { render json: #report.errors, status: :unprocessable_entity }
end
end
end
private
def report_params
params.require(:report).permit(:filing_year, :filing_number, :order_number, :location, :environmental_review,:biological_review, :cultural_review, :date_received, :status, attachments_attributes: [:id, :attachable_id, :attachable_type, :attachment])
end
end
And for the models:
class Attachment < ActiveRecord::Base
belongs_to :attachable, polymorphic: true
validates :attachable_id, :attachable_type, presence: true
do_not_validate_attachment_file_type :attachment
Paperclip.interpolates :attached_to do |attachment, style|
attachment.instance.attachable.class.to_s.downcase
end
has_attached_file :attachment,
:url => "/attachments/:id/:basename.:extension",
:path => ":rails_root/public/attachments/:attached_to/:id/:basename.:extension",
:default_url => "/attachments/original/no-file.txt"
end
class Report < ActiveRecord::Base
has_one :environmental_review
has_many :attachments, as: :attachable
accepts_nested_attributes_for :attachments
validates :order_number, presence: true
.
.
.
end
And view (in slim):
.report
= form_for #report do |f|
.
.
.
= f.fields_for :attachments do |a|
= a.file_field :attachment
.
.
.
Thank you.
Well for one in your create method you call #reports.attachments.build again, but don't set the assignable_id, you need to do that. The other thing you can do is add a hidden form field that has the name attachable_id.