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"]>]>
Related
I am trying to create a validation in a model (I think it would be better to validate it at a database level actually) so an user can not purchase and item that has already bought in a interval of 2 days (after 2 days it's ok if he/she buys it again).
I have a model for the "purchase" action, and every purchase action has an item associated called "purchasable". Also every purchase action has a user_id associated.
I am trying it like this in the purchase model (but my problem is that I don't know how to correctly write this validation, or even how to retrieve "purchasable.id" or the "user.id" when writing the method for the validation)
class Purchase < ApplicationRecord
belongs_to :purchasable, polymorphic: true
belongs_to :user
validates :price, presence: true
validate :no_purchase_overlap
def no_purchase_overlap
allPur = Purchase.includes(:purchasable).all
if allPur.where("created_at >= ? AND user_id = ? AND purchase_id = ?", (Time.now-2.days), user_id, purchasable.id)
errors.add(:date_end, 'it overlaps another purchase')
end
end
end
A purchase and its purchasable look like:
irb(main):006:0> Purchase.first
Purchase Load (0.1ms) SELECT "purchases".* FROM "purchases" ORDER BY "purchases"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<Purchase id: 1, price: 0.299e1, quality: "SD", purchasable_type: "Season", purchasable_id: 9, user_id: 1, created_at: "2020-02-25 09:34:59", updated_at: "2020-02-25 09:34:59">
irb(main):007:0> Purchase.first.purchasable
Purchase Load (0.1ms) SELECT "purchases".* FROM "purchases" ORDER BY "purchases"."id" ASC LIMIT ? [["LIMIT", 1]]
Season Load (0.2ms) SELECT "seasons".* FROM "seasons" WHERE "seasons"."id" = ? LIMIT ? [["id", 9], ["LIMIT", 1]]
=> #<Season id: 9, title: "Brandy of the Damned", plot: "Here's looking at you, kid.", number: 1, created_at: "2020-02-25 09:34:17", updated_at: "2020-02-25 09:34:17">
Right now it's failing my tests, it seems like it is not allowing any purchase to be saved:
it 'a movie can not be purchased by the same user more than once' do
purchase = Purchase.new(price: 2.99, quality: 'HD', purchasable: movie, user: user)
purchase_rep = Purchase.new(created_at: Time.now + 1.day ,price: 1.99, quality: 'SD', purchasable: movie, user: user)
expect { purchase.save }.to change { user.purchases.count }.by(1)
expect { purchase_rep.save }.to raise_error
end
failure message:
Failures:
1) Purchase When creating a purchase a movie can not be purchased by the same user more than once
Failure/Error: expect { purchase.save }.to change { user.purchases.count }.by(1)
expected `user.purchases.count` to have changed by 1, but was changed by 0
# ./spec/models/purchase_spec.rb:14:in `block (3 levels) in <main>'
Solved with
def no_purchase_overlap
if Purchase.where("created_at >= ? AND user_id = ?", (Time.now-2.days), self.user.id).any?{|purchase| purchase.purchasable == self.purchasable}
errors.add(:date_end, 'it overlaps another purchase')
end
end
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))
I have an application where a DB table things contains locatable objects - like cities, addresses, party places, bus stops and so on.
One locatable object can e.g. be a City and Country. These objects are connected to Thing with a polymorphic has_one association since each City represents exactly one locatable Thing. But Country also has_many :cities since all countries are located in a specific country. I am doing all sorts of weird associative requests using this structure and so far (with Rails 3.0) it has worked well. But with Rails 4.2, it fails, see below.
Here is the DB structure:
Thing: id, locateable_id, locateable_type, lat, lng, name, country_id, city_id, ...
City: id, name, country_id
Country: id, name
Code:
class Thing < AR::Base
belongs_to :locateable, :polymorphic => true
belongs_to :country
belongs_to :city
...
end
class Country < AR::Base
has_one :thing, as: :locatable
has_many :cities
...
end
class City < AR::Base
has_one :thing, as: :locatable
...
end
In Rails 3.0.20, Ruby 1.9.3, calling this code
1.9.3-p551 :005 > Thing.find_by_name("Aachen").country.thing
# Thing Load (0.5ms) SELECT "things".* FROM "things" WHERE "things"."name" = 'Aachen' LIMIT 1
# Country Load (0.3ms) SELECT "countries".* FROM "countries" WHERE "countries"."id" = 276 ORDER BY name LIMIT 1
# Thing Load (0.3ms) SELECT "things".* FROM "things" WHERE ("things".locateable_id = 276 AND "things".locateable_type = 'Country') LIMIT 1
=> <Thing id: 355371, locateable_id: 276, locateable_type: "Country", name: "Deutschland", country_id: 276, ...>
would return the city's country. In Rails 4.2.5, Ruby 2.2.1, the exact same code returns the city's thing again:
2.2.1 :008 > Thing.find_by_name("Aachen").country.thing
# Thing Load (0.4ms) SELECT "things".* FROM "things" WHERE "things"."name" = $1 LIMIT 1 [["name", "Aachen"]]
# Country Load (0.3ms) SELECT "countries".* FROM "countries" WHERE "countries"."id" = $1 ORDER BY name LIMIT 1 [["id", 276]]
=> <Thing id: 105548, locateable_id: 3, locateable_type: "City", name: "Aachen", country_id: 276, city_id: 3, ...>
Why? This seems incorrect. Especially since it is inconsistent:
> Country.find(276).thing
=> #<Thing id: 355371, locateable_id: 276, locateable_type: "Country", coordinate_id: nil, name: "Deutschland", ...>
# but
> c = Thing.find_by_name("Aachen").country
=> <Country id: 276, name: "Deutschland">
> c.thing
=> #<Thing id: 105548, locateable_id: 3, locateable_type: "City", coordinate_id: nil, name: "Aachen", ...>
Is this maybe a bug in Rails? Or am I abusing the polymorphic association system?
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))
A has many Bs, B has many Cs. C has a property called thing:
class A < ActiveRecord::Base
has_many :bs
end
class B < ActiveRecord::Base
belongs_to :a
has_many :cs
end
class C < ActiveRecord::Base
belongs_to :b
attr_accessible :thing
end
I'd like to query for all Bs belonging to an A, and eagerly load Cs that belong to said B:
> a = A.first
A Load (0.2ms) SELECT "as".* FROM "as" LIMIT 1
=> #<A id: 1, created_at: "2012-08-21 09:25:18", updated_at: "2012-08-21 09:25:18">
> bs = a.bs.includes(:cs)
B Load (0.2ms) SELECT "bs".* FROM "bs" WHERE "bs"."a_id" = 1
C Load (0.1ms) SELECT "cs".* FROM "cs" WHERE "cs"."b_id" IN (1)
=> [#<B id: 1, a_id: 1, created_at: "2012-08-21 09:25:22", updated_at: "2012-08-21 09:25:22", thing: nil>]
>
This works well:
> bs[0]
=> #<B id: 1, a_id: 1, created_at: "2012-08-21 09:25:22", updated_at: "2012-08-21 09:25:22", thing: nil>
> bs[0].cs
=> [#<C id: 1, b_id: 1, thing: 2, created_at: "2012-08-21 09:29:31", updated_at: "2012-08-21 09:29:31">]
>
—but not in the case where I want to later perform where() searches on the Cs that belong to B instances:
> bs[0].cs.where(:thing => 1)
C Load (0.2ms) SELECT "cs".* FROM "cs" WHERE "cs"."b_id" = 1 AND "cs"."thing" = 1
=> []
> bs[0].cs.where(:thing => 2)
C Load (0.2ms) SELECT "cs".* FROM "cs" WHERE "cs"."b_id" = 1 AND "cs"."thing" = 2
=> [#<C id: 1, b_id: 1, thing: 2, created_at: "2012-08-21 09:29:31", updated_at: "2012-08-21 09:29:31">]
>
Note that queries are re-issued, despite our having the available information.
Of course, I can just use Enumerable#select:
> bs[0].cs.select {|c| c.thing == 2}
=> [#<C id: 1, b_id: 1, thing: 2, created_at: "2012-08-21 09:29:31", updated_at: "2012-08-21 09:29:31">]
>
This avoids a re-query, but I was sort of hoping Rails could do something similar itself.
The real downside is that I want to use this code where we don't know if the association has been eagerly loaded or not. If it hasn't, then the select method will load all C for B before doing the filter, whereas the where method would produce SQL to get a smaller set of data.
I'm not convinced this matters at all, but if there was something I'm missing about eager loading, I'd love to hear it.
I don't think you're missing anything. I don't believe active record can do anything that smart -- and it would be very difficult to do reliably I think. Like you say, it would have to determine whether you've eager-loaded the association, but it would also have to make a guess as to whether it would be faster to loop through the in-memory collection of Cs (if it's a small collection) or whether it would be faster to go to the database to get all the appropriate Cs in one shot (if it's a very large collection).
In your case, the best thing might be to just set the default scope to always preload the cs, and maybe even write your own fancy method to get them by thing. Something like this maybe:
class B < ActiveRecord::Base
belongs_to :a
has_many :cs
default_scope includes(:cs)
def cs_by_thing(thing)
cs.select{|c|c.thing == thing}
end
end
Then you could always know that you never go back to the DB when querying for your cs:
a = A.first
[db access]
a.bs.first
[db access]
a.bs.first.cs
a.bs.first.cs_by_thing(1)
a.bs.first.cs_by_thing(2)