Why does Rails `includes` method not add included model to attributes? - ruby-on-rails

I'm running into a strange issue with nested rails models related to the includes method. I'm attempting to simply move an item from one object to its parent like so:
Current:
[
{
"created_on": "2014-09-11T15:52:34-04:00",
"id": 8,
"mail_notification": false,
"project_id": 2,
"user_id": 15,
"member_roles": [
{
"id": 10,
"inherited_from": null,
"member_id": 8,
"role_id": 3
}
]
}
]
Needed:
[
{
"created_on": "2014-09-11T15:52:34-04:00",
"id": 8,
"mail_notification": false,
"project_id": 2,
"user_id": 15,
"role_id": 3
}
]
For some reason, when I loop through the current object, It strips out the :member_roles. Case in point:
members = Member.includes(:member_roles).find_all_by_project_id(#project)
# Contains :member_roles
puts members.to_json(include: [:member_roles])
#=> [{"created_on":"2014-09-11T15:52:34-04:00","id":8,"mail_notification":false,"project_id":2,"user_id":15,"member_roles":[{"id":10,"inherited_from":null,"member_id":8,"role_id":3}]}]
# Does not contain :member_roles
puts members.first.attributes
#=> {"id"=>8, "user_id"=>15, "project_id"=>2, "created_on"=>Thu, 11 Sep 2014 15:52:34 EDT -04:00, "mail_notification"=>false}
Why does the :member_roles object disappear?

you cannot do what you expect.
Member.includes(:member_roles) is eager loading your relation (ie it fetchs all the member_roles instances required by the member collection when you actually use on of this object the first time)
to_json(include: [:member_roles]) is including the json reprensation of the related model in the parent member model json.
What you describe is called method delegation (Module.delegate), but since you have a one to many relation between your 2 models you cannot do it

Related

LUA: add values in nested table

I have a performance issue in my application. I would like to gather some ideas on what I can do to improve it. The application is very easy: I need to add values inside a nested table to get the total an user wants to pay out of all the pending payments. The user chooses a number of payments and I calculate how much it is they will pay.
This is what I have:
jsonstr = "{ "name": "John",
"surname": "Doe",
"pending_payments": [
{
"month": "january",
"amount": 50,
},
{
"month": "february",
"amount": 40,
},
{
"month": "march",
"amount": 45,
},
]
}"
local lunajson = require 'lunajson'
local t = lunajson.decode(jsonstr)
local limit -- I get this from the user
local total = 0;
for i=1, limit, 1 do
total = total + t.pending_payments[i].amount;
end;
It works. At the end I get what I need. However, I notice that it takes ages to do the calculation. Each JSON has only twelve pending payments (one per month). It is taking between two to three seconds to come up with a result!. I tried in different machines and LUA 5.1, 5.2., 5.3. and the result is the same.
Can anyone please suggest how I can implement this better?
Thank you!
For this simple string, try the test code below, which extracts the amounts directly from the string, without a json parser:
jsonstr = [[{ "name": "John",
"surname": "Doe",
"pending_payments": [
{
"month": "january",
"amount": 50,
},
{
"month": "february",
"amount": 40,
},
{
"month": "march",
"amount": 45,
},
]
}]]
for limit=0,4 do
local total=0
local n=0
for a in jsonstr:gmatch('"amount":%s*(%d+),') do
n=n+1
if n>limit then break end
total=total+tonumber(a)
end
print(limit,total)
end
I found the delay had nothing to do with the calculation in LUA. It was related with a configurable delay in the retrieval of the limit variable.
I have nothing to share here related to the question asked since the problem was actually in an external element.
Thank #lfh for your replies.

delete attributes nested within an array of objects Ruby on rails

I need to remove the attribute nested1 that is inside the attr7_nested in a massive way, I don't need to persist this data in the bank, just store it in a variable to send to a log file.
example file follows:
[{
"attr1": 120,
"attr2": 24,
"attr3": 11400,
"attr4": "Caixa",
"attr5": 2000000,
"attr6": 1744000,
"attr7_nested": {
"nested1": 1,
"nested2": "Essential",
"nested3": "med",
"nested4": "Med"
}
},
{
"attr1": 120,
"attr2": 24,
"attr3": 11400,
"attr4": "Caixa",
"attr5": 2000000,
"attr6": 1744000,
"attr7_nested": {
"nested1": 1,
"nested2": "Ess",
"nested3": "med",
"nested4": "Med"
}
}]
When array is the array containing the nested hashes from your question than the following would remove the nested1 keys from all nested attr7_nested hashes:
array.each { |hash| hash[:attr7_nested].delete(:nested1) }

