Rails Activerecord multiple table includes - ruby-on-rails

I have four tables:
argument with fields
id
comments with
id
comment_id
argument_id
user_id
users
id
nicknames with
id
proposal_id
user_id
name
each argument has many comments,
each comment belongs to a user,
each user has a specific nickname in the argument.
When I fetch the argument comments from DB, I would like to include also the nicknames of each author.
The answer is about the ActiveRecord query I don't know how to write.
I tried with
#argument.comments.includes(:user => :nicknames)
but it doesn't seems to work and when I get the nickname through
nickname = #argument.nicknames.find_by_user_id(comment.user.id)
it executes the query...
[1m[36mNickname Load (0.6ms)[0m [1mSELECT "nicknames".* FROM "nicknames" WHERE "nicknames"."argument_id" = 59 AND "nicknames"."user_id" = 9 LIMIT 1[0m
any suggestion?

You can tell if an association is loaded with loaded?.
What is happening here, if I understand your problem, is that you are trying to run a finder on an ActiveRecord::Relation. Quickly browsing through the code, it does not appear that it will try to see if a collection is loaded before it issues the query. It does, however, take a block that will avoid multiple queries. For example (the model names have been changed because I am using a sample project I created for another question):
c = Canteen.first
Canteen Load (0.2ms) SELECT "canteens".* FROM "canteens" LIMIT 1
=> #<Canteen id: 1, name: "Really good place", created_at: "2012-12-13 00:04:11", updated_at: "2012-12-13 00:04:11">
c.meals.loaded?
=> false
c.meals.find {|m| m.id == 3}
Meal Load (0.2ms) SELECT "meals".* FROM "meals" WHERE "meals"."canteen_id" = 1
=> #<Meal id: 3, canteen_id: 1, name: "Banana Pie", price: #<BigDecimal:7fcb6784fa78,'0.499E1',18(45)>, created_at: "2012-12-13 00:37:41", updated_at: "2012-12-13 00:37:41">
You see in the last example that ActiveRecord issues the query to load the associated records. This is because ActiveRecord is calling to_a on the association, forcing the entire set to be loaded, and then filtering based on the block conditions. Obviously, this is not ideal.
Let's try again, eager loading the association.
c = Canteen.includes(:meals).first
Canteen Load (0.2ms) SELECT "canteens".* FROM "canteens" LIMIT 1
Meal Load (0.2ms) SELECT "meals".* FROM "meals" WHERE "meals"."canteen_id" IN (1)
=> #<Canteen id: 1, name: "Really good place", created_at: "2012-12-13 00:04:11", updated_at: "2012-12-13 00:04:11">
c.meals.loaded?
=> true
c.meals.find {|m| m.id == 3}
=> #<Meal id: 3, canteen_id: 1, name: "Banana Pie", price: #<BigDecimal:7fcb68b596f0,'0.499E1',18(45)>, created_at: "2012-12-13 00:37:41", updated_at: "2012-12-13 00:37:41">
In the last example here, you see that the collection is not loaded again. Instead, the block is used to filter the already loaded records.
As you can see below, even if the records are loaded, ActiveRecord will issue a query to grab the associated record:
c.meals.loaded?
=> true
c.meals.find(1)
Meal Load (0.1ms) SELECT "meals".* FROM "meals" WHERE "meals"."canteen_id" = 1 AND "meals"."id" = ? LIMIT 1 [["id", 1]]
=> #<Meal id: 1, canteen_id: 1, name: "Enchiladas", price: #<BigDecimal:7fcb6584ce88,'0.699E1',18(45)>, created_at: "2012-12-13 00:04:40", updated_at: "2012-12-13 00:04:40">
SELECT "meals".* FROM "meals" WHERE "meals"."canteen_id" = 1 AND "meals"."id" = 3
=> [#<Meal id: 3, canteen_id: 1, name: "Banana Pie", price: #<BigDecimal:7fcb68b808e0,'0.499E1',18(45)>, created_at: "2012-12-13 00:37:41", updated_at: "2012-12-13 00:37:41">]

Maybe something like :
#argument.includes(:comments => [{ :user => :nicknames }])
Didn't try it though...

You can try something like this to include more than one table
User.find(:all, :include => Room.find(:all,:include => :review))

Related

Ruby: Convert an array or hash to an activerecord relation

I am trying to convert a hash to an activerecord relation but am not able to do so. I intend to use the activerecord relation to sort and then filter the Category table. The end goal is to create an instance method to filter the top 5 visited categories, which i can then use/call in the controller. This is what i have in mind:
Category model:
def top_5_visited
Article.joins(:categories).group('categories.name').sum(:impressions_count)
// Add sort
// Limit to top 5
end
Category controller:
#categories = Category.top_5 visited
A hash {"Simula"=>7, "Haskell"=>5, "JavaScript"=>10, "C#"=>112} will be created through the following query:
total_count = Article.joins(:categories).group('categories.name').sum(:impressions_count)
I have also tried to convert it to an array using sort_by method:
total_count_sorted = total_count.sort_by {|_key, value| value}
I have googled "convert array to activerecord relation" and referenced this post, but testing this:
Category.where(id: total_count_sort.map(&:id))
in the rails console, brings up this error:
NoMethodError: undefined method id for ["Simula", 7]:Array
What you want to do start from the inverse end (Category) and use an aggregate in the ORDER clause.
Category.joins(:articles)
.order('SUM(articles.impressions_count) DESC')
.group(:id)
.limit(5)
irb(main):005:0> Category.joins(:articles).order("SUM(articles.impressions_count) DESC").group('categories.id').limit(5)
Category Load (1.5ms) SELECT "categories".* FROM "categories" INNER JOIN "articles" ON "articles"."category_id" = "categories"."id" GROUP BY categories.id ORDER BY SUM(articles.impressions_count) DESC LIMIT $1 [["LIMIT", 5]]
=> #<ActiveRecord::Relation [#<Category id: 4, name: "C#", created_at: "2017-11-15 15:06:32", updated_at: "2017-11-15 15:06:32">, #<Category id: 3, name: "JavaScript", created_at: "2017-11-15 15:06:32", updated_at: "2017-11-15 15:06:32">, #<Category id: 1, name: "Simula", created_at: "2017-11-15 15:03:37", updated_at: "2017-11-15 15:03:37">, #<Category id: 2, name: "Haskell", created_at: "2017-11-15 15:06:32", updated_at: "2017-11-15 15:06:32">]>
And you should create a class method - not an instance method as this is basically just a scope and does not make sense to call on an instance.
class Category < ApplicationRecord
has_many :articles
def self.order_by_article_impressions
self.joins(:articles)
.order('SUM(articles.impressions_count)')
.group(:id)
end
def self.top_5_visited
order_by_article_impressions.limit(5)
end
# Or use `scope` which is just syntactic sugar
scope(:top_5_visited) -> { order_by_article_impressions.limit(5) }
end
Change the code to:
Category.where(id: total_count_sort.map(&:last))

Finding matches from an array rails

Patient have an array of clinician id's that they are shared with stored in shared_with. I would like to get a list of the patients where the current user, a clinician, has their id stored in the patient's shared_with
What I have tried to do now doesn't work:
#shared = Patient.find_by(shared_with: current_user.clinician.id).order("first_name asc")
For example, our current_user is associated with clinician.id 1 and there are patients with shared_with values of 1, 4 for patient 10 and 1, 7 for patient 15. I want #shared to be a list with just patient 10 and 15.
Patient model:
Patient:
clinician_id: integer
first_name: string
last_name: string
user_id: integer
shared_with: string
serialize :shared_with, Array
Patient.rb:
class Patient < ActiveRecord::Base
belongs_to :clinician
belongs_to :user
accepts_nested_attributes_for :user, :allow_destroy => true
end
As far as I can tell, the Patient model doesn't need a belongs_to for clinicians, and doesn't need a clinician_id -- unless these are related in another fashion...in which case, carry on.
Assuming your database supports an array field (such as postgres) then you're very close. You just need to wrap it in braces and since it's now in quotes, you'll need a #{} set for interpolation. Like so:
Patient.where(shared_with: "{#{current_user.clinician.id}}").order("first_name asc")
Doing a test with mock modeling you provided I see this in the console:
2.1.1 :005 > current_user = User.first
User Load (0.8ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> #<User id: 1, name: "benji", created_at: "2015-08-30 02:35:17", updated_at: "2015-08-30 02:35:17">
2.1.1 :006 > Patient.where(shared_with: "{#{current_user.clinician.id}}").order("first_name asc")
Clinician Load (59.0ms) SELECT "clinicians".* FROM "clinicians" WHERE "clinicians"."user_id" = $1 ORDER BY "clinicians"."id" ASC LIMIT 1 [["user_id", 1]]
Patient Load (0.7ms) SELECT "patients".* FROM "patients" WHERE "patients"."shared_with" = '{1}' ORDER BY first_name asc
=> #<ActiveRecord::Relation [#<Patient id: 2, clinician_id: nil, first_name: "tom", last_name: "jerry", user_id: nil, created_at: "2015-08-30 19:12:59", updated_at: "2015-08-30 19:26:37", shared_with: ["1"]>]>

What does it mean when a instance method returns a scope and how to access them?

I'm using Closure_Tree gem and one of its instance methods, tag.descendants, returns a scope of all children, children's' children.
**tag.descendants** returns a scope of all children, childrens' children, etc., excluding self ordered by depth.
My questions are:
What is scope? Is it different from the name_scope?
It seems like the tag.descendants method is returning a hash. Please correct me if I'm wrong. And how can I access and return the name values?
This is what I received from rails console:
2.0.0-p353 :010 > #tag.descendants
Tag Load (0.5ms) SELECT "tags".* FROM "tags" INNER JOIN "tag_hierarchies" ON "tags"."id" = "tag_hierarchies"."descendant_id" WHERE "tag_hierarchies"."ancestor_id" = ? AND ("tags"."id" != 1) ORDER BY "tag_hierarchies".generations asc [["ancestor_id", 1]]
=> #<ActiveRecord::AssociationRelation [#<Tag id: 4, name: "Drinks", created_at: "2014-01-25 09:53:20", updated_at: "2014-01-25 09:53:20", parent_id: 1>, #<Tag id: 5, name: "Alcoholic", created_at: "2014-01-25 16:12:43", updated_at: "2014-01-25 16:12:43", parent_id: 4>, #<Tag id: 6, name: "Non-Alcoholic", created_at: "2014-01-25 16:14:13", updated_at: "2014-01-25 16:14:13", parent_id: 4>]>
2.0.0-p353 :011 >
I would like to know how I could call the name values of all the descendants. I've tried #tag.descendants.name but it returned "tag".
2.0.0-p353 :011 > #tag.descendants.name
=> "Tag"
Scopes are what allow you to take one big Active Record object and split it up into small different parts. For example you can scope projects so that user A can only see project A and user B can only see Project B, while all projects are on the project table. Check out the api it may clear things up about how you set a scope http://api.rubyonrails.org/classes/ActiveRecord/Scoping/Named/ClassMethods.html#method-i-scope
#tag.descendants.pluck(:name)
the reason #tag.descendants.name doesn't work is because you are calling the name method on a collection of decendants and it doesn't know which name to give you.

Thinking sphinx - exclude classes if field isn't present in index

I'm doing an application search that might filter on several different fields - some of which are not present in every index.
At the moment sorting out the correct classes to search on in the controller, using a complicated if elsif elsif thing.
Is there a way to get thinking sphinx to automatically not search a model if one a condition field isn't present, rather than ignoring the condition as seems to happen at the moment.
Eg
ThinkingSphinx.search(#query, :conditions => {:genres =>"classical-music"}, :match_mode => :extended, :classes => [Performer, Promoter, Tour, Venue, User], :order => :name_sort, :sort_mode => :asc)
User doesn't have genres, but is included in the search results
update
It definitely doesn't work as I'd like. My user model doesn't have the genres field in the index, so I'd like it to be excluded when I search on genres.
Example searches
User.search("anne")
Sphinx Query (3.6ms) anne
Sphinx Found 1 result
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IN (80)
=> [#<User id: 80, first_name: "Anne", last_name: "Bowers", bio: nil, email: "anne#bowers.com", phone: nil, created_at: "2012-11-20 09:36:05", updated_at: "2012-11-20 09:36:05", role: nil, promoter_id: nil, performer_id: nil, job_title: nil>]
ThinkingSphinx.search("anne", :conditions => {:genres => "music"}, :match_mode => :extended, :classes => [User], :order => :name_sort, :sort_mode => :asc)
Sphinx Query (3.2ms) anne #genres music
Sphinx Found 1 result
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IN (80)
=> [#<User id: 80, first_name: "Anne", last_name: "Bowers", bio: nil, email: "anne#bowers.com", phone: nil, created_at: "2012-11-20 09:36:05", updated_at: "2012-11-20 09:36:05", role: nil, promoter_id: nil, performer_id: nil, job_title: nil>]
It'd be really good if the user was excluded.
You can try to pass classes to search in params.
But your way should work too i think.
For me
ThinkingSphinx.search(conditions: { type: "text" })
it just writes an error
Sphinx Daemon returned warning: index bulletin_core,bulletin_delta,user_delta ...: query error: no field 'type' found in schema
but it doesnt crash. It returns only instances of classes which have indexed field type
You also can try a way which is written here https://groups.google.com/forum/?fromgroups=#!topic/thinking-sphinx/rrAPXtxUMjg but I haven't tryed it yet

When are Active Record objects in has_many relationships saved?

I'm using Rails 1.2.3 (yeah, I know) and am confused about how has_many works with respect to object persistence.
For the sake of example, I'll use this as my declaration:
class User < ActiveRecord::Base
has_many :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :user
end
As I understand it, this generates, among others, a method User#assignments.build, which creates an Assignment object whose user_id is the receiving instance's id (and whose other fields are as specified in the argument), but does not save this object in the database. The object can be saved later by calling Assignment#save!.
However, The Pragmatic Programmers' Agile Web Development with Rails, Second Edition, which I've been using as a tutorial and reference, says:
If the parent object exists in the database, then adding a child
object to a collection automatically saves that child.
There seems to be a contradiction here. What I'd like to know is:
If I do some_user.assignments.build, is the Assignment object saved?
If I do some_user.assignments << Assignment.new, is the Assignment object saved?
If I do some_user.assignments << Assignment.create, are two database calls made, or just one? What about if I modify the Assignment object between creating it and adding it to some_user.assignments?
What happens if I save! an Assignment object whose corresponding User has not yet been saved in the database?
P.S. The reason I don't just use User#assignments.create for everything is because it doesn't let me farm out initialization to an external method, which I'd like to be able to do. I also don't want to make multiple trips to the database.
NOTE: All the console tests below are run in Rails 3. You might get a different output in Rails 1, you'll have to run the tests yourself to compare.
Having your rails console handy is extremely valuable if you want to understand what's happening behind the scenes with Active Record. Here's what happens with a non saved object:
u = User.new
#<User id: nil, name: nil, created_at: nil, updated_at: nil>
u.assignments.build(:name => "example")
#<Assignment id: nil, name: "example", user_id: nil, created_at: nil, updated_at: nil>
u.save
#SQL (0.2ms) INSERT INTO `users` (`created_at`, `name`, `updated_at`) VALUES ('2012-06-01 19:25:45', NULL, '2012-06-01 19:25:45')
#SQL (0.2ms) INSERT INTO `assignments` (`created_at`, `name`, `updated_at`, `user_id`) VALUES ('2012-06-01 19:25:45', 'example', '2012-06-01 19:25:45', 1)
As you can see, both are saved at the same time when the new user was saved. Now let's try scenario two:
u = User.create!(:name => "test")
#SQL (0.2ms) INSERT INTO `users` (`created_at`, `name`, `updated_at`) VALUES ('2012-06-01 19:27:21', 'test', '2012-06-01 19:27:21')
#<User id: 2, name: "test", created_at: "2012-06-01 19:27:21", updated_at: "2012-06-01 19:27:21">
u.assignments.build(:name => "example")
#<Assignment id: nil, name: "example", user_id: 2, created_at: nil, updated_at: nil>
So, from this we can conclude:
If I do some_user.assignments.build, is the Assignment object saved?
Nope
If I do some_user.assignments << Assignment.new, is the Assignment object saved?
No. This is exactly what assignments.build does, no difference.
If I do some_user.assignments << Assignment.create, are two database calls made, or just one?
Just the assignments.
What about if I modify the Assignment object between creating it and adding it to some_user.assignments?
Don't understand, sorry.
What happens if I save! an Assignment object whose corresponding User has not yet been saved in the database?
It is saved to the database without a user_id. When you then call save on your user, an update command is issued to the assignment to add in the user id. Here it is in console:
u = User.new(:name => "John Doe")
#<User id: nil, name: "John Doe", created_at: nil, updated_at: nil>
a = Assignment.new(:name => "test")
#<Assignment id: nil, name: "test", user_id: nil, created_at: nil, updated_at: nil>
u.assignments << a
#[#<Assignment id: nil, name: "test", user_id: nil, created_at: nil, updated_at: nil>]
a.save!
#SQL (0.2ms) INSERT INTO `assignments` (`created_at`, `name`, `updated_at`, `user_id`) VALUES ('2012-06-01 19:33:24', 'test', '2012-06-01 19:33:24', NULL)
a.user_id
#nil
u.save!
#INSERT INTO `users` (`created_at`, `name`, `updated_at`) VALUES ('2012-06-01 19:33:36', 'John Doe', '2012-06-01 19:33:36')
#UPDATE `assignments` SET `user_id` = 3, `updated_at` = '2012-06-01 19:33:36' WHERE `assignments`.`id` = 3
Hope this helps.

Resources