Is there a way in rails to check to see if a parents collection is nil in the query? I want to get all the parents that don't have any children. Example:
parent_with_no_child = Parent.find(:all, :include => :childs, :conditions => {:childs => :childs.exist?})
Parent.all( :include => :children, :conditions => "children.parent_id IS NULL")
I prefer to use counter cache column as shown in this Railscasts episode and get the :children_count on Parent model as written by #PeterWong
Parent.find(Child.all.collect(&:user_id))
Looking forward to seeing better solution. (I remember there is a way to just return some specific columns instead of the full table from Child. But I do not remember the method......)
IMO since parent hasn't child means the parent's id do not exist in child's parent_id, a way to get all ids in child's parent_id would be a must.
BTW, you may consider adding a cache counter children_count to parents table, so creating or destroying a child would update it's parent's counter.
In this case, you may just do this: Parent.where(:children_count => 0)
However, you will have to make sure the cache counter is correct and consistent, or else the result would not be correct.
parent_with_no_child = Parent.find(:all,
:joins => :childs,
:group => 'childs.parent_id HAVING COUNT(child.parent_id) = 0')
Or something like that.
Related
I'm using "acts_as_tree" plugin for my user messaging thread feature on my website. I have a method that makes deleting selected messages possible. The messages don't actually get deleted. Their sender_status or recipient_status columns get set to 1 depending on what user is the sender or recipient of the message.
Anyway if both users have those status's set to one then that last line makes sure the message row is completely moved from the database. Now this is fine as long as it's not the parent message being deleted. If the parent message deleted then the children that haven't been selected for deletion won't be accessible anymore.
Here is the method:
def delete_all_users_selected_messages(message_ids, user_id, parent_id)
Message.where(:id => message_ids, :sender_id => user_id).update_all(:sender_status => 1)
Message.where(:id => message_ids, :recipient_id => user_id).update_all(:recipient_status => 1)
Message.delete_all(:sender_status => 1, :recipient_status => 1, :parent_id => parent_id).where("id != ?", parent_id)
end
It's quite obvious what I'm trying to do. I need to have the parent ignored. So where the primary key is equal to the parent_id means that row is a parent (normally the parent_id is nil but I needed it set to the primary keys value for some other reason, long story and not important). Anyway is there an SQL statement I can add on to the end of the last line in tat method? To make sure it only deletes messages where the id of the row is not equal to the parent_id?
I can arrange for the parent_id row to never be permitted for deletion unless the actual thread (MessageThreads table that references the messages tables conversations) is deleted.
Any way how can I make it so this parent row is ignored when that delete_all method is run?
Kind regards
Nowadays in Rails 4, you can do:
Model.where.not(attr: "something").delete_all
and
Model.where.not(attr: "something").destroy_all
And don't forget about difference:
destroy_all: The associated objects are destroyed alongside this object by calling their destroy method. Instantiates all the records and destroys them one at a time, so with a large dataset, this could be slow
delete_all: All associated objects are destroyed immediately without calling their destroy method. Callbacks are not called.
Why not use association from the parent record, something like this?
Message.where(:id => parent_id).first
.children.where(:sender_status => 1, :recipient_status => 1)
.delete_all
This worked for me in the end.
Message.where('id != ? AND parent_id = ?', parent_id, parent_id).where(:sender_status => 1, :recipient_status => 1).delete_all
Basically returns all messages of that particular conversation except the one where id == parent_id. Whenever id == parent_id this means it's a parent message.
I would consider a slightly different approach and have the model that has the has_many relationship have :dependent => destroy with it, e.g.
User has_many :messages, :dependent => :destroy
That way you don't get the 'dangling orphan record' issue you describe.
I would try and approach it this way rather than thinking 'all records except'.
I don't know if there is something I am not addressing but this is what comes to find for the issue described.
What I would like to find is all Events, where event.event_date is in the future, and only get the top 3 events sorted by how many Users the event has associated with it. Events and Users are joined by a HABTM relationship. Here's what I tried:
#popular_events = Event.where("event_date >= ?", Time.now)
.find(:all,
:joins => :users,
:group => 'event_id',
:order => "users.count DESC",
:limit => 10 )
I've tried a few other things with no luck. It is saying users.count is not a valid column.
believe it or not, this is a pain in the b*tt to do this with ActiveRecord. You will more or less have to rely on raw SQL to do it. Some answers here : http://www.tatvartha.com/2009/03/activerecord-group-by-count-and-joins/
I have a relationship table in a rails application called edit_privileges, in which the User is the "editor" and a number of other classes are "editable". Let's say that two of those classes are Message and Comment.
My EditPrivilege model uses the following code:
belongs_to :editor, :class_name => "User"
belongs_to :editable, :polymorphic => true
And User, of course
has_many :edit_privileges, :foreign_key => "editor_id"
In order to determine if a user has edit privileges for a certain model, I can't do the normal query:
user.edit_privileges.find_by_editable_id(#message.id)
because if the user has edit privileges to edit a comment with the same id as #message, the query will return true with the wrong edit privilege record from the table.
So, I tried doing these options:
user.edit_privileges.find(:all, :conditions => ["editable_id = ? AND editable_type ?", #message.id, #message.class.to_s])
user.edit_privileges.where(:editable_id => #message.id, :editable_type => #message.class.to_s)
which works great at finding the right record, but returns an array instead of an object (an empty array [] if there is no edit privilege). This is especially problematic if I'm trying to create a method to destroy edit privileges, since you can't pass .destroy on an array.
I figure appending .first to the two above solutions returns the first object and nil if the result of the query is an empty has, but is that really the best way to do it? Are there any problems with doing it this way? (like, instead of using dynamic attribute-based finders like find_by_editabe_id_and_editable_type)
Use find(:first, ...) instead of find(:all, ...) to get one record (note it might return nil while find will raise an RecordNotFound exception). So for your example:
user.edit_privileges.find(:first, :conditions => { :editable_id => #message.id, :editable_type => #message.class.to_s })
BTW, if you're on more edge rails version (3.x), Model.where(...).first is the new syntax:
user.edit_privileges.where(:editable_id => #message.id, :editable_type => #message.class.to_s).first
I think it's safe to say everyone loves doing something like this in Rails:
Product.find(:all, :conditions => {:featured => true})
This will return all products where the attribute "featured" (which is a database column) is true. But let's say I have a method on Product like this:
def display_ready?
(self.photos.length > 0) && (File.exist?(self.file.path))
end
...and I want to find all products where that method returns true. I can think of several messy ways of doing it, but I think it's also safe to say we love Rails because most things are not messy.
I'd say it's a pretty common problem for me... I'd have to imagine that a good answer will help many people. Any non-messy ideas?
The only reliable way to filter these is the somewhat ugly method of retrieving all records and running them through a select:
display_ready_products = Product.all.select(&:display_ready?)
This is inefficient to the extreme especially if you have a large number of products which are probably not going to qualify.
The better way to do this is to have a counter cache for your photos, plus a flag set when your file is uploaded:
class Product < ActiveRecord::Base
has_many :photos
end
class Photo < ActiveRecord::Base
belongs_to :product, :counter_cache => true
end
You'll need to add a column to the Product table:
add_column :products, :photos_count, :default => 0
This will give you a column with the number of photos. There's a way to pre-populate these counters with the correct numbers at the start instead of zero, but there's no need to get into that here.
Add a column to record your file flag:
add_column :products, :file_exists, :boolean, :null => false, :default => false
Now trigger this when saving:
class Product < ActiveRecord::Base
before_save :assign_file_exists_flag
protected
def assign_file_exists_flag
self.file_exists = File.exist?(self.file.path)
end
end
Since these two attributes are rendered into database columns, you can now query on them directly:
Product.find(:all, :conditions => 'file_exists=1 AND photos_count>0')
You can clean that up by writing two named scopes that will encapsulate that behavior.
You need to do a two level select:
1) Select all possible rows from the database. This happens in the db.
2) Within Ruby, select the valid rows from all of the rows. Eg
possible_products = Product.find(:all, :conditions => {:featured => true})
products = possible_products.select{|p| p.display_ready?}
Added:
Or:
products = Product.find(:all, :conditions => {:featured => true}).select {|p|
p.display_ready?}
The second select is the select method of the Array object. Select is a very handy method, along with detect. (Detect comes from Enumerable and is mixed in with Array.)
This follows this prior question, which was answered. I actually discovered I could remove a join from that query, so now the working query is
start_cards = DeckCard.find :all, :joins => [:card], :conditions => ["deck_cards.deck_id = ? and cards.start_card = ?", #game.deck.id, true]
This appears to work. However, when I try to move these DeckCards into another association, I get the ActiveRecord::ReadOnlyRecord error.
Here's the code
for player in #game.players
player.tableau = Tableau.new
start_card = start_cards.pop
start_card.draw_pile = false
player.tableau.deck_cards << start_card # the error occurs on this line
end
and the relevant Models (tableau are the players cards on the table)
class Player < ActiveRecord::Base
belongs_to :game
belongs_to :user
has_one :hand
has_one :tableau
end
class Tableau < ActiveRecord::Base
belongs_to :player
has_many :deck_cards
end
class DeckCard < ActiveRecord::Base
belongs_to :card
belongs_to :deck
end
I am doing a similar action just after this code, adding DeckCards to the players hand, and that code is working fine. I wondered if I needed belongs_to :tableau in the DeckCard Model, but it works fine for the adding to player's hand. I do have a tableau_id and hand_id columns in the DeckCard table.
I looked up ReadOnlyRecord in the rails api, and it doesn't say much beyond the description.
Rails 2.3.3 and lower
From the ActiveRecord CHANGELOG(v1.12.0, October 16th, 2005):
Introduce read-only records. If you call object.readonly! then it will
mark the object as read-only and raise
ReadOnlyRecord if you call
object.save. object.readonly? reports
whether the object is read-only.
Passing :readonly => true to any
finder method will mark returned
records as read-only. The :joins
option now implies :readonly, so if
you use this option, saving the same
record will now fail. Use find_by_sql
to work around.
Using find_by_sql is not really an alternative as it returns raw row/column data, not ActiveRecords. You have two options:
Force the instance variable #readonly to false in the record (hack)
Use :include => :card instead of :join => :card
Rails 2.3.4 and above
Most of the above no longer holds true, after September 10 2012:
using Record.find_by_sql is a viable option
:readonly => true is automatically inferred only if :joins was specified without an explicit :select nor an explicit (or finder-scope-inherited) :readonly option (see the implementation of set_readonly_option! in active_record/base.rb for Rails 2.3.4, or the implementation of to_a in active_record/relation.rb and of custom_join_sql in active_record/relation/query_methods.rb for Rails 3.0.0)
however, :readonly => true is always automatically inferred in has_and_belongs_to_many if the join table has more than the two foreign keys columns and :joins was specified without an explicit :select (i.e. user-supplied :readonly values are ignored -- see finding_with_ambiguous_select? in active_record/associations/has_and_belongs_to_many_association.rb.)
in conclusion, unless dealing with a special join table and has_and_belongs_to_many, then #aaronrustad's answer applies just fine in Rails 2.3.4 and 3.0.0.
do not use :includes if you want to achieve an INNER JOIN (:includes implies a LEFT OUTER JOIN, which is less selective and less efficient than INNER JOIN.)
Or in Rails 3 you can use the readonly method (replace "..." with your conditions):
( Deck.joins(:card) & Card.where('...') ).readonly(false)
This might have changed in recent release of Rails, but the appropriate way to solve this problem is to add :readonly => false to the find options.
select('*') seems to fix this in Rails 3.2:
> Contact.select('*').joins(:slugs).where('slugs.slug' => 'the-slug').first.readonly?
=> false
Just to verify, omitting select('*') does produce a readonly record:
> Contact.joins(:slugs).where('slugs.slug' => 'the-slug').first.readonly?
=> true
Can't say I understand the rationale but at least it's a quick and clean workaround.
Instead of find_by_sql, you can specify a :select on the finder and everything's happy again...
start_cards = DeckCard.find :all,
:select => 'deck_cards.*',
:joins => [:card],
:conditions => ["deck_cards.deck_id = ? and cards.start_card = ?", #game.deck.id, true]
To deactivate it...
module DeactivateImplicitReadonly
def custom_join_sql(*args)
result = super
#implicit_readonly = false
result
end
end
ActiveRecord::Relation.send :include, DeactivateImplicitReadonly