Update attachment Rails Active Storage - ruby-on-rails

I have a React frontend backed by a Rails API and I am sending photos to Cloudinary through Active Storage. My settings are standard: I have an Event model with the method has_one_attached :photo, and the POST endpoint '/api/v1/events' is served by the 'Events#create' method with simply #event = Event.new(event_params). I also save an additional column 'url' in the 'events' table in order to display it back whenever needed (and I get it with #event.photo.url (a warning says that 'service_url' is said to be depreciated). I can fetch it (browser's fetch api) and use it in React. That works.
When I edit my form and submit the form with a new photo, then I have to adapt the PATCH query with the following events#update method. I followed the Rails guides that says you should purge and create_and_upload!.
def update
#event = Event.find(params[:id])
logger.debug "..................BEFORE : ..#{#event.to_json}"
if event_params[:photo] || #event.url
#event.photo.purge
#event.url = nil
end
if #event.update(event_params)
if event_params[:photo]
ActiveStorage::Blob.create_and_upload!(
io: File.open(event_params[:photo].tempfile),
filename: event_params[:photo].original_filename,
content_type:event_params[:photo].content_type
)
#event.url = #event.photo.url
end
logger.debug "................AFTER :.. #{#event.to_json}"
render json: {status: :ok}
else
render json: {errors: #event.errors.full_messages},
status: :unprocessable_entity, notice:"not authorized"
end
end
My logs show that this produces an url but no url is sent to React. If I modify other fields in this form, I get the update back in React from Rails. However, the link isn't sent: url: null.
I don't even use Active Job, but used 6.03 and even updated to 6.1. Anyone experienced this?
FI, the params hash contains the following:
"photo"=>#<ActionDispatch::Http::UploadedFile:0x00007fd938f1ecf8 #tempfile=#<Tempfile:/var/folders/11/whcyvzvx3w54zb0n1r0929200000gn/T/RackMultipart20200714-79173-t9s624.svg>, #original_filename="Rafting.svg", #content_type="image/svg+xml", #headers="Content-Disposition: form-data; name=\"event[photo]\"; filename=\"Rafting.svg\"\r\nContent-Type: image/svg+xml\r\n">}

In case of any interest, I found an answer on how to update Active Storage. My model is Event with a column url that contains a Cloudinary link, and the attached active storage object is named photo). Then on a PATCH, firstly purge if needed and then just update the event.url column with the method .url (formerly .service_url) doing:
event.update(url: event.photo.url)
def update
event = Event.find(params[:id])
# purge if a link already exists and the params contain a new picture
if event_params[:photo] && event.url
event.photo.purge
end
if event.update(event_params)
if event_params[:photo]
event.update(url: event.photo.url)
end
render json: event, status: :ok
else
render json: {errors: event.errors.full_messages},
status: :unprocessable_entity, notice:"not authorized"
end
end
The Rails guides seem misleading..

Related

Update Multiple Records with a single command in Ruby

Basically I want to update an array of objects that my api recieves in a single command. I have done it when I was inserting but I couldn't find a way to do update it.
Here is m create method for multiple insertions:
def create_all
if Attendance.create attendance_params
render json: { message: "attendance added" }, status: :ok
else
render json: { message: "error in creation" }, status: :bad_request
end
end
Params:
def attendance_params
params.require(:attendance).map do |p|
p.permit(
:student_id,
:id,
:attendance
)
end
end
I tried to do similar thing with update but it generates this error:
Completed 500 Internal Server Error in 11ms (ActiveRecord: 2.7ms)
Argument Error (When assigning attributes, you must pass a hash as an argument.)
my update method is this:
def update_attendance
if Attendance.update attendance_params
render json: { message: "attendance updated" }, status: :ok
end
end
ActiveRecord Create can take an array of hashes and create multiple records simultaneously.
However, ActiveRecord Update cannot.
You could potentially create an "update_batch" method on your model that allows for an array of hashes. You would have to send an array of hashes and each hash would have to include the id of the record you are updating (and allow that in your strong parameters definition). Then in your update_batch method you would have to grab the id from each hash and update each:
class Attendance < ActiveRecord
def update_batch(attendance_records)
attendance_records.each do |record|
Attendance.find(record[:id]).update(record.except(:id))
end
end
end
Please check this example and try applying it:
Attendance.where(:student_id => [23,45,68,123]).update_all(:attendance => true)
Or if you're trying to update all Attendance records:
Attendance.update_all(:attendance => true)
Also, please check this link:
https://apidock.com/rails/ActiveRecord/Relation/update_all

How can I use a bingInt (as string) as a parameter?

