Reputation: 1859
Can somebody point out how ActiveRecord is generating a nested hash out of a Left Outer Join query?
I am doing an eager load for a complex query in which different WHERE clauses needs to be applied on to the has_many associated tables. When Rails generate the query to fetch associations with conditions applied, I do not get the intended result.
I am able to construct the result in pieces - for the driving table, and separately for the has_many associations. Now I need to merge the results of association tables into the driving table.
Or in simpler terms, I have the following tables in an application.
Items
___________________
| id | item_name |
___________________
| 1 | "Item One" |
| 1 | "Item One" |
| 1 | "Item One" |
| 1 | "Item One" |
| 1 | "Item One" |
___________________
Inventories
_________________
| id | quantity |
_________________
| 1 | 10 |
| 1 | 10 |
| 1 | 10 |
| 1 | 10 |
| 1 | 10 |
_________________
Transactions
_________________________________________
| id | item_id | tran_status | tran_qty |
_________________________________________
| 1 | 1 | "ordered" | 1 |
| 2 | 1 | "picked" | 1 |
| 3 | 1 | "ordered" | 4 |
| 4 | 1 | "canceled" | 5 |
| 5 | 1 | "ordered | 2 |
_________________________________________
Adjustments
_______________________________________
| id | item_id | adj_status | adj_qty |
_______________________________________
| 1 | 1 | "adjusted" | 1 |
| 4 | 1 | "canceled" | 1 |
_______________________________________
I am trying to create a JSON response for an AJAX action in the following format (using JBuilder).
[{"id": 1,
"name": "Item One",
"inventory": {"quantity": 10},
"transactions": [ {"tran_status": "Ordered", "tran_qty": 1},
{"tran_status": "Picked", "tran_qty": 1},
{"tran_status": "Ordered", "tran_qty": 4},
{"tran_status": "Ordered", "tran_qty": 2} ],
"adjustments": [ {"adj_status": "Adjusted", "adj_qty": 1} ]
}
]
Now that this could be potentially requested for multiple items, I would like to do an eager load during querying.
However, the following query does not work. Rails generates a left-outer join query and that spoils the party.
Item.includes(:inventory).
includes(:transactions).
where("transactions.status not in ('canceled')").
references(:transactions).
includes(:adjustments).
where("adjustments.status not in ('canceled')").
references(:adjustments).
where(id: params[:item])
So, what I am try to do is to execute the queries in three steps and create a hash or Openstruct at the end for iterating in JBuilder.
items = Item.includes(:inventory).
item_ids = items.collect(&:id)
trans = Transaction.where(item_id: item_ids).where("status not in ('canceled')")
adjs = Adjustment.where(item_id: item_ids).where("status not in ('canceled')")
Now I would like to merge all these into a single array of hashes.
I realize that Rails would be doing something like this internally, and thought of tapping into existing knowledge than building a less efficient way of doing this with my limited knowledge.
Any help would be appreciated.
The question was in the context of trying to build a solution myself. The core problem was to get Rails generate queries with conditions applied on associated tables. The accepted answer is the suggestion to build associations with conditions (which can be found in the discussion thread of the accepted answer).
Upvotes: 1
Views: 2566
Reputation: 8058
require 'json'
hash_arr = []
Item.includes(:inventories,:transactions,:adjustments).all.each do |item|
hash = {"id"=>item.id,
"name"=>item.name,
"inventory"=>item.inventories.count,
"transactions"=>item.transactions.select("tran_status,tran_qty").collect{ |x|
{:tran_status=>x.tran_status,:tran_qty=>x.tran_qty}
},
"adjustments"=>item.adjustments.select("adj_status,adj_qty").collect{|x| {:adj_status=>x.adj_status,:adj_qty=>x.adj_qty}
}
}
hash_arr.push hash
end
hash_arr.to_json
Hope this matchs your requirements
Or you can use as_json
and specify your own JSON format( this would be much cleaner). Check here
Upvotes: 0
Reputation: 962
If I'm getting you right, you need to iterate over items:
items = Item.includes(:inventory)
result = []
items.each do |i|
result << {
:item => i,
:trans => Transaction.where(item_id: i.id ).where("status not in ('canceled')"),
:adjs => Adjustment.where(item_id: i.id).where("status not in ('canceled')")
}
end
Or you can get it in arrays with a single collect:
result = Item.includes(:inventory, :transactions, :adjustments).collect { |i| i,
i.transactions.where("status not in ('canceled')"),
i.adjustments.where("status not in ('canceled')")
}
, provided that you have correctly set up the associations
Upvotes: 1