ActiveRecord - find parents - ruby-on-rails-3

I want to find things that have some children. So given:
class Foo < ActiveRecord::Base
has_many :bars
has_many :bazes
scope :is_a_parent ...what goes here?...
I want to get Foos that have any bars or any bazes. Certainly all things are possible with raw SQL, exists (select 1 from bars ...) or exists (select 1 from bazes ...), but yuk.
Surely there's some way to use any? in conjunction with arel or method? Some other way to do it without resorting to SQL?

Does this help you? That prior answer is looking for the inverse of yours (Foos that have no bars nor bazes) but you should be able to invert the logic.

class Foo < ActiveRecord::Base
has_many :bars
has_many :bazes
scope :is_a_parent, (joins(:bars) or joins(:bazes)).uniq
would give you all foos having a bar or a baze
Btw, usually functions starting with "is_", should end with "?", and should return only true/false.

Related

Add Arbitrary Attribute to SQL Query from Joins Record without WHERE clause (Active record)

I'm trying to create an attribute in my select statement that depends on whether or not an association exists. I'm not sure if it's possible with a single query, and the goal is to not have to iterate a list afterward.
Here is the structure.
class Project < ApplicationRecord
has_many :subscriptions
has_many :users, through: :subscriptions
end
class User < ApplicationRecord
has_many :subscriptions
has_many :projects, through: :subscriptions
end
class Subscription < ApplicationRecord
belongs_to :project
belongs_to :user
end
Knowing a project, the goal of the query is to return ALL users and include on them a new attribute call subscribed - denoting whether or not they are subscribed.
non-working code (pseudo code):
project = Project.find_by(name: 'has_subscribers')
query = 'users.*, (subscriptions.project_id = ?) AS subscribed'
users = User.includes(:subscriptions).select(query, project.id)
user.first.subscribed
# => true or false
I'm open to whether or not there is a better way of going about this. However, the information is:
You know the project record.
You query a list of ALL users
Each user record has a subscribed attribute, denoting whether its
subscribed to the given project
Solution:
I was able to figure out a straight forward solution using the bool_or aggregate method. Coalesce ensures that the value returned is false instead of nil, should no subscriptions exists.
query = "users.*, COALESCE(bool_or(subscriptions.project_id = '#{project_id}'::uuid), false) as subscribed"
User.left_outer_joins(:subscriptions)
.select(query)
.group('users.id')
Yep, you can do this:
User.joins(:projects).select(Arel.star, Subscription.arel_table[:project_id])
Which will result in a SQL query like this:
SELECT *, "subscriptions"."project_id" FROM "users" INNER JOIN "subscriptions" ON "subscriptions"."user_ud" = "users"."id";
If you want to specify a specific project (i.e. use an expression), you can do it with Arel like this:
User.joins(:projects).select(Arel.star, Subscription.arel_table[:project_id].eq(42))
Unfortunately, you won't have a column name alias, and you can't call as on an Arel::Nodes::Equality instance. I don't know enough about the internals of Arel to have a way out of that box. But you can do this if you want the composability of Arel (e.g. if this is going to be something that needs to work with multiple models or columns):
User.joins(:projects).select(Arel.star, Subscription.arel_table[:project_id].eq(42).to_sql + " as has_project")
This is a bit clunky, but it works and provides a user.has_project method that returns a boolean. You can pretty it up like so:
class User
scope :with_project_status, lambda do |project_id|
has_project =
Subscription.arel_table[:project_id].
eq(project_id).to_sql + " as has_project"
joins(:projects).select(Arel.star, has_project)
end
end
User.with_project_status(42).where(active: true)

Rails ActiveRecord: find related model COUNT with a HABTM (has_and_belongs_to_many) relationship

I have two models
class Items < ApplicationRecord
has_and_belongs_to_many :users
end
class Users < ApplicationRecord
has_and_belongs_to_many :items
end
I want to find out all the items that have 2 users associated with them, and delete them.
I could think of doing it using an iterative approach like:
Item.all.each do |i|
if i.users.all.count == 2
i.delete
end
end
Is there a more elegant way (oneliner?) to do this using only the ActiveRecord ORM ?
Thanks.
Not sure but following should work
Item.joins(:users).group('items.id').having('COUNT(items_users.user_id) = 2').destroy_all
Use group and having for filter the condition.
Item.joins(:users)
.group('COUNT(users.id)')
.having('COUNT(users.id) = ?', 2)
.select('COUNT(users.id) AS users_count')
.destroy_all

Joining with named scopes in Ruby