So I'm creating a system where a visitor can ask my client for a music composition. The user starts to fill a form with details and such, and when he submits it, it's not yet sent to my client. However, he can get an estimation of the price, and modify his demand if he wants to. After that, He still can submit it one last time, and this time, the estimation is sent.
I don't want to use the default id as a parameter because it would be way too simple to 'parse' other estimations if the url looks ends with /3 or /4. You'd just have to try a few URLs and if it's your lucky day, you'd get to "hack" an estimation that isn't yours. I'm planning to use a cron job to delete these estimations after a while, but I don't want to take any risk.
To avoid that, I decided to use the visitor's session_id as a parameter, on which I removed every alphabetic characters, but still saved as a string in my MySQL 5.7 database so that ActiveRecord would be ok with that. I also changed my routes accordingly, and the result is supposed to be something like
localhost:3000/devis/4724565224204064191099 # Devis means 'quotation' in french
However, when I try to get to this route, I get the following error:
ActiveRecord::RecordNotFound (Couldn't find Devi with an out of range value for 'id')
Here is the relevant part of my controller:
devis_controller.rb
# ...
def create
#devi = Devi.new(devi_params)
respond_to do |format|
#devi.status = 'created'
#devi.session_id = session.id.gsub(/[^\d]/, '').to_s # How I store my parameter
# Latest record returns '4724565224204064191099'
if #devi.save
format.html { redirect_to #devi, notice: 'Devi was successfully created.' }
format.json { render :show, status: :created, location: #devi }
else
format.html { render :new }
format.json { render json: #devi.errors, status: :unprocessable_entity }
end
end
end
# ...
private
def set_devi
#devi = Devi.find(params[:session_id].to_s) # Tried '.to_s', didn't work
end
And here are my routes:
# 'index' and 'destroy' don't exist
resources :devis, only: %i(create update)
get '/devis/nouveau', to: 'devis#new', as: :devis_new
get '/devis/:session_id', to: 'devis#show', as: :devis_show
get '/devis/editer/:session_id', to: 'devis#edit', as: :devis_edit
My question is the following: is there a way to present the :session_id as a string to my controller's params? Or do I have a better option?
Thank you
I think session_id is a int at database, and then is where you should do the change.
change the type of session_id to string and ActiveRecord map session_id as string from then on

Custom error JSON in Rails 5

How do I render custom error JSON in a Rails 5 API? Right now, if I perform a GET on this url http://localhost:3000/users/5, it returns the 404 not found code, and all the traces associated with it. How can I stop Rails from automatically rendering all the traces?
Example of the generated error response: https://pastebin.com/C1dQA5eL
Hi you can create a custom module and extend it in your controller. Create a method in that module with parameters of resource and value. And on the basis of that send response and after that you can extend it in your respective Controller
like this:
class MyController
include AppError
end
I think you should if....else.
def show
user = User.find_by(id: params[:id])
if user.present?
render json: user
else
render json: { status: :not_found }
end
end

The order in which validation and save are executed

In an integration test, I'm trying to save an invalid link. It is invalid because it links two nodes that belong to two different organizations (which my model validation does not permit). The error message displayed is however not the error message from my model validation but the error "Unable" from the controller.
I would have expected the validation from the model to come before this line in the controller. Moreover, I don't understand why, if we would take the model validation out of account, it wouldn't save. Could someone perhaps explain?
Part of my controller method:
if link.save
render json: #organization, message: "Saved", status: :created
else
render json: link, message: "Unable", status: :bad_request)
end
And in the Link model:
validate :same_org
def same_org
org1 = self.first_node.organization unless self.first_node.nil?
org2 = self.second_node.organization unless self.second_node.nil?
unless org1 == org2
errors.add(:second_node_id, "You can't link two nodes from different organizations")
end
end
From the api docs:
http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-save
"By default, save always run validations. If any of them fail the action is cancelled and save returns false."
So, you are correct in your assumption that validations (by default) run first. So the issue is that you are not passing that message to your view, not surprising as this line:
render json: link, message: "Unable", status: :bad_request)
Just passed back "Unable"
What you need to do is accesses the errors messages. replace "Unable" with
link.errors.full_messages.to_sentence
And you should be good.
this happens because at your controller you don't send validation error message so, you should change your controller code to something like
if link.save
render json: #organization, message: "Saved", status: :created
else
render json: {errors: link.errors.full_messages, message: "Unable"}, status: :bad_request
end

How to display error message on model in rails

I need to display error message on model in rails,
my coding on model is like this,
if my_address.valid?
# I need here the validation error.
return nil
end
I used errors.add("Invalid address") but it is not working
please help to solve this problem ,
You will be able to access the errors via object.errors, i.e. for your case my_address.errors. It will return Error objects, you can check up on it here: http://api.rubyonrails.org/classes/ActiveRecord/Errors.html
I suggest taking a look at how scaffolds (script/generate scaffold my_model) displays validation errors.
Here's a short summary:
def create
#post = Post.new(params[:post])
if #post.save # .save checks .valid?
# Do stuff on successful save
else
render :action => "new"
end
end
In the "new" view, you'll use #post.errors, most likely with <%= error_messages_for :post %>.

Resources