Rails + CouchDb binary file upload to Database - ruby-on-rails

Simple-Stored is based on top of CouchPotato for handling CouchDb in rails. While trying to upload files to the couchdb we have tryied using base64, json post and nothing seems to work quite right; we are trying to upload to the _attachments propertie of a document already stored.
Having the model like this :
class Patient
include SimplyStored::Couch
end
and in the controller while receiving the file trough the update action
def update
#patient = Patient.find(params[:id])
if params[:patient][:_attachments]
attachement = params[:patient][:_attachments]
filedata = attachement.tempfile.read
data = Base64.encode64(filedata).gsub(/\n/, '')
type = attachement.content_type
or_name = attachement.original_filename
#patient._attachments = {or_name => {'data' => data, 'content_type' => type}}
#patient.save
return render :json => #patient._attachments
end
end
Now the fun part is that I can see that #patient._acttachments has the file itself and that is what is returning in the render after the .save; but it is not actually saving it on the couchdb database.
Any ideas why is not doing the save or should I try to just push the _attachment to the couchdb database. ? (which by the way always returns a 500 error :( )

the solution it's very simple, based on the couchpotato website, you actually don't need to convert it to base64 here is the example of code working
if params[:patient][:_attachments]
attachement = params[:patient][:_attachments]
data = attachement.tempfile.read
type = attachement.content_type
or_name = attachement.original_filename
params[:patient][:_attachments] = {or_name => {'data' => data, 'content_type' => type}}
end
if #patient.update_attributes(params[:patient]) #blah blah blah
since the values are in the [:patient][:_attachments] params, you just need to pass it as another param tested and working.
Also you need to define your patients model as
property :_attachments
dunno if that is required but I did it.
I know I should not ask for money but since I WORK FOUR YOU its only 100 pesos/hour.. see you at the office
cheers
lols

I donno about the Ruby and couchpotato, but I don't think you need to Base64 your attachment. Just read the binary info and write it to request.
my 2cents. :)

Related

Reading text from a PDF works in Rails console but not in Rails application

