RoR save body of rest put to file system - ruby-on-rails

I have used Ruby on Rails to create a simple Rest api server. I have added to my route.rb this, which sends the whole path to myapp's controller's update method, which I need:
put 'dreceiver/*other', to: 'myapps#update'
My controller:
class MyAppsController < ApplicationController
protect_from_forgery with: :null_session
# PUT
# Expected Parameters: {"other"=>"api/1.0/file/abc123"}
def update
#Need to grab last part of path sent in:
if params[:other] =~ /api\/1.0\/file\/.*/
batchid = params[:other].split('/').last
else
batchid = nil
end
unless batchid.nil?
render :text => '', :status => 201
else
render :text => '', :status => 401
end
return
end
end
My problem: I expect a gzip file to be sent in the body of the PUT request. I need to save it to the file system. I see lot's of examples of storing it in the db using paperclip, but I really don't want to do any migrations or deal with the db if I don't have to... this is just to test some client code sending the file. Thanks for your help in advance.

Paperclip does not store uploaded files in the database, it stores them on the filesystem and then writes the filename to the database (usually -- it can be configured in multiple ways).
Multipart POSTs (and PUTs or PATCHes) work just like regular POSTs. Rails will automatically create a Hash-like object from the request body. One of the values in this hash should be a object that responds to #tempfile that you can treat pretty much just like a normal File object.
def update
gzip_file = params[:uploaded_file].tempfile # replace uploaded_file with the field name
...
end

Related

Rails + ActiveStorage on S3: Set filename on download?

