Best Rails 3.1 practice to complex query - ruby-on-rails

Given:
(models):
Contract (has_many :invoices, belongs_to :user)
Invoice (belongs_to :contract)
This way, for example:
my_contracts = Contract.where(user_id: current_user.id) #=> [#<Contract id: 1, title: "1">, #<Contract id: 2, title: "2">]
In this case we have two Contracts for User. And each of Contracts have multiple number of Invoices.
Now we need to gather all Invoices for each of contracts and sort them by 'updated_at'.
Something like:
all_invoices = my_contracts.map{|i| i.invoices.sort_by(&:updated_at)}
but using ActiveRecord.
How it could be done right?

The way you are doing it is not bad, just use includes to get eager loading of the invoices instead of lazy (n+1) loading
contracts = Contract.where(:user_id => current_user.id).includes(:invoices)
# or this might do the same => current_user.contracts.includes(:invoices)
invoices = contracts.map{|i| i.invoices }
invoices.sort_by(&:updated_at).each do |invoice|
# ....
end
try this and also what David Sulc posted, view the generated sql and experiment with the result in rails console; using joins vs includes has very different behavior, depending on situation one maybe better than the other
see also
http://guides.rubyonrails.org/active_record_querying.html#joining-tables
http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations

It should be something like
Invoice.joins(:contracts).where(user_id: current_user.id).order(:updated_at)
Arel isn't a strong suit of mine (still need to work on it), but if this doesn't work, it should at least get you closer to the answer.

Related

How to execute where condition on a joined table without querying the db in a loop

I have the following models
App has_many features
Feature has_many translations
Now I want to lookup a translation in order see to if I need to create or update.
App.joins(features: :translations).first.features.each do |feature|
languages.each do |lang|
translation = feature.translations.where(language: lang).first_or_initialize
end
end
Now the problem is that I have a db lookup for each language which is quite a problem since I have a lot of features.
How can I benefit from eager loading in order to just lookup the translation in the joined table?
Lets partition the languages into two two arrays, found and not_found:
found, not_found = languages.partition do |lang|
App.joins(:features => :translations).where("translations.language" => lang).present?
end
found_language_translations = Translation.where("language in (?)", found)
new_language_translations = not_found.map{|lang| Translation.new(language: lang) } #This will fire up too many queries though :/

Ruby on Rails: Organize records from ActiveRecord based on its parent_id or parent record

I have this simple array delivered from activerecord
[#<Product id: 1, parent_id: , text: "Hurray">, #<Product id: 2, parent_id: 1, text: "Hurray again">, #<Product id: 3, parent_id: 1, text: "Hurray again 2">, #<Product id: 4, parent_id: 2, text: "Boo yeah !">]
As you can see each entry has a parent_id. Now the the idea here is to group the Product along with its child product. Now the way the child product is known by, is by its parent_id. So for example, Product 2's parent product is Product 1.
Now, I have setup the database and all the actions in the controller in such a way that the result delivered should be delivered as mentioned below.
Although, i am unable to do now is show the detail in a simple format. Which probably should look like on a rails erb page
1. Product 1 has products
2. Product 2 has products
1. Product 4
3. Product 3 has products
Nil
4. Product 4 has products
Nil
I tried my running a loop of all the product and put an if condition with something like if n.parent_id.present?. But I only got one level deep, as you can see I am expecting the result is multiple levels and result is different all the time.
Any help or guidance here is appreciated.
Thanks
UPDATE: What I tried is this
<%#products.each do |n|%>
<li><%=Product "#{n.id}" has products%></li>
<%if n.parent_id.present?%>
<li><%=Product "#{n.id}" has products%></li>
<%end%>
<%end%>
The problem you see here, I cant go on using if/else if the level is too deep. In other words I am trying to display a thread based view for products.
Hope I am making sense :).
Please use acts_as_tree or the ancestry gem. This http://railscasts.com/episodes/262-trees-with-ancestry should give you the info you need.
I see a deep lack of understanding ActiveRecord Relations.
Using a foreign key inside your table is not for multiple ownings. You'd need to create a one to many association, probably.
See http://guides.rubyonrails.org/association_basics.html
If I don't misunderstood what you are asking (table organized as a tree), I had some thoughts about similar problem and discussed it with a friend. Notice that is a not-so-small issue anyway, there are various ways to face it. The one I prefer is this:
Create a new table and call it product_products. In this way you obtain one important point: you can have one product in multiple parents (which may happen) and you can "easily" build nested levels. Notice that you still need to put a depth limit (or otherwise you must block in some way loop referencing), to avoid loop references destroy your server.
For printing that out, to clean up your mind, I suggest you to use a recursive method (a helper in this case).
For example (based on your current table structure, not on the one I suggested):
def tree_print(products)
products.each do |product|
nested_tree_print(product)
end
end
def nested_tree_print(product)
print product.name
nested_tree_print(product) unless product.parent_id.nil?
end
Please consider it pseudocode, is just a basic idea how to solve the problem.
You can also read about these two gems: ancestry and acts_as_tree
Update 1
After reading your comments, let's suppose that Product has_many products, here is some pseudocode:
def tree_print(products)
    products.each do |product|
        nested_tree_print(product)
    end
end
# Consider this like a private method, you should use the other in views
def nested_tree_print(product)
product.products.each do |printable_product|
print printable_product.name
nested_tree_print(product) unless product.parent_id.nil?
end
end
Care that this code is definitely not optimized. If there aren't a lot of products is ok (you will benefit a lot from caching everything in memory), it can cause stackoverflows easily and you need to check for max-depth.

Datamapper: Sorting results through association

