Carrierwave delete file from controller - ruby-on-rails

The title says everything. I currently have a model concern with which I'm setting if a model can have attachments or not using include Attachable. So far so good.
Then, when I display the list of files attached to a particular model, I'm adding a link to delete it such as :
DELETE /posts/:post_id/attachments/:id(.:format) attachments#destroy
For that purpose, I created an AttachmentsController with a destroy method. So I have 2 problems here. The first, how can I delete a file from this controller using Carrierwave (for delete the file itself and the table record)?
Second, since my attachable behavior gonna be plug in several model :
DELETE /posts/:post_id/attachments/:id(.:format) attachments#destroy
DELETE /users/:user_id/attachments/:id(.:format) attachments#destroy
...
How can I do in my AttachmentsController to delete a file depending on the associated model dynamically?
class Attachment < ActiveRecord::Base
include Sluggable
belongs_to :attachable, polymorphic: true
mount_uploader :file, AttachmentUploader
validates :name, presence: true, if: :file?
validates :file, presence: true, if: :name?
end
class AttachmentsController < ApplicationController
before_action :authenticate_user!
def destroy
// Don't know how to remove that file
redirect_to :back
rescue ActionController::RedirectBackError
redirect_to root_path
end
end
Hope I was clear.
Thanks
EDIT :
Ok I create a tweak on the params hash in order to get the associated object dynamically within AttachmentsController :
private
def get_attachable_model
params.each do |name, value|
if name =~ /(.+)_id$/
model = name.match(/([^\/.]*)_id$/)
return model[1].classify.constantize
end
end
nil
end

Ok I finally found a solution myself. Here is my destroy method from AttachmentsController :
def destroy
model, param = get_attachable_instance
model_attach = model.find_by slug: params[param.to_sym]
file = model_attach.attachments.find_by slug: params[:id]
file.destroy
redirect_to :back
rescue ActionController::RedirectBackError
redirect_to root_path
end
Not sure if it's the best way to go, but it does works

Related

Serializing Rails 6 models based on current_user