How to get details of grouped object

I am building a Rails 5 app and in this app I got four models.
User, Report, Trip and Expense.
User has_many Reports, trips and expenses
Reports has_many trips and expenses.
I want to get a JSON response with all the trips and expenses that a user has done this month, grouped by Report.
I can do this but I need only the Id and Title of the report (when grouping) and now I get only the object name.
I use this method (located in the User model):
def grouped_reports
trips = self.trips
expenses = self.expenses
items = trips + expenses
items.group_by(&:report)
end
The output in JSON is this:
{
"#<Report:0x007fa225163ba8>": [{
"id": 12,
"account_id": 20,
"user_id": 92,
"vehicle_id": null,
"ttype": null,
"description": "saf",
"start_latitude": 57.4874919,
"start_longitude": 12.0761927999999,
"end_latitude": 59.3293235,
"end_longitude": 18.0685808000001,
"start_address": "Chicago",
"end_address": "New york",
"distance": 490,
"time": null,
"status": "pending",
"registered_at": "2018-08-24T02:00:00.000+02:00",
"approvals_count": 0,
"rejections_count": 0,
"created_at": "2018-08-24T22:39:22.637+02:00",
"updated_at": "2018-08-24T22:39:22.637+02:00",
"report_id": 79,
"return_trip": null,
"triptype_id": 10
},
{
"id": 13,
"account_id": 20,
"user_id": 92,
"cost": 100,
"etype": null,
"description": "sdsd",
"start_address": null,
"end_address": null,
"distance": null,
"reimbursable": false,
"status": "pending",
"registered_at": "2018-08-08T00:00:00.000+02:00",
"created_at": "2018-08-24T22:39:40.343+02:00",
"updated_at": "2018-08-24T22:39:40.343+02:00",
"approvals_count": 0,
"rejections_count": 0,
"report_id": 79,
"expensetype_id": 15
}
]
}
There is two things I need to improve.
Display id and title of report not .
Get only this months reports.
Update, this works but is it the right way in terms of performance?
And not only performance, is it actually using the title of the report as a grouping factor? Which is not good since may reports can share the same title. I want to group by report_id but display report title.
def grouped_reports
trips = self.trips
expenses = self.expenses
items = trips + expenses
items.group_by{ |t| [t.report.id, t.report.title] }
end
Code would differ depending on what JSON format you want to output.
I usually use json generator such jbuilder, but this time I suggest a array and hash structure.
class User < ApplicationRecord
has_many :reports
has_many :expenses
has_many :trips
def grouped_reports
start_date = Time.current.beginning_of_month
monthly_trips = trips.where('created_at >= ?', start_date)
monthly_expenses = expenses.where('created_at >= ?', start_date)
report_ids = (monthly_trips.map(&:report_id) + monthly_expenses.map(&:report_id)).uniq
reports = Report.where(id: report_ids)
reports.map do |report|
{
id: report.id,
title: report.title,
trips: trips.select {|t| t.report_id == report.id},
expenses: expenses.select {|e| e.report_id == report.id}
}
end
end
end
You are grouping with trips and expenses joined to one array, but it is not preferable to put different types in same arrays for JSON. It would be safe to have a hash format and separate key for trip and expense.
To extract records for this month, use where to filter.
It is possible to fetch trips by using includes for reports and expenses, but from a performance point of view it is better to get the related trips at once.
If you want to further improve performance, narrow down only columns used when outputting JSON by using select method. This would be a huge improvement if a lot of records are outputted.

Search a JSON Response using Ruby