I'm working on a Rails 3.2 app that uses Datamapper as its ORM. I'm looking for a way to sort a result set by an attribute of the associated model. Specifically I have the following models:
class Vehicle
include DataMapper::Resource
belongs_to :user
end
class User
include DataMapper::Resource
has n, :vehicles
end
Now I want to be able to query the vehicles and sort them by the name of the driver. I tried the following but neither seems to work with Datamapper:
> Vehicle.all( :order => 'users.name' )
ArgumentError: +options[:order]+ entry "users.name" does not map to a property in Vehicle
> Vehicle.all( :order => { :users => 'name' } )
ArgumentError: +options[:order]+ entry [:users, "name"] of an unsupported object Array
Right now I'm using Ruby to sort the result set post-query but obviously that's not helping performance any, also it stops me from further chaining on other scopes.
I spent some more time digging around and finally turned up an old blog which has a solution to this problem. It involves manually building the ordering query in DataMapper.
From: http://rhnh.net/2010/12/01/ordering-by-a-field-in-a-join-model-with-datamapper
def self.ordered_by_vehicle_name direction = :asc
order = DataMapper::Query::Direction.new(vehicle.name, direction)
query = all.query
query.instance_variable_set("#order", [order])
query.instance_variable_set("#links", [relationships['vehicle'].inverse])
all(query)
end
This will let you order by association and still chain on other scopes, e.g.:
User.ordered_by_vehicle_name(:desc).all( :name => 'foo' )
It's a bit hacky but it does what I wanted it to do at least ;)
Note: I'm not familiar with DataMapper and my answer might not be within the standards and recommendations of using DataMapper, but it should hopefully give you the result you're looking for.
I've been looking through various Google searches and the DataMapper documentation and I haven't found a way to "order by assocation attribute". The only solution I have thought of is "raw" SQL.
The query would look like this.
SELECT vehicles.* FROM vehicles
LEFT JOIN users ON vehicles.user_id = users.id
ORDER BY users.name
Unfortunately, from my understanding, when you directly query the database you won't get the Vehicle object, but the data from the database.
From the documentation: http://datamapper.org/docs/find.html. It's near the bottom titled "Talking directly to your data-store"
Note that this will not return Zoo objects, rather the raw data straight from the database
Vehicle.joins(:user).order('users.name').all
or in Rails 2.3,
Vehicle.all(:joins => "inner join users on vehicles.user_id = user.id", :order => 'users.name')

rails mongoid criteria find by association

I'm trying to find a record by associated username which is included in a belongs_to relation, but it's not working.
Articles belong to Users
Users have many articles
Article.where(user_id: someid) works fine, but I'd like to use the username as reference which is stored in the Users table.
Article.includes(:user).where(:username => "erebus")
Article.includes(:user).where("user.username" => "erebus")
I also have identity_map_enabled: true
Article.includes(:user).inclusions returns the relation details
Doesn't work, what am I not understanding?
You have to keep in mind that there are no joins in mongodb. In relational dbs, includes forms a join query and you can use columns from both the tables in query. However due to absence of joins in mongodb, same is not possible.
In mongoid, includes just saves a bunch of db calls. It fetches and stores the associated records in identity map for fast retrieval, but still while querying, one query can only deal with one collection.
If you need articles based on user names, I would suggest following work around:
user_ids = User.where(username: 'erebus').only(:_id).map(&:_id)
articles = Article.where(:user_id.in => user_ids)
You can make it little shorter from what rubish suggested:
user_ids = User.where(username: 'erebus').pluck(:id)
articles = Article.where(:user_id.in => user_ids)
Or one liner:
articles = Article.where(:user_id.in => User.where(username: 'erebus').pluck(:id))

Help converting Rails 2 Database logic to Rails 3.1/ PostgreSQL

How do I select a single random record for each user, but order the Array by the latest record pr. user.
If Foo uploads a new painting, I would like to select a single random record from foo. This way a user that uploads 10 paintings won't monopolize all the space on the front page, but still get a slot on the top of the page.
This is how I did it with Rails 2.x running on MySQL.
#paintings = Painting.all.reverse
first_paintings = []
#paintings.group_by(&:user_id).each do |user_id, paintings|
first_paintings << paintings[rand(paintings.size-1)]
end
#paintings = (first_paintings + (Painting.all - first_paintings).reverse).paginate(:per_page => 9, :page => params[:page])
The example above generates a lot of SQL query's and is properly badly optimized. How would you pull this off with Rails 3.1 running on PostgreSQL? I have 7000 records..
#paintings = Painting.all.reverse = #paintings = Painting.order("id desc")
If you really want to reverse the order of the the paintings result set I would set up a scope then just use that
Something like
class Painting < ActiveRecord::Base
scope :reversed, order("id desc")
end
Then you can use Painting.reversed anywhere you need it
You have definitely set up a belongs_to association in your Painting model, so I would do:
# painting.rb
default_scope order('id DESC')
# paintings_controller.rb
first_paintings = User.includes(:paintings).collect do |user|
user.paintings.sample
end
#paintings = (first_paintings + Painting.where('id NOT IN (?)', first_paintings)).paginate(:per_page => 9, :page => params[:page])
I think this solution results in the fewest SQL queries, and is very readable. Not tested, but I hope you got the idea.
You could use the dynamic finders:
Painting.order("id desc").find_by_user_id!(user.id)
This is assuming your Paintings table contains a user_id column or some other way to associate users to paintings which it appears you have covered since you're calling user_id in your initial code. This isn't random but using find_all_by_user_id would allow you to call .reverse on the array if you still wanted and find a random painting.

Resources