In an API controller, I'd like to limit what fields of a model can be seen depending on who is logged in. ActiveModel Serializers would seem to allow this, but I've had no luck with the following:
class MyModelSerializer < ActiveModel::Serializer
attributes :name, :custom_field, :secret_field
has_many :linked_records
def custom_field
object.do_something
end
def filter(keys)
unless scope.is_admin?
keys.delete :secret_field
keys.delete :linked_records
end
keys
end
end
But, the filtering is never performed and so my output always contains :secret_field and :linked_records even if there's no user logged in.
Perhaps this is because I am using Rails 6, and it would seem that ActiveModel Serializers might no longer be the best tool (e.g. https://stevenyue.com/blogs/migrating-active-model-serializers-to-jserializer).
Please do offer your suggestions for a means to perform this, if you can think of a better means.
EDIT:
Further to all the comments below, here's some different code:
attributes :name, :id, :admin_only_field, :is_admin
$admin_only = %i[:id, :admin_only_field]
def attributes(*args)
hash = super
$admin_only.each do |key|
unless scope.is_admin?
hash.delete(key)
end
end
hash
end
def is_admin
if scope.is_admin?
'admin!'
else
'not an admin!'
end
end
If I then visit the model's index page without being an admin I see that the admin_only_field and id are both present, and is_admin says that I'm not. Bizarre.
class MyModelSerializer < ActiveModel::Serializer
attributes :name, :custom_field, :secret_field
has_many :linked_records
def custom_field
object.do_something
end
private
def attributes
hash = super
unless scope.is_admin?
hash.delete :secret_field
hash.delete :linked_records
end
hash
end
end

Using PaperTrail in Rails to approve/deny edits

Here is my code so far:
class Video < ActiveRecord::Base
has_paper_trail meta: { athlete_id: :athlete_id, approved: false },
if: Proc.new { |v| v.needs_approval? }
validate :should_be_saved?
def should_be_saved?
errors.add(:base, 'added for approval') if needs_approval?
end
def needs_approval
#needs_approval ||= false
end
def needs_approval?
#needs_approval
end
end
# ApplicationController
class ApplicationController < ActionController::Base
def user_for_paper_trail
return unless user_signed_in?
original_user.present? ? original_user : current_user
end
# Used to determine the contributor
# When contributor logs in, warden saves the contributor_id in session
def original_user
return nil unless remember_contributor_id?
#original_user ||= User.find(remember_contributor_id)
end
def info_for_paper_trail
{ athlete_id: current_user.id } if current_user
end
end
The problem I am running into currently is when the Video object is saved the validation fails (because I told it too), but I need for the validation to fail but the version object continue with its creation. Just not too sure how to go about doing that.
EDITS
Here is my code (the code below is still using the ApplicationController code from above):
class Video < ActiveRecord::Base
# .. other methods
include Contributable
attr_accessible :video_type_id, :athlete_id, :uploader_id, :created_at, :updated_at, :uniform_number, :featured,
:name, :panda_id, :date, :thumbnail_url, :mp4_video_url, :from_mobile_device, :duration, :sport_id,
:delted_at, :approved
end
module Contributable
extend ActiveSupport::Concern
included do
has_paper_trail meta: { athlete_id: :athlete_id, approved: false },
unless: Proc.new { |obj| obj.approved? },
skip: [:approved]
end
def log_changes_or_update(params, contributor = nil)
update_attribute(:approved, false) unless contributor.blank?
if contributor.blank?
update_attributes params
else
self.attributes = params
self.send(:record_update)
self.versions.map(&:save)
end
end
end
class VideosController < ApplicationController
def update
# ... other code
# original_user is the contributor currently logged in
#video.log_changes_or_update(params[:video], original_user)
end
end
The app I am working on has a small layer of complexity that allows for users with a certain role to edit profiles they have access too. I am trying to save the versions of each change out (using paper_trail) without affecting the existing object.
The code above works exactly how I want it to, however, I am just curious to know if in my log_changes_or_update method is not the correct way to go about accomplishing the overall goal.
Why not just remove the validation and add an approved attribute with a default value of false to the Video model? That way the Video object is saved and a paper_trail version is created. Later when the video gets approved paper_trail will note that change too.

Rails accepts_nested_attributes_for always creates the nested models, but does not update them

Given the following:
class WebsitesController < ApplicationController
# POST /websites/save
# POST /websites/save.json
def save
Website.exists?(name: params[:website][:name]) ? update : create
end
# POST /websites
# POST /websites.json
def create
#server = Server.find_or_create_by_name(params[:server_id])
#website = #server.websites.new(params[:website])
#etc... #website.save
end
# PUT /websites/1
# PUT /websites/1.json
def update
#website = Website.find_by_name(params[:website][:name])
#etc... #website.update_attributes
end
end
The client does not have any IDs of these models
The request that gets sent only has the names, but not the ids.
And the following models
class Website < ActiveRecord::Base
serialize :website_errors
attr_accessible :plugins_attributes
has_many :plugins
accepts_nested_attributes_for :plugins
end
class Plugin < ActiveRecord::Base
belongs_to :website
end
When I make a POST request to /websites/save.json, the Website gets updated correctly if it exists, but the Plugins that belong to it always get recreated causing duplicate content in the Database. Why does this happen? I redirect to the update action which calls update_attributes so how can it be that it does not update it? I take it that it's because no ID is given with the request.
Can I make the Controller listen to plugin_name instead of plugin_id?
Modify your controller to have this:
def update
#website = Website.find_by_name(params[:website][:name])
if #website.update(params)
redirect_to website_path(#website)
else
render :edit
end
end
Also, if you're using strong_parameters, you'll need this at the bottom of your controller:
params.require(:website).
permit(
:name,
...,
plugins_attributes: [
:name,
...,
]
)
end

Rails find and redirect or Create

I'm trying to set up a Create action in my model where it first checks to see if there is already an object in the database based on the params that I have. If there is, I would like to redirect to the show page for that object. If not, I would like to create an object using the params. How would I do this and what's the best 'Rails way' to accomplish it? I tried find_or_create_by but just ended up with duplicate entries in my database, which is exactly what I'm trying to avoid.
Here is my create action so far:
def create
#book = Book.new(book_params)
if #book.save
redirect_to #book
else
# do something else
end
end
I have my params set up this way:
def book_params
params.require(:book).permit(:title, :author, :isbn, :description)
end
I also have a uniqueness validation on :isbn.
validates :isbn, presence: true, length: { is: 13 }, uniqueness: true, numericality: true
Thanks.
A before_filter that runs before your create that does a query and then redirects out.
class BooksController < ApplicationController
before_filter :check_for_existing_book, :only => [:create]
def create
# your existing create logic here
end
private
def check_for_existing_book
book = Book.where(:isbn => params[:book][:isbn]).first
if book
redirect_to(book) and return
end
end
end
You'll probably want to clean up the logic for inspecting params[:book][:isbn] and ensure you don't bomb out on invalid input, but this is the gist.

How to modify a record before saving on Ruby on Rails

Looking for a way to either:
Change one of the fields of a new record (namely - force it to lower-case) before saving it to a RoR db.
I've tried:
before_create do |term|
term.myfield.downcase!
end
but this gives an error of:
undefined method `before_create' for RowsController:Class
or
Check that the field is all lowercase, and if not, raise an error message, and not create the record.
tried:
before_filter :check_lowcase, :only => [:new]
def check_lowcase
if (Term.new =~ /[^a-z]+/)
flash[:notice] = "Sorry, must use lowercase"
redirect_to terms_path
end
end
this seems to just be ignored....
You need to do it on your model, not your controller:
class YourModel < ActiveRecord::Base
before_create :downcase_stuff
private
def downcase_stuff
self.myfield.downcase!
end
end
before_create :lower_case_fields
def lower_case_fields
self.myfield.downcase!
end
before_save { |classname| classname.myfield = myfield.downcase }

Resources