I have a simple one-page searchable PDF that is uploaded to a Rails 6 application model (Car) using Active Storage. I can extract the text from the PDF using the 'tempfile' and 'pdf-reader' gems in the Rails console:
> #car.creport.attached?
=> true
> f = Tempfile.new(['file', '.pdf'])
> f.binmode
> f.write(#car.creport.blob.download)
> r = PDF::Reader.new(f.path.to_s)
> r.pages[1].text
=> "Welcome to the ABC Car Report for January 16, 20...
But, if I try the same thing in the create method of my cars_controller.rb, it doesn't work:
# cars_controller.rb
...
def create
#car = Car.new(car_params)
#car.filetext = ""
f = Tempfile.new(['file', '.pdf'])
f.binmode
f.write(#car.creport.blob.download)
r = PDF::Reader.new(f.path.to_s)
#car.filetext = r.pages[1].text
...
end
When I run the Rails application I can create a new Car and select a PDF file to attach. But when I click 'Submit' I get a FileNotFoundError in cars_controller.rb at the f.write() line.
My gut instinct is that the controller is trying to read the blob in order to write it to the temp file too soon (i.e., before the blob has even been written). I tried inserting a sleep(2) to give it time, but I get the same FileNotFoundError.
Any ideas?
Thank you!
I don't get why you're jumping through so many hoops. And using .download without a block loads the entire file into memory (yikes). If #car.creport is an ActiveStorage attachment you can just use the open method instead:
#car.creport.blob.open do |file|
file.binmode
r = PDF::Reader.new(file) # just pass the IO object
#car.filetext = r.pages[1].text
end if #car.creport
This steams the file to disk instead (as a tempfile).
If you're just taking file input via a plain old file input you will get a ActionDispatch::Http::UploadedFile in the parameters that also is extemely easy to open:
params[:file].open do |file|
file.binmode
r = PDF::Reader.new(file) # just pass the IO object
#car.filetext = r.pages[1].text
end if params[:file].respond_to?(:open)
The difference looks like it's with your #car variable.
In the console you have a blob attached (#car.creport.attached? => true). In your controller, you're initializing a new instance of the Car class, so unless you have some initialization going on that attaches something in the background, that will be nil.
Why that would return a 'file not found' error I'm not sure, but from what I can see that's the only difference between code samples. You're trying to write #car.creport.blob.download, which is present on #car in console, but nil in your controller.

Rails Httparty JSON to params to save

Rails 4.5 Ruby 2.3.1
I am getting json from an API and trying to store the following into a model CLrates
1. timestamp as unix time (date)
2. Currency_code (string)
3. quote (decimal monetary value)
I can use the following in irb after parsing the json and know how to get the elements individually using: response["quotes"]. How can I generate params to be saved in the model above when the body is as follows:
irb(main):036:0> puts response.body
{
"success":true,
"terms":"https:\/\/xxxxx.com\/terms",
"privacy":"https:\/\/xxxxx.com\/privacy",
"timestamp":1504817289,
"source":"USD",
"quotes":{
"USDAED":3.672703,
"USDAFN":68.360001,
"USDCUC":1,
"USDCUP":26.5,
"USDCVE":91.699997,
"USDCZK":21.718701,
............ many more lines removed for brevity
"USDZWL":322.355011
}
I can do this using a separate associated model but have very little idea how to create the params to save to a single table.
The following links got me to this point and well worth a read if you need info on httparty GET (client):
1. http://www.rubydoc.info/github/jnunemaker/httparty/HTTParty/
2. http://eric-price.net/blog/rails-api-wrapper/
3. https://www.driftingruby.com/episodes/playing-with-json
The class and method in lib/clayer.rb:
class clayer
include HTTParty
format :json
read_timeout 10
def self.get_quotes
response = HTTParty.get('http://www.nnnnnnn.net/api/live?
access_key=nnnnnnnnnn&format=1')
end
end
I used irb as I am still learning how to run this through rails c. This will be called in the controller and saved however need to work out how to get the params from the json
Thanks for the help
OK: after digging I think I am on the right track
I get the response["QUOTES"], loop through them and build the params required saving each at the end of the loop
rates = response["QUOTES"]
rates.each do |k,v|
clrate = Realtimerates.new
clrate.date = response["timestamp"]
clrate.countrycode = "#{k}"
clrate.price = "#{v}"
clrate.save
end
Going to give this a whirl
In model
class Realtimerate < ActiveRecord::Base
include HTTParty
format :json
read_timeout 5
def self.get_cl_rates
response = HTTParty.get('http://www.mmmmm.net/api/live?access_key="key"&format=1')
rates = response["quotes"]
rates.each do |k,v|
create!(
date: Time.at(response["timestamp"]),
country_code: "#{k}",
price: "#{v}")
end
end
end
In the controller:
def index
Realtimerate.get_cl_rates
#realtimerates = Realtimerate.all
end
This is working and shows latest GET.
You already have a hash in your response.body. All you need to do now is to assign the relevant key-value to your model's attributes. e.g.
clrate = ClRate.new
res = response.body
clate.time = res["timestamp"]
...
clate.save

Post image from Rails

I've got a Base64 encoded image coming in to my application. I want to re-post that image somewhere else, but it's setting the content-type to multipart/form-data at the destination. How do I upload this image?
file_name = permitted_params[:file_name]
file_contents = permitted_params[:file_contents]
file = Tempfile.new( file_name )
file.binmode
file.write( Base64.decode64( file_contents ) )
file.rewind()
raw_response = RestClient.put(
url,
{ 'upload' => file, :content_type => 'image/jpeg' },
:headers => {:content_type => 'image/jpeg'}
)
UPDATE (SOLVED)
I needed to use RestClient because I needed to pass it through to another server (hence the 'url' in the PUT).
My problem was in decoding the image I wasn't stripping out the
data:image/jpeg;base64,
then with this code:
raw_response = RestClient.put(url,
file_binary,
{:content_type => imageContentType})
I was able to get it to put the image and set the correct content-type. The answer below did help though, because I tried it to make sure the image was being decoded properly and it wasn't.
It is quite simple to do. First, you need to decode base64 encoded file. You will get binary file representation. Next use send_data from ActionController to send binary data. Also I have set a filename so it will be delivered to the user.
require 'base64'
class SomeController < ApplicationController
def some_action
file_name = permitted_params[:file_name]
file_base64_contents = permitted_params[:file_contents]
file_binary_contents = Base64.decode64(file_base64_contents)
# https://apidock.com/rails/ActionController/Streaming/send_data
send_data file_binary_contents, filename: file_name
end
end
I'll suggest you to update this implementation with error handling to improve security of your app. One more thing, don't use RestClient. Why do you need it here? Rails gives you all needed things for HTTP communication from controller.
If you have any questions about this please ask. Good luck.

Rails - submitting JSONs to database from controller

I am working on a Rails app, and I am attempting to insert attributes from JSONs as database entries. I'm running into a problem, though, and would appreciate some guidance.
I've been able to jam a few things together and come up with something that sort of works...
def create
#report_group = Array.new
#report_group.push({location:"home", comments:"Hello, database!"}, {location:"away", comments:"Goodbye, database!"})
#report_group.each do |x|
#new_report = Report.new(x)
#new_report.user_id = current_user.id
#new_report.save
end
end
private
def report_params(params)
params.permit(:user_id,:location,:comments)
end
This is a good first step - this commits two entries to my database, one for each of the hashes pushed into #report_group, but it is suffering from a problem - the create action does not reference the report_params whitelist.
I have built several Rails apps where entries are submitted one at a time via the standard Rails form helpers, but I have never done it with multiple JSONs like this before. Trying out the syntax I'd use in a typical form helper situation
#new_report = Report.new(report_params(x))
throws the expectable error undefined method permit' for #<Hash:0x007f966b35e270> but I am not sure what else to do here.
EDIT TO SHOW SOLUTION
Big thanks to #oreoluwa for pointing me in the right direction. Here's the solution that I came up with.
def create
#report_group = Array.new
#report_group.push({location:"home", comments:"Hello, database!"}, {location:"away", comments:"Goodbye, database!"})
#report_group.each do |x|
hash = ActionController::Parameters.new(x)
#new_report = Report.new(report_params(hash))
#new_report.user_id = current_user.id
#new_report.save
end
end
private
def report_params(params)
params.permit(:user_id,:location,:comments)
end
You're getting the error because a Hash is not the same as an ActionController::Parameters. In order to use the permit method with your Hash you may need to first convert it to ActionController::Parameters, as such:
hash = {location:"home", comments:"Hello, database!"}
parameter = ActionController::Parameters.new(hash)
parameter.permit(:user_id,:location,:comments)
I don't know if that is what you're looking for, but I thought to point you in the right direction.

Send multiple files to a webservice

I'm currently trying to send multiple files to a webservice (and to proceed, depending on the response afterwards, but that's not where I am at yet).
The following code sends one file:
def show
...
conn = Faraday.new(:url => 'webservice.abc' ) do |faraday|
faraday.request :multipart
faraday.adapter :net_http
end
payload = { :files => Faraday::UploadIO.new("#{Rails.root}/fileone.xml", 'application/xml') }
conn.post 'http://webservice.abc', payload
#output = response.body
end
And now I'm stuck, trying to find a way to send 2 (or more) files at once, which is necessary as the purpose of the webservice is to compare these. It seems that when I put them into an array, they can't be handled with.
So what I'm looking for is the way to "bundle" the files in order to POST them afterwards (as said before- it works with one file)
TYIA for your time
Thanks Deepak,
gave me a hint into the right direction. Should be :files[i], though- so the payload line from the question reads:
payload = { :files[0] => Faraday::UploadIO.new("#{Rails.root}/fileone.xml", 'application/xml'),
:files[1] => Faraday::UploadIO.new("#{Rails.root}/filetwo.xml", 'application/xml')}

Resources