Is there a way to change/set the filename on download?
Example: Jon Smith uploaded his headshot, and the filename is 4321431-small.jpg. On download, I'd like to rename the file to jon_smith__headshot.jpg.
View:
<%= url_for user.headshot_file %>
This url_for downloads the file from Amazon S3, but with the original filename.
What are my options here?
The built-in controller serves blobs with their stored filenames. You can implement a custom controller that serves them with a different filename:
class HeadshotsController < ApplicationController
before_action :set_user
def show
redirect_to #user.headshot.service_url(filename: filename)
end
private
def set_user
#user = User.find(params[:user_id])
end
def filename
ActiveStorage::Filename.new("#{user.name.parameterize(separator: "_")}__headshot#{user.headshot.filename.extension_with_delimiter}")
end
end
Starting with 5.2.0 RC2, you won’t need to pass an ActiveStorage::Filename; you can pass a String filename instead.
I know this was already answered, but I would like to add a second way of doing this. You could update your file name when the user object is saved. Using OP's example of the user model and the headshot_file field, this is how you could solve this:
# app/models/user.rb
after_save :set_filename
def set_filename
file.blob.update(filename: "ANYTHING_YOU_WANT.#{file.filename.extension}") if file.attached?
end
The approach of #GuilPejon will work. The problem with directly calling the service_url is:
It is short-lived (not recommended by rails team)
It will not work if the service is disk in development mode.
The reason it does not work for disk service is that disk service requires ActiveStorage::Current.host to be present for generating the URL. And ActiveStorage::Current.host gets set in app/controllers/active_storage/base_controller.rb, so it will be missing when service_url gets called.
ActiveStorage as of now gives one more way of accessing the URL of the attachments:
Using rails_blob_(url|path) (recommended way)
But if you use this, you can only provide content-disposition and not the filename.
If you see the config/routes.rb in the `ActiveStorage repo you will find the below code.
get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
direct :rails_blob do |blob, options|
route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
end
and when you look into blobs_controller you will find the below code:
def show
expires_in ActiveStorage::Blob.service.url_expires_in
redirect_to #blob.service_url(disposition: params[:disposition])
end
So it is clear that in rails_blob_(url|path) you can only pass disposition and nothing more.
I haven't played with ActiveStorage yet, so this is kind of a shot in the dark.
Looking at the ActiveStorage source for the S3 service, it looks like you can specify the filename and disposition for the upload. From the guides it seems that you can use rails_blob_path to access the raw URL of the upload and pass these parameters. Therefor you might try:
rails_blob_url(user.headshot_file, filename: "jon_smith__headshot.jpg")

Params not being saved in rails

Disclaimer: I am very new to Ruby + rails. I'm not sure if this is a bug, but my params variable always seems to be null. I am working on a large and unfamiliar codebase so I'm not sure if it's something else interfering or my own code; any suggestions would be welcome however.
In my routes file I have match '/proxy_request/:number/:ref' => 'proxies#show', via: :get- I was under the impression that this would store :number and :ref variables in params. However when my proxies#show function runs (below), params is an empty hash.
In case it probably is something else interfering with params, is there another way to pass :number and :ref to proxies#show?
class ProxiesController < ApplicationController
include Service
skip_before_action :restrict_access!
def show
binding.pry #params is null here
data = { date: Adapter.staging_date.get(params[:number], params[:ref])}
render json: data, content_type: "application/javascript", callback: #_request.env["QUERY_STRING"].match(/jQuery\d*_\d*/)
end
end
I removed the include Service and all seems to be ok now

Ruby on Rails and JSON request processing

I have ruby on rails app and my controller should process request which creates many objects. Objects data is passed from client via json using POST method.
Example of my request (log from controller):
Processing by PersonsController#save_all as JSON
Parameters: {"_json"=>[{"date"=>"9/15/2014", "name"=>"John"},
{"date"=>"9/15/2014", "name"=>"Mike"}], "person"=>{}}
So i need to save these two users but i have some issues:
How to verify strong parameters here? Only Name and Date attributes can be passed from client
How can I convert String to Date if i use Person.new(params)?
Can i somehow preprocess my json? For example i want to replace name="Mike" to name="Mike User" and only then pass it in my model
I want to enrich params of every person by adding some default parameters, for example, i want to add status="new_created" to person params
First of all I'd name the root param something like "users", then it gives a structure that is all connected to the controller name and the data being sent.
Regarding strong params. The config depends of your rails app version. <= 3.x doesn't have this included so you need to add the gem. If you're on >= 4.x then this is already part of rails.
Next in your controller you need to define a method that will do the filtering of the params you need. I should look something like:
class PeopleController < ApplicationController
def some_action
# Here you can call a service that receives people_params and takes
# care of the creation.
if PeopleService.new(people_params).perform
# some logic
else
# some logic
end
end
private
def base_people_params
params.permit(people: [:name, :date])
end
# Usually if you don't want to manipulate the params then call the method
# just #people_params
def people_params
base_people_params.merge(people: normalized_params)
end
# In case you decided to manipulate the params then create small methods
# that would that separately. This way you would be able to understand this
# logic when returning to this code in a couple of months.
def normalized_params
return [] unless params[:people]
params[:people].each_with_object([]) do |result, person|
result << {
name: normalize_name(person[:name]),
date: normalize_date(person[:date]),
}
end
end
def normalize_date(date)
Time.parse(date)
end
def normalize_name(name)
"#{name} - User"
end
end
If you see that the code starts to get to customized take into a service. It will help to help to keep you controller thin (and healthy).
When you create one reason at the time (and not a batch like here) the code is a bit simpler, you work with hashes instead of arrays... but it's all pretty much the same.
EDIT:
If you don't need to manipulate a specific param then just don't
def normalized_params
return [] unless params[:people]
params[:people].each_with_object([]) do |result, person|
result << {
name: person[:name],
date: normalize_date(person[:date]),
}
end
end

Resque not working

i have a rails 3 app, and today i decided add resque to my message_image uploader
in my controller in create action
def create
#recipient = User.find(params[:message][:recipient_id])
#message = current_user.send_message(#recipient, params[:message][:body])
##message.images << MessageImage.create!(params[:message][:images_attributes])
respond_with(#message) do |format|
if #message.save!
Resque.enqueue(AddImageToMessage, #message.id, params[:message][:images_attributes])
format.js do
unless request.referer.include? 'messages'
render :text => "window.location = '#{messages_path}'", notice: 'message has been succ created.'
end
end
else
redirect_to :back
end
end
end
and i have class
class AddImageToMessage
#queue = :messages_queue
def self.perform(message_id, images_params)
message = Message.find(message_id)
message.images << MessageImage.create!(images_params)
message.save
end
end
But if i try to create new message and attach it image, a have an error in redis log server
https://gist.github.com/Olefine/6270753 .In this logs i have a record invalid, but all my validations are pass if i use standart method in controller
#message.images << MessageImage.create!(params[:message][:images_attributes])
I guess reason for that is how resque/redis work internally resque dump data to redis by serializing it
to json format
so it some what internally does this
object.to_json ## before pushing to redis
JSON.parse object ## after popping from redis
there lies your problem unlike yaml with json it not advisable to serialize complex object
But in your case your image_attributes contain a complex hash containing a file object and AFAIK no json library can serialize/deserialize a file ,which is even evident in your log as well
"tempfile"=>["�PNG\r\n", "\u001A\n", "\u0000\u0000\u0000\rIHDR\u0000\u0000\u0000�\u0000\u0000\u0000�\b\u0006\u0000\u0000\u0000�X��\u0000\u0000\u0000\u0006bKGD\u0000�\u0000�\u0000�����\u0000\u0000\u0000\atIME\a�\u0004\u0019\u000F\u0016!7\u0011\u001A-\u0000\u0000 \u0000IDATx���wt\u0014�����m��$��{�]D#\u0001�M�t\u0011\u0005EA�l(XP\u0004\u0015\u0010Q�H���S� �t�\"��P\u0013\b\u0010 !��-3�\u001FK����4\u0012���\u001C�w��\\6������\u001F��\u001Fv\u0011\n", "{\u0000�\u0018\u0001#\t \b�X\u001DK\u0000\u0012��\a?��\u001D�\u007F\u0014\u0004�\t�2��\u0016##�\u0001P\u001D(\u000F�\\<�\u0000D\u0000 ..."
you can see that object serialization and deserialization is not what you except you it should be a file not a string displaying binary data output
I suggest you rather change your architecture and perhaps rethinking on how you intend to do this
Suggestion
Perhaps you can model your form multipart options and then use carrierwave_backgrounder or anything like it (if you dont intend to use carrierwave) and offload the uploading part in background through those library
Hope this help

Ruby parse CSV file to print out the rows

I have a file upload in my Rails application and I want to parse the CSV file assuming the upload went okay. You can see the comment below that indicates where I would like to read the rows of the CSV file. How can I do this? I used carrierwave for the file upload.
I mounted it as such
mount_uploader :file, LCFileUploader
Here is the code I currently have
require 'CSV'
class LCFilesController < ApplicationController
def new
authorize! :create, :lc_file
#lc_file = LCFile.new
end
def create
authorize! :create, :lc_file
puts params
#lc_file = LCFile.new(params[:lc_file])
#lc_file.user_id = current_user.id
if #lc_file.save
#PARSE CSV HERE TO PRINT OUT THE ROWS OF THE CSV FILE
CSV.foreach(#lc_file.file.path) do |row|
puts row
end
redirect_to lc_path, :notice => 'New lc created!'
else
render :new
end
end
end
and I get this error:
undefined method `find_all_by_team_id' for #<Class:0x007fe14c40d848>
You can use the CSV class:
puts CSV.read(#lc_file.file.path)
or one row at a time:
CSV.foreach(#lc_file.file.path) do |row|
puts row
end
Besides CSV generation there are a few more issues:
the redirect will not work after you send send some output. But even if it did, the output would not be seen, since you're redirecting.
the path you are redirecting to is incorrect (I believe that's why you get that error). I suppose you want something like lcfiles_path or lcfile_path(#lc_file). Run rake routes (the same way you ran rails console) to see a list of all available routes.
Now if you still have issues, I suggest posting another question, as this one was mainly about CSV generation and that should be solved using the code I posted at the start of this answer.

Resources