I originally asked this question about acts_as_tree but it seems to be more general so I'm rephrasing it a bit here.
I have a model "category" using acts_as_tree. I often find myself needing to join it with other models for various queries, but I don't know how to use the provided scopes such as children, ancestors, siblings, descendants to create the conditions that I need.
Concrete example:
class Category < ActiveRecord::Base
acts_as_tree
has_many :photo
end
class Photo
belongs_to :category
end
cat = Category.first
How can I query for all the photos of children of cat or all the photos of its siblings?
Seems like I need something like:
Photo.joins(cat.children)
Photo.joins(cat.siblings)
which I know is not a valid code.
Since act_as_tree doesn't really use scopes but methods that return results, you can do it like:
class Photo
belongs_to :category
scope :from_category, -> (category) {
joins(:category).where(category: category)
}
end
cat = Category.first
Photo.from_category(cat.children)
Photo.from_category(cat.siblings)
And that should work.

Custom Association Method - Can This Be Done

I have three models: Wager, Race, RaceCard and WagerType. I've created a has_many association in Wagers and have added a custom association method (in_wager). The purposes of the method is to filter the correct races for each wager. (Some wagers span multiple races). I'd like to be able to do somethings like: Wager.first.races.in_wager and have the appropriate races returned.
class Wager < ActiveRecord::Base
belongs_to :wager_type
belongs_to :race_card
has_many :races, :through => :race_card do
def in_wager
where('races.race_nbr BETWEEN ? AND ?', a, b)
end
end
end
My custom method works fine if I hardcode the values for a and b, however, I need those values to be dynamic. Specifically, the value of b should equal the race_nbr attribute from the Wager model:
b = wagers.race_nbr
and the value of a should equal b minus the Number Of Race for the particular Wager type (know as Legs) plus 1:
a = b - Legs + 1
The value for legs is in the WagerType model. Note Wagers belong_to WagerType and WagerType has_many Wagers. Therefore, a could be expressed as:
a = (b - (select wager_types.legs where wagers_types.id = wagers.wager_type_id) + 1)
MY Question: Is it actually possible to do this with my in_wager association method. I've been banging my head on this for a couple of evening now and can't quite figure out how to assign the correct values to a and b. Also if you feel I'm coming at this the wrong way, I'd be happy to hear alternative approaches. Thanks for your help.
Note: I never really mentioned the RaceCard or Races models. They have the following associations:
class RaceCard < ActiveRecord::Base
has_many :races
end
class Races < ActiveRecord::Base
belongs_to :race_card
has_many :wagers, :through => :race_card
end
Update: I was reading Design Patterns in Ruby last night and came across the Proc. I'm going to see if I can use it within the Association method to calculate the values for a and b.
you can use self.proxy_association.owner to get the parent object inside of an association method. From there you can get the values you want.
If I understand your models correctly then the code should look something like this.
class Wager < ActiveRecord::Base
belongs_to :wager_type
belongs_to :race_card
has_many :races, :through => :race_card do
def in_wager
owner = self.proxy_association.owner
b = owner.race_nbr
a = b - owner.wager_type.legs + 1
where('races.race_nbr BETWEEN ? AND ?', a, b)
end
end
end
The I got this from the Rails api reference to Association Extensions (The reference to proxy_association is at the bottom of the section).
Do you absolutely need to use an has_many relation ? maybe you could just create a method in the Wager class
def races
b = self.race_nbr
a = b + self.race_card.legs
Races.find_by_race_card_id(self.id).where('race_nbr BETWEEN ? AND ?', a, b)
end
I don't really understand your model, so the request is certainly wrong, but you get the idea...

Querying for rows without matching ID in associated table

I have a very standard app backed by an SQL database with a User model, a Problem model, and a CompletedProblem model acting as a join table between the two.
I'm trying to create a method that returns all problems not solved by a particular user. I have run into a wall, however, and I would appreciate pointers on what my method should look like.
Below are the models as well as my latest (incorrect) pass at creating this method.
class User < ActiveRecord::Base
has_many :completed_problems
has_many :problems, :through => :completed_problems
def unsolved_problems
Problem.includes({:wall => :gym}, :completed_problems).
where('completed_problems.user_id != ? OR completed_problems.user_id IS NULL)', self.id)
end
end
class Problem < ActiveRecord::Base
has_many :completed_problems
has_many :users, :through => :completed_problems
end
class CompletedProblem < ActiveRecord::Base
belongs_to :user
belongs_to :problem
end
(For the curious: this method does work so long as there is only one user marking problems as solved. As soon as you add a second, each user starts to return only those problems that have been solved by other users, instead of those not solved by herself.)
Via a friend:
select * from problems where id not in (select problem_id from completed_problems where user_id = USER_ID))
Although I'd still be interested in hearing if there's a way in ActiveRecord to do this.
I think something like this will do it:
Problem.where(["id NOT IN (?)", self.problems.all.map(&:id)])