Reputation: 6866
I'm working in Rails 3 and have a table with multiple child tables, e.g.
class Foo < ActiveRecord::Base
has_many :things
has_many :items
has_many :widgets
end
class Thing < ActiveRecord::Base
belongs_to :foo
end
class Item < ActiveRecord::Base
belongs_to :foo
end
class Widget < ActiveRecord::Base
belongs_to :foo
end
Is there a simple way for me to check to if a given Foo has a child record in one or more of the tables? Basically, is there a better way to do this:
if !foo.things.empty? or !foo.items.empty? or !foo.widgets.empty?
puts "This foo is in use!"
emd
Upvotes: 9
Views: 4740
Reputation: 2267
The answer by @Marcelo De Polli is the most generalized one posted so far. This answer is an updated version of it for Rails 5.
The parent class for a model is ApplicationRecord
in Rails 5 and later, which used to be ActiveRecord::Base
up to Rails 4 (n.b., the original question is tagged as Rails 3).
For simplicity of the code, use:
class Foo < ApplicationRecord
def children?
self.class.reflect_on_all_associations.map{ |a| self.send(a.name).any? }.any?
end
end
To pursue more run-time efficiency when a model may have many classes of children, use:
class Foo < ApplicationRecord
def children?
self.class.reflect_on_all_associations.each{ |a| return true if self.send(a.name).any? }
false
end
end
Upvotes: 0
Reputation: 14740
This is what any?
is for.
class Foo < ActiveRecord::Base
def children?
things.any? || items.any? || widgets.any?
end
end
Since this has become a topic of contention, I present to you:
> foo = Foo.last
Foo Load (0.6ms) SELECT "foos"......
> foo.items
Item Load (0.9ms) SELECT "items".*.......
> foo.items.any?
=> true #OH, WHAT's that? NO SQL CALLS? GEE WILLICKERS
> foo.items.exists?
Item Exists (0.5ms) #Hmmmmmm....
=> true
The point here is that under any circumstances, exists
makes a DB call, where as any?
will not, if spaces
is always loaded into memory. Now as I said, many times, the importance is not the efficiency of the DB call (AND YES, the SQL call exists?
makes is more efficient), but the fact that any?
won't necessarily make a call to the DB, which is a HUGE advantage. Look for yourself:
[20] pry(main)> Benchmark.measure { foo.item.exists? }
Item Exists (0.5ms) SELECT 1 AS one FROM "items" ...
=> #<Benchmark::Tms:0x007fc1f28a8638
@cstime=0.0,
@cutime=0.0,
@label="",
@real=0.002927,
@stime=0.0,
@total=0.00999999999999801,
@utime=0.00999999999999801>
[21] pry(main)> Benchmark.measure { foo.items.any? }
=> #<Benchmark::Tms:0x007fc1f29d1aa0
@cstime=0.0,
@cutime=0.0,
@label="",
@real=7.6e-05,
@stime=0.0,
@total=0.0,
@utime=0.0>
For a more concise timing, look at this:
> Benchmark.measure { 1000.times {foo.items.exists?} }.total
=> 2.5299999999999994
> Benchmark.measure { 1000.times {foo.items.any?} }.total
=> 0.0
Now as I said, many times, it depends on circumstance -- you could have many circumstances where these items aren't loaded into memory, but many times, they are. Choose which one works best for you depending on how you're calling it.
Upvotes: 6
Reputation: 29291
This should work for any given model.
class Foo < ActiveRecord::Base
def children?
has_associated_records = self.class.reflect_on_all_associations.map { |a| self.send(a.name).any? }
has_associated_records.include?(true)
end
end
Upvotes: 5
Reputation: 27463
Suppose all the associations are loaded into memory:
class Foo < ActiveRecord::Base
has_many :things
has_many :items
has_many :widgets
def in_use?
[things, items, widgets].flatten.any?
end
end
Edit
I just realized that this is wrong: each association (even if still loaded into memory) will be loaded which isn't good.
things.any? || items.any? || widgets.any?
is more correct and has been proposed before me.
Upvotes: 0
Reputation: 41874
Well, I think you're on the right track, but maybe just put that as a method in your Foo model
class Foo < ActiveRecord::Base
def children?
things.any? || items.any? || widgets.any?
end
end
Then just say, Foo.first.children?
and get true
if the Foo instance has any children.
Upvotes: 9
Reputation: 18070
You could subclass Thing Item and Widget. Or add a polymorphic join table to keep track of it. Not ideal, I know.
You could at least do this, so it would read a little better.
if foo.things.exists? || foo.items.exists? || foo.widgets.exists?
puts "This foo is in use!"
end
'empty?' uses 'exists?' behind the scenes, I believe.
Upvotes: 0