I'm using a Ruby script to interface with an application API and the results being returned are in a JSON format. For example:
{
"incidents": [
{
"number": 1,
"status": "open",
"key": "abc123"
}
{
"number": 2,
"status": "open",
"key": "xyz098"
}
{
"number": 3,
"status": "closed",
"key": "lmn456"
}
]
}
I'm looking to search each block for a particular "key" value (yzx098 in this example) and return the associated "number" value.
Now, I'm very new to Ruby and I'm not sure if there's already a function to help accomplish this. However, a couple days of scouring the Googles and Ruby resource books hasn't yielded anything that works.
Any suggestions?
First of all, the JSON should be as below: (note the commas)
{
"incidents": [
{
"number": 1,
"status": "open",
"key": "abc123"
},
{
"number": 2,
"status": "open",
"key": "xyz098"
},
{
"number": 3,
"status": "closed",
"key": "lmn456"
}
]
}
Strore the above json in a variable
s = '{"incidents": [{"number": 1,"status": "open","key": "abc123"},{"number": 2,"status": "open","key": "xyz098"},{"number": 3,"status": "closed","key": "lmn456"}]}'
Parse the JSON
h = JSON.parse(s)
Find the required number using map
h["incidents"].map {|h1| h1['number'] if h1['key']=='xyz098'}.compact.first
Or you could also use find as below
h["incidents"].find {|h1| h1['key']=='xyz098'}['number']
Or you could also use select as below
h["incidents"].select {|h1| h1['key']=='xyz098'}.first['number']
Do as below
# to get numbers from `'key'`.
json_hash["incidents"].map { |h| h['key'][/\d+/].to_i }
json_hash["incidents"] - will give you the value of the key "incidents", which is nothing but an array of hash.
map to iterate thorough each hash and collect the value of 'key'. Then applying Hash#[] to each inner hash of the array, to get the value of "key". Then calling str[regexp], to get only the number strings like '098' from "xyz098", finally applying to_i to get the actual integer from it.
If the given hash actually a json string, then first parse it using JSON::parse to convert it to a hash.Then do iterate as I said above.
require 'json'
json_hash = JSON.parse(json_string)
# to get values from the key `"number"`.
json_hash["incidents"].map { |h| h['number'] } # => [1, 2, 3]
# to search and get all the numbers for a particular key match and take the first
json_hash["incidents"].select { |h| h['key'] == 'abc123' }.first['number'] # => 1
# or to search and get only the first number for a particular key match
json_hash["incidents"].find { |h| h['key'] == 'abc123' }['number'] # => 1

rails extract data from simple json response

I need to extract some data from a JSON response i'm serving up from curb.
Previously I wasn't calling symbolize_keys, but i thought that would make my attempt work.
The controller action:
http = Curl.get("http://api.foobar.com/thing/thing_name/catalog_items.json?per_page=1&page=1") do|http|
http.headers['X-Api-Key'] = 'georgeBushSucks'
end
pre_keys = http.body_str
#foobar = ActiveSupport::JSON.decode(pre_keys).symbolize_keys
In the view (getting undefined method `current_price' )
#foobar.current_price
I also tried #foobar.data[0]['current_price'] with the same result
JSON response from action:
{
"data": {
"catalog_items": [
{
"current_price": "9999.0",
"close_date": "2013-05-14T16:08:00-04:00",
"open_date": "2013-04-24T11:00:00-04:00",
"stuff_count": 82,
"minimum_price": "590000.0",
"id": 337478,
"estimated_price": "50000.0",
"name": "This is a really cool name",
"current_winner_id": 696969,
"images": [
{
"thumb_url": "http://foobar.com/images/93695/thumb.png?1365714300",
"detail_url": "http://foobar.com/images/93695/detail.png?1365714300",
"position": 1
},
{
"thumb_url": "http://foobar.com/images/95090/thumb.jpg?1366813823",
"detail_url": "http://foobar.com/images/95090/detail.jpg?1366813823",
"position": 2
}
]
}
]
},
"pagination": {
"per_page": 1,
"page": 1,
"total_pages": 131,
"total_objects": 131
}
}
Please note that accessing hash's element in Rails work in models. To use it on hash, you have to use OpenStruct object. It's part of standard library in rails.
Considering, #foobar has decoded JSON as you have.
obj = OpenStruct.new(#foobar)
obj.data
#=> Hash
But, note that, obj.data.catalog_items willn't work, because that is an hash, and again not an OpenStruct object. To aid this, we have recursive-open-struct, which will do the job for you.
Alternative solution [1]:
#foobar[:data]['catalog_items'].first['current_price']
But, ugly.
Alternative solution [2]:
Open Hash class, use method_missing ability as :
class Hash
def method_missing(key)
self[key.to_s]
end
end
Hope it helps. :)

Resources