Best way to join two different model queries in Rails? - ruby-on-rails

I have two different models that I need to join together in a selection query and list on a page. They share all of attributes that I'll need to reference in the view (created_at, updated_at, name, etc), and I want them in order of creation. I'm wondering what the most efficient way to do this is? I was thinking of performing the selection query on each object individually and adding the relevant parts into a common array but that seems inefficient.
For example if my models were Dogs and Cats and I wanted a list of all dogs and cats of age 5, I was thinking something like
#pets = []
dogs = Dogs.where(:age => 5)
cats = Cats.where(:age => 5)
dogs.each do |dog|
hash = {"id" => dog.id, "name" => dog.name, "created_at" => dog.created_at }
#pets.push(hash)
end
cats.each do |cat|
hash = {"id" => cat.id, "name" => cat.name, "created_at" => cat.created_at }
#pets.push(hash)
end
But is that the best way to do it? also, I'm not sure how to sort the finished array in this example according to date created...

try this
dogs = Dogs.where(:age => 5)
cats = Cats.where(:age => 5)
#pets = (dogs + cats).sort_by { |pet| pet.created_at }
OR if you want your hashes still, use map to create the array of hashes
dogs = Dogs.where(:age => 5)
cats = Cats.where(:age => 5)
#pets = (dogs + cats).sort_by do |pet|
pet.created_at
end.map do |pet|
{ "id" => dog.id, "name" => pet.name, "created_at" => pet.created_at }
end

Related

Rails - Iterate thru Multiple Arrays

I'm looking for a more DRY way to iterate through some code. I have a User model and I want to keep count of certain Users in a ReportRecord model (for reporting).
I have a defined list of values of User.names that I want to record (i.e. "Jan", "Lisa", "Tina"). How can I make this code more DRY as the list is much longer than three values?
#users = User.all
#users.each do |u|
# this part repeats with different names
quantity = u.where("name = ?", "Jan").count
ReportRecord.create(:user_id => u.id, :name => "Jan", :quantity => quantity)
# repeated code with different name
quantity = u.where("name = ?", "Lisa").count
ReportRecord.create(:user_id => u.id, :name => "Lisa", :quantity => quantity)
# repeated code with different name
quantity = u.where("name = ?", "Tina").count
ReportRecord.create(:user_id => u.id, :name => "Tina", :quantity => quantity)
end
I would sum all users first (1 query instead of 3):
quantity_by_name = User.select(:name).where(name: %w(Jan List Tina))
.group(:name).sum(:quantity)
#=> { 'Lisa' => 1, 'Jan' => 2, 'Tina' => 3 }
quantity_by_name.each do |name, quantity|
ReportRecord.create(name: name, quantity: quantity)
end
names = %w(Jan List Tina)
names.each do |name|
count = User.where(name: name).count
ReportRecord.create(name: name, quantity: count) # I don't understand `u.id`
end

How to merge hash with activerecord relations in controller / scope

