Ruby get the size in bytes of an array - ruby

I would like to obtain the size in bytes of the content of an array (items) in ruby.
I fill my array like this:
#records.each do |record|
items << { :table => table, :id => record.id, :lruos => record.updated_at }
end
In fact, I want to force sending the Content-Length of this array when I serialize it in JSON:
respond_to do |format|
#response['Content-Length'] = items.to_s.size
format.json { render :json => { :success => "OK", :items => items } }
end
So any idea to do this could be interesting.
(for a reason I don't know the content length is not sent, so I want to force it)
I use Rails 3.0.5.

Like WTP said, you probably intend on returning the size of the JSON representation instead of ruby representation of the array, because the JSON is the actual response to the browser. You can do this by encoding beforehand (yielding a string) and then checking its size.
response['Content-Length'] = ActiveSupport::JSON.encode(items).size
More about JSON serialization and rails

Alternatively, you can also do this by item.to_json.bytesize. This will give you the size of JSON string that is being sent.

For those that are still wondering - I found this to work
ActiveSupport::JSON.encode(items).size.to_s
Which while its many years later - may help someone.

Related

How can I inline json in ruby/rails?

I'd like to skip parsing some json returned by my DB when I just turn it back into json again immediately in the response from my padrino app.
ie I have
get :data, provides: :json do
Record.order(:day).map do |r|
{r.day.to_s => JSON.parse!(r.data)}
end.reduce({}, :merge!).to_json
end
and I'd like something like the following (inspired by String#html_safe):
get :data, provides: :json do
Record.order(:day).map do |r|
{r.day.to_s => r.data.json_literal}
end.reduce({}, :merge!).to_json
end
I know I can move the hash creation to the model with #as_json but that doesn't address the unnecessary performance hit from parsing and re-encoding the json.
Example output:
{
"2010-01-01":{"linux64":12186213,"mac":24131170},
"2010-01-02":{"linux":10650417,"mac":24139611,"win":12210218},
"2010-01-03":{"linux":10628353,"linux64":12184435,"win":12229263}
}
where the object that is the value of each key/value pair is available as a json-string in r.data eg '{"linux":10650417,"mac":24139611,"win":12210218}' which is why i want to avoid parsing r.data and just inline it.
I tried bypassing the JSON parse/dump altogether with the following:
get :data, provides: :json do
"{"+Record.order(:day).map do |r|
"\"#{r.day}\":#{r.data},"
end.reduce(&:+).delete(' ').chop+"}"
end
but performance was even worse than the version with the unnecessary parsing. (Which is weird, I'm not sure if string concatenation is slow or string interpolation...)
It turns out that the best way to do this is actually at the DB layer, as postgres can inline json columns into json objects and aggregate strings far faster than ruby can. So I did that and dropped my latency from 30ms to 8ms. The body of the method is below:
Sequel::Model.db.fetch("select json_object_agg(day,data) as jsobj from records;").first[:jsobj]
References:
Create json with column values as object keys
How to run raw SQL queries with Sequel

How can I download multiple .xlsx files using axlsx gem?

Hi I'm having trouble downloading multiple files with axlsx. The problem is I'm sending an array of Id's to the controller and asking it to download the report using the render command. It raises an AbstractController::DoubleRenderError. I was thinking of overriding the error but realized it's a bad idea, I don't know what else to do... Any suggestions? Thanks.
My controller code looks like this:
def download_report
params[:user_id].each do |user_id|
#report = Report.find_by(:user_id => user_id)
render :xlsx => "download_report", :filename => "#{#report.user.last_name}.xlsx"
end
end
My axlsx template:
wb = xlsx_package.workbook
wb.add_worksheet(name: "Reports") do |sheet|
wb.styles do |s|
# template code
end
end
It is the built in expectation of Rails that you would call render once per request. And, the browser is going to expect one response per request. So, you are going to have to do something else!
You can use render_to_string, and combine the results into a zip file, serving that. See the bottom of this response.
Or, you could create a single spreadsheet and have each user's report show up on their own worksheet.
Or, on the client side, you could use javascript to request each spreadsheet and download each one separately.
The zip one would be something like this code, which uses render_to_string, rubyzip, and send_data:
def download_report
compressed_filestream = Zip::ZipOutputStream.write_buffer do |zos|
params[:user_id].each do |user_id|
#report = Report.find_by(:user_id => user_id)
content = render_to_string :xlsx => "download_report", :filename => "#{#report.user.last_name}.xlsx"
zos.put_next_entry("user_#{user_id}.xlsx")
zos.print content
end
end
compressed_filestream.rewind
send_data compressed_filestream.read, :filename => 'download_report.zip', :type => "application/zip"
end
Axlsx requires rubyzip, so you should have it already. And you probably want to lookup each user and use their name for the spreadsheet, unless you have it otherwise.

How to use valid? method on an array in rails 3

I am working on an app in rails 3.
I have several records that i want to save to my database. I am trying to make sure that all the objects in an array (the records are stored in an array) are valid before saving. The Owner model validates the presence of name and email. In the rails console, I have tried the following:
#owner = Array.new
=> []
#owner[0] = Owner.new (name:"peter", email:"peter#gmail.com")
=> returns object
#owner[1] = Owner.new (name:"fred", email:"fred#gmail.com")
=> returns object
#owner[2] = Owner.new (name:"", email:"")
=> returns object
#owner[0].valid?
=> true
#owner[1].valid?
=> true
#owner[2].valid?
=> false
#owner.each { |t| t.valid? }
=> returns an array like this: [object1, object2, object3]. I would expect something like this instead: [true,true,false]
I dont understand why the .valid? method works fine if I individually check the elements of the array using #owner[i], but doesnt work correctly if I'm using .each to iterate through the array. Anybody know what might be the problem?
What I am trying to do is achieve something like this:
(#owner.each { |t| t.valid? }).all?
To make sure that each record is valid, then I can proceed to save them.
Thanks
Each does not return an array of valid? values. You probably want either:
(#owner.collect { |t| t.valid? }).all?
or
(#owner.all? { |t| t.valid? })
The examples can also be written as:
#owner.collect(&:valid?).all?
or
#owner.all?(&:valid?)

Is this the right way to submit files over a JSON api using ruby?

I posted yesterday a question about how to post files through JSON APIs here:
Posting JSON with file content on Ruby / Rails
However I couldn't really find exactly what I was looking for, so I tried by doing the following:
1) I wrote a rake task to do the upload:
desc "Tests JSON uploads with attached files on multipart formats"
task :picture => :environment do
file = File.open(Rails.root.join('lib', 'assets', 'photo.jpg'))
data = {title: "Something", description: "Else", file_content: Base64.encode64(file.read)}.to_json
req = Net::HTTP::Post.new("/users.json", {"Content-Type" => "application/json", 'Accept' => '*/*'})
req.body = data
response = Net::HTTP.new("localhost", "3000").start {|http| http.request(req) }
puts response.body
end
And then got this on the controller/model of my rails app, like this:
params[:user] = JSON.parse(request.body.read)
...
class User < ActiveRecord::Base
...
has_attached_file :picture, formats: {medium: "300x300#", thumb: "100#100"}
def file_content=(c)
filename = "#{Time.now.to_f.to_s.gsub('.', '_')}.jpg"
File.open("/tmp/#{filename}", 'wb') {|f| f.write(Base64.decode64(c).strip) }
self.picture = File.open("/tmp/#{filename}", 'r')
end
end
So, question is: Am I reinventing the wheel or is this the right way to do it?
BTW: It works, I just need to know if this is a convention for uploading files through json.
JSON is a data serializing format. There is no standard pattern for uploading data or files as data in the serialized object. JSON has expectations that the data fields will be basic objects so you probably want to use Base64 encoding of the file to turn it into a string.
You are free to define your structure however you want, and processing it is your responsibility.

Savon returning XML as string, not hash

I am trying to parse a SOAP response using Savon. The response is XML but is being returned as one long string. If I use #to_hash the entire XML object is still a string, now stored in
hash[:response][:return]
which means it is still a huge unusable mess.
My code looks like
response = soapClient.request(:get_sites_user_can_access) do
soap.body = { :sessionid => session[:login_response][:login_return],
:eid => user }
end
rep = response.to_hash
pp rep[:get_sites_user_can_access_response][:get_sites_user_can_access_return]
What step am I missing to get useful information out of the response? Note: Unfortunately I can't post the XML response because of the info it contains, but it looks like an entire XML document stored as a string. It's class is Nori::StringWithAttributes
I was able to get the desired results but parsing the Nori string(?) using this documentation. This seems like a less than ideal method, but I realized the last element is an array of hashes. So it's hash, of hashes, with an array of hashes. Anyway, here is what worked for me. Advice on how to make this less ugly and clunky would be appreciated.
response = soapClient.request(:get_sites_user_can_access) do
soap.body = { :sessionid => session[:login_response][:login_return],
:eid => user }
end
rep = response.to_hash[:get_sites_user_can_access_response][:get_sites_user_can_access_return]
hrep = Nori.parse(rep)
hrep[:list][:item].each { |item| pp item[:site_id] }

Resources