I have a page where I display a list of threads for a user whether the tread was started by them or if they were the recipient of one started.
Here is my model:
has_many :threads_as_starter, :class_name => 'MessageThread', :foreign_key => 'sender_id'
has_many :threads_as_recipient, :class_name => 'MessageThread', :foreign_key => 'recipient_id'
What I'd like to do is define a method that I can store in a in instance variable and loop through on my view page that displays threads of the current_user.
When I run: MessageThread.where( 'sender_id OR recipient_id = ?', 4 )
1.9.3p0 :045 > MessageThread.where( 'sender_id OR recipient_id = ?', 4 )
MessageThread Load (0.4ms) SELECT `message_threads`.* FROM `message_threads` WHERE (sender_id OR recipient_id = 4) ORDER BY message_threads.created_at DESC
=> [#<MessageThread id: 89, message_id: 219, sender_id: 4, recipient_id: 38, status: 0, created_at: "2012-02-15 12:26:17", updated_at: "2012-02-15 12:26:17">, #<MessageThread id: 88, message_id: 218, sender_id: 2, recipient_id: 4, status: 0, created_at: "2012-02-14 13:41:19", updated_at: "2012-02-14 13:41:19">, #<MessageThread id: 87, message_id: 210, sender_id: 1, recipient_id: 2, status: 0, created_at: "2012-02-14 13:31:12", updated_at: "2012-02-14 13:31:12">]
I'm confused as to why it's showing me rows where the sender or recipient id isn't equal to 4.
What query would return all results where the sender_id is 4 but also all the results where the recipient_id is 4? I need to give the signed in user to see a list of all their current threads. Ones that were started by them and ones that weren't.
User A and B only have 1 thread in the message_threads table but either of them can have more threads but with different users but only 1 with each of those users. I use this message_thread table to reference conversations in my messages table where I use the acts_as_tree gem.
There must be a way I can group threads_as_starter and threads_as_recipient e.g. all_threads then call current_user.all_threads to return all.
Thanks in advance
Kind regards
where('sender_id OR recipient_id = ?', 4)
should be
where('sender_id = ? OR recipient_id = ?', 4)
or probably
where('sender_id = ? OR recipient_id = ?', 4, 4)
Related
I have the following query:
I have the following model:
2.1.5 :025 > OnlineCourseRegistration.last
=> #<OnlineCourseRegistration id: 14392, cart_id: 15177, user_id: 7133, course_class_id: 681, created_at: "2017-10-29 23:28:45", updated_at: "2017-10-30 20:18:53", exam_attempts: 0, exam_completed_at: nil, evaluation_completed_at: nil, status: "Active", score: "", add_extension: false, retest_cart_id: nil, retest_purchased_at: nil>
...and I am running this query:
registrations = OnlineCourseRegistration.where(course_class_id: 123).where(status: "Completed").where("score >= ?", 80)
It is possible that more than one record for the same user_id can be returned. If this is the case I would like to only return the last record...or the record with the latest :exam_completed_at date for that user.
For context here is the entire loop:
registrations = OnlineCourseRegistration.where(course_class_id: 123).where(status: "Completed").where("score🏸 >= ?", 80)
if !registrations.empty?
registrations.each do |b|
email_recipients << b
end
I build up the email_recipients array, then hand that off to a mailer. The issue I am having is that if the user gets returned twice, then they are getting two emails. I only want them to get a single email, so I would like on the the last record returned (or the record with the most recent exam_completed_at date.
Here is what I came up with:
registrations = OnlineCourseRegistration.where(course_class_id: 842).where(status: "Completed").where("score🏸 >= ?", 80).order(exam_completed_at: :desc)
Note I added an order clause above, then...
registrations = registrations.uniq_by {|u| u.user_id}
I am unsure if uniq_by removes the first or last record, but this in combination with the additional order clause seems to do the trick.
So i'm trying to create a scope that takes two variables, (current_user, other_user) and seperate the messages between the two based off of sender_id and receiver_id (my foreign_keys).
I've used the following before on a project, and it worked well:
scope :between, -> (me, other) { Message.where{((:sender_id == my{me.id}) & (:receiver_id == my{other.id})) | ((:sender_id == my{other.id}) & (:receiver_id == my{me.id}))}}
However, on my current project, I use the same thing, and it's doing two things.
1: After binding.pry it provides this response.
[1] pry(#<ConversationsController>)> #messages
Message Load (0.4ms) SELECT "messages".* FROM "messages"
=> #<ActiveRecord::QueryMethods::WhereChain:0x00000106d83c98
#scope=
[#<Message id: 1, title: nil, body: "Message 1", sender_id: 11, receiver_id: 1, created_at: "2014-05-20 19:29:34", updated_at: "2014-05-20 19:29:34">,
#<Message id: 2, title: nil, body: "Message 2", sender_id: 1, receiver_id: 11, created_at: "2014-05-20 19:30:15", updated_at: "2014-05-20 19:30:15">,
#<Message id: 3, title: nil, body: "radda?", sender_id: 1, receiver_id: 109, created_at: "2014-05-20 19:30:39", updated_at: "2014-05-20 19:30:39">]>
As you can see above, it's combining all user id's, not just the sender and receiver. So all users receive all messages. And then, I keep getting this WhereChain Error.
#<ActiveRecord::QueryMethods::WhereChain:0x00000107f99ea0>
Please help me understand what I may be doing wrong, or what I could do right. Thank you.
Try:
scope :between, -> (me, other) { where(['(sender_id = :me and receiver_id = :other) or (sender_id = :other and receiver_id = :me)', {me: me.id, other: other.id}])}
You do not have squeel gem installed in your current project and the syntax you are using requires it. In Rails 3 you would receive an error wrong number of arguments 0 for 1, however in rails 4 you can call where without any params (so you can call not). The block you passed has been ignored and WhereChain object is returned.
Note that squeel is no longer maintained. Use with care.
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)
Suppose I've got a card-game app, which features a Player model, which has an actions integer column; and a Card model. A player can play a card they own, which costs an action; one particular card grants two actions when it's played.
If I code this as follows:
class Player < ActiveRecord::Base
has_many :cards
def play_card(card)
raise "Not yours!" unless cards.include? card
self.actions -= 1
card.play
save!
end
end
class Card < ActiveRecord::Base
belongs_to :player
def play
player.actions += 2
end
end
... then the net effect of Player#play_card is to decrement actions by 1. The only way I've found to make both changes apply to the same object, thereby resulting in a net increment of 1 action, is to define the functions like this:
class Player < ActiveRecord::Base
has_many :cards
def play_card(card)
raise "Not yours!" unless cards.include? card
self.actions -= 1
// Stick that change in the Database
save!
card.play
end
end
class Card < ActiveRecord::Base
belongs_to :player
def play
// Force reload of the player object
player(true).actions += 2
// And save again
player.save!
end
end
But that turns a single database write into two writes and a read! Surely there must be a better way. What am I missing?
In the first version of your code you are loading the same row of the table players but while you are expecting rails to be smart enough to recognize that it has already load this row in memory, rails doesn't work that way. So when you are issuing a +=2 on player it does he +=2 on another instance than the one on which you have done -=1.
i've setup a little example to show that there are too instance of the same row:
ruby-1.8.7-p174 > p_instance_1 = Player.first
=> #<Player id: 1, actions: -1, created_at: "2010-10-13 17:07:22", updated_at: "2010-10-13 17:11:00">
ruby-1.8.7-p174 > c = Card.first
=> #<Card id: 1, player_id: 1, created_at: "2010-10-13 17:07:28", updated_at: "2010-10-13 17:07:28">
ruby-1.8.7-p174 > p_instance_2 = c.player
=> #<Player id: 1, actions: -1, created_at: "2010-10-13 17:07:22", updated_at: "2010-10-13 17:11:00">
ruby-1.8.7-p174 > p_instance_1.object_id
=> 2158703080
ruby-1.8.7-p174 > p_instance_2.object_id
=> 2156926840
ruby-1.8.7-p174 > p_instance_1.actions += 1
=> 0
ruby-1.8.7-p174 > p_instance_2.actions += 1
=> 0
So finally as you haven't save the instance with the +=2 applied, there's only the one with the -1 that is saved
UPDATE
You can try to trick rails to use the same instance of player all the way. This is a little bit ugly but it works.
class Player < ActiveRecord::Base
has_many :cards
def play_card(card)
raise "Not yours!" unless cards.include? card
new_self = card.player
card.play
new_self.actions -= 1
new_self.save!
end
end
class Card < ActiveRecord::Base
belongs_to :player
def play
player.actions += 2
end
end
so when you input those commands:
ruby-1.8.7-p174 > p = Player.first
=> #<Player id: 1, actions: 0, created_at: "2010-10-14 13:33:51", updated_at: "2010-10-14 13:33:51">
ruby-1.8.7-p174 > p.play_card(Card.first)
=> true
ruby-1.8.7-p174 > p
=> #<Player id: 1, actions: 0, created_at: "2010-10-14 13:33:51", updated_at: "2010-10-14 13:33:51">
ruby-1.8.7-p174 > p.reload
=> #<Player id: 1, actions: 1, created_at: "2010-10-14 13:33:51", updated_at: "2010-10-14 13:34:40">
You have the right number of actions in player, and in the logs card is only loaded once:
Player Load (0.5ms) SELECT * FROM "players" LIMIT 1
Card Load (0.2ms) SELECT * FROM "cards" LIMIT 1
Card Load (0.2ms) SELECT "cards".id FROM "cards" WHERE ("cards"."id" = 1) AND ("cards".player_id = 1) LIMIT 1
Player Load (0.1ms) SELECT * FROM "players" WHERE ("players"."id" = 1)
Player Update (0.6ms) UPDATE "players" SET "updated_at" = '2010-10-14 13:34:40', "actions" = 1 WHERE "id" = 1
To sum up the whole thing, I would say that there's something wrong in your code design. If i understand well,what you would like is that every AR instance of a table row is the same object in the ObjectSpace, but I guess that if rails was build that way it would introduce strange behaviors where you could work on half backed object changed in validations and other hooks.