I have Item model(table) with column [id,name,notes]. then I have hash lets call it stock with column [id_of_item,total_stock],
when I do query in controller I would like to join the hash into the table as additional column so I can show the total_stock of the item.
I prefer not to use map/each (looping through all the items since the items table has thousand records. I still don't know whether this possibly or not, thank you.
if your stock is
[[1, "total_stock_1"], [2, "total_stock_2"]]
you should use
stock = Hash[[[1, "total_stock_1"], [2, "total_stock_2"]]]
to translate your hash to this style
stock = {1 => "total_stock_1", 2 => "total_stock_2"}
stock = {1 => "total_stock_1", 2 => "total_stock_2"}
#items = Item.all.map{|item| item.attributes.merge({total_stock: stock[item.id]})}
# the output will be a json not a ActiveRecordRelation
[
{:id => 1, :name => 'item1', :notes => xxx, :total_stock => "total_stock_1"},
{:id => 2, :name => 'item2', :notes => yyy, :total_stock => "total_stock_2"}
]
You can do this in controller:
#items = Item.all
render json: #items.map{|item| {'item': item.as_json.merge stock.select{|item| item['id_of_item'] == item.id}['total_stock']} }}

How can I use bulk insert in the case

how can I use bulk insert from array in Rails, like below:
name_list = [{"id" => 1,"name" => "bob"},{"id" => 2,"name" => "ted"}]
Namelist.import name_list
I can`t insert values of above array.
You can do that in single insert query using activerecord-import gem.
name_list = [{"id" => 1,"name" => "bob"},{"id" => 2,"name" => "ted"}]
namelist_objects = []
name_list.each do |n|
namelist_objects << Namelist.new(n)
end
Namelist.import(namelist_objects)
The above answers of others will work fine but name_list.size number of insert queries will run which is not feasible when the array is big.
Hope that helps!
You can just pass the array of hash to create
Namelist.create([{id: 1, name: "bob"}, {id: 2, name: "ted"}])
I am not sure what you want, but maybe it will help you. I think you want create Namelist for both name_lists, isn't ?
name_list = [{"id" => 1,"name" => "bob"},{"id" => 2,"name" => "ted"}]
name_list.map{|k| Namelist.create(k) }

Create json document from ActiveRecord and avoid N+1

I'm trying to create a json array (string actually) based on my db structure. I have the following relationship:
Country > State > City
The way I'm doing it now is very innefficient (N+1):
data = "[" + Country.all.map{ |country|
{
name: country.name,
states: country.states_data
}.to_json
}.join(",") + "]"
Then on the Country model:
def states_data
ret_states = []
states.all.each do |state|
ret_states.push name: state.name, cities: state.cities_data
end
ret_states
end
Then on the State model:
def cities_data
ret_cities = []
cities.all.each do |city|
ret_cities.push name: city.name, population: city.population
end
ret_cities
end
How can I do this more efficiently?
Eager load the states and cities. Just be careful because this could take up a lot of memory for large datasets. See documentation here http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations. Whenever possible I like using joins in addition to includes to fetch all data at once.
#to_json will also serialize Arrays for you, so you don't need to manually add bits of JSON.
Your code from above could be altered like so:
data = Country.joins(:states => :cities).includes(:states => :cities).all.map{ |country|
{
name: country.name,
states: country.states_data
}
}.to_json
But you could also remove the need for the _data methods.
data = Country.joins(:states => :cities).includes(:states => :cities).to_json(
:only => :name,
:include => {
:states => {
:only => :name,
:include => {
:cities => {
:only => [:name, :population]
}
}
}
}
)
That is pretty ugly, so you may want to look into overriding #as_json for each of your models. There is a lot of information about that available on the web.
u can provide the model to be included when converting to json.
country.to_json(:include => {:states => {:include => :cities}})
check http://apidock.com/rails/ActiveRecord/Serialization/to_json for

Making an if statement inside a hash in a model

i am trying to display all the residents on a pdf and their dependents, but the dependents doesn't have a stand they are identified by the foreign key user_id of the resident. e.g resident1.id = 5 --- dependent3.user_id = 5 mean dependent3 belongs to resident1, and therefore if i want to display the dependent stand what should i do and i would like to display all the residents and their dependents and the stand information for dependents to be the stand info of the resident the dependent belong to. now my information should be inside a hash so it can generate my pdf file..
my code is
data = []
c = 1
residents.each do |r|
data <<{"Fullname" => r.firstname, "Lastname" => r.lastname, "Street-Number" => r.stand.street_no, "street Name" => r.stand.streetname} if r.stand || r.user_id = r.id
end
remember my dependents and residents are in the same table but residents doesn't have the user_id foreigh key only the dependent have it.
and my output only display the information of the residents who have stands not the dependents.
please anyone who is willing to help.cause i dont know if i can but an if statement inside a hash like:
residents.each do |r|
data <<{"Fullname" => r.firstname, "Lastname" => r.lastname} if r.stand || r.user_id = r.id{"Street-Number" => r.stand.street_no, "street Name" => r.stand.streetname}
Assuming that .stand.street_no, etc. is valid even if .stand is false (since it could be false and still be called if the r.user_id == r.id part is true), then the following would work:
data = residents.map do |r|
hash = { "Fullname" => r.firstname, "Lastname" => r.lastname }
hash.merge!({ "Street-Number" => r.stand.street_no, "street Name" => r.stand.streetname}) if r.stand || r.user_id == r.id
hash
end
Explanation:
Iterate through each resident object and build a hash that contains values that we know will be used.
Conditionally merge! in the extra keys if the if clause is true.
Return the hash.
Doing this in a map eliminates the need to predeclare the data array, nor push the hash on each pass through the loop.
data = []
residents.each do |r|
tmp_hash = {"Fullname" => r.firstname, "Lastname" => r.lastname}
if r.stand || r.user_id = r.id
tmp_hash.merge({"Street-Number" => r.stand.street_no, "street Name" => r.stand.streetname})
end
data << tmp_hash
end

Resources