How to join three tables in Rails? - sql

I have my records set up as follows
Subscription: id, plan_id <- belongs to plan
Plan: id, role_id <- belongs to role
Role: id, name
Given this data
Subscription: id: 1, plan_id: 5
Plan: id: 5, role_id: 10
Role: id: 10, name: 'Gold'
I'm trying to write a join so that I can find subscriptions based on their associated roles, i.e.:
Subscription.joins(:plan).joins(:role).where("roles.name = ?", 'Gold')
But this approach doesn't work. Any help would be appreciated.
Thanks.

If you have proper associations then use this:
Subscription.includes(:plan => [:role]).where("roles.name = 'Gold'").first
You can also write the query manually:
Subscription.joins("INNER JOIN plans ON plans.id = subscriptions.plan_id
INNER JOIN roles ON roles.id = plans.role_id").where("roles.name = 'Gold'").first

If you're trying to find all the subscriptions for a given role, why not start with the role? Rails can take care of these "one step removed" associations if we configure them correctly.
gold_role = Role.where(name: 'Gold').first
gold_role.subscriptions
This is possible via Rails' has_many :through relations. I think this is the correct setup for your models:
class Subscription < ActiveRecord::Base
belongs_to :plan
has_one :role, through: :plan
end
class Plan < ActiveRecord::Base
has_many :subscriptions
belongs_to :role
end
class Role < ActiveRecord::Base
has_many :plans
has_many :subscriptions, through: :plans
end

Related

SQL How to query a has_many_through relationship

I have three models that have a has_many_through relationship like this:
class Tag < ActiveRecord::Base
has_many :tag_workspaces
has_many :workspaces, through: :tag_workspaces, dependent: :destroy
end
class Workspace < ActiveRecord::Base
has_many :tag_workspaces
has_many :tags, through: :tag_workspaces, dependent: :destroy
end
class TagWorkspace < ActiveRecord::Base
belongs_to :tag
belongs_to :workspace
end
I need to write a query using sql language to get all the tags that belong to a specific workspace.
So far my query looks like this:
select '0' as "value", 'Seleziona un Tag' as "name"
union all
SELECT name AS "value",
name
FROM tags
But I need to select only the tags that belong to workspace 1, for example. How could I accomplish that? Thank you so much!
In SQL, this would be ...
select t.*
from tags t
join tag_workspaces tw on tw.tag_id = t.id
join workspaces w on w.id = tw.workspace_id
where w.id = 1

rails select distinct nested associations and fetch those associations

i have the models User, Company, Product, View
class Company < ActiveRecord::Base
has_many :users
end
class Product < ActiveRecord::Base
has_many :views_by_user, -> { where viewable_type: User },
as: :viewable, class_name: "View"
end
class User < ActiveRecord::Base
has_many :viewed, as: :viewer, class_name: "View"
belongs_to :company
end
class View < ActiveRecord::Base
belongs_to :viewable, polymorphic: true
belongs_to :viewer, polymorphic: true
end
What i did with the above is, when a user views product, i save the data in the views
Now i want the list of distinct companies that have looked at my product(via user) and total count for my serializer. what i have done is,
distinct_users = #product.views_by_user
.includes(viewer: [:company])
.joins("left outer join users on views.viewer_id = users.id")
.select("distinct users.company_id, views.*")
but with this, i would have to do something like
distinct_users.will_paginate(...).map(&:viewer).map(&:company)
is there a better way to do it? also if i use distinct_users.count it throws me an error
PG::UndefinedFunction: ERROR: function count(integer, views) does not exist
LINE 1: SELECT COUNT(distinct users.company_id,...
Start from Company if this is the type of record you actually want. You can use merge to combine the conditions on a relation with those from another. Try this:
Company.joins(:users => :viewed).merge(View.where(viewable: #product))
HTH

ActiveRecord has_many through polymorphic has_many

It seems like rails still not support this type of relation and throws ActiveRecord::HasManyThroughAssociationPolymorphicThroughError error.
What can I do to implement this kind of relation?
I have following associations:
Users 1..n Articles
Categories n..n Articles
Projects 1..n Articles
And here is Subscription model
Subscription 1..1 User
Subscription 1..1 Target (polymorphic (Article, Category or User))
And I need to select articles through Subscription#target#article according to user#subscriptions.
I have no idea hot to implement this
Ideally I want to get instance of Association class
UPDATE 1
Here is little example
Let say user_1 has 4 Subscription records:
s1 = (user_id: 1, target_id: 3, target_type: 'User')
s2 = (user_id: 1, target_id: 2, target_type: 'Category')
s3 = (user_id: 1, target_id: 3, target_type: 'Project')
s4 = (user_id: 1, target_id: 8, target_type: 'Project')
I need method User#feed_articles, that fetches all articles, that belong to any of target, I subscribed.
user_1.feed_articles.order(created_at: :desc).limit(10)
UPDATE 2
I separate articles sources by type in User model:
has_many :out_subscriptions, class_name: 'Subscription'
has_many :followes_users, through: :out_subscriptions, source: :target, source_type: 'User'
has_many :followes_categories, through: :out_subscriptions, source: :target, source_type: 'Category'
has_many :followes_projects, through: :out_subscriptions, source: :target, source_type: 'Project'
has_many :feed_user_articles, class_name: 'Article', through: :followes_users, source: :articles
has_many :feed_category_articles, class_name: 'Article', through: :followes_categories, source: :articles
has_many :feed_project_articles, class_name: 'Article', through: :followes_projects, source: :articles
But how can I merge feed_user_articles with feed_category_articles and feed_project_articles without loss of perfomance
UPDATE 3.1
The only way I found is to use raw SQL join query. Looks like it works fine, but I'm not sure.
def feed_articles
join_clause = <<JOIN
inner join users on articles.user_id = users.id
inner join articles_categories on articles_categories.article_id = articles.id
inner join categories on categories.id = articles_categories.category_id
inner join subscriptions on
(subscriptions.target_id = users.id and subscriptions.target_type = 'User') or
(subscriptions.target_id = categories.id and subscriptions.target_type = 'Category')
JOIN
Article.joins(join_clause).where('subscriptions.user_id' => id).distinct
end
(This is just for Users and Categories)
It supports scopes and other features. The only thing interests me: does this query lead to some undesirable effect?
I think that from DB performance prospective using UNION ALL multiquery will be more efficient than using polymorphic multijoin. Also it will be more readable. I tried to write an Arel query as example but it does not play nice (I failed to make order by clause work properly) so I think you have to put it via raw SQL. You can use SQL template apart from ORDER BY clause for drying it up.
You are correct that Rails doesn't support has_many :through w/ polymorphic associations. You could mimic this behavior by defining an instance method on your User class. That would look something like this:
def articles
Article.
joins("join subscriptions on subscriptions.target_id = articles.id and subscriptions.target_type = 'Article'").
joins("join users on users.id = subscriptions.user_id")
end

How to write this ActiveRecord Query using Join instead of subquery in Rails 4

Consider the following:
class User < ActiveRecord::Base
has_many :events
end
class Event < ActiveRecord::Base
belongs_to :user #this user is the event owner
has_many :members
end
class Members < ActiveRecord::Base
belongs_to :user
belongs_to :event
end
Now, I need to list all the members for which current_user is the owner. so I have come up with this:
#members = Member.where event_id: current_user.events
which produces the following query:
SELECT "members".* FROM "members" WHERE "members"."event_id" IN (SELECT "events"."id" FROM "events" WHERE "events"."user_id" = 1)
This works as expected but uses subqueries instead of JOIN. Does anyone know a better way to write this same query?
Add a has_many :through association to your User model:
class User < ActiveRecord::Base
has_many :events
has_many :members, :through => :events
end
Now you can query for all a user's members through the members association:
user.members
The SQL generated will look something like:
SELECT "members".* FROM "members" INNER JOIN "events" ON "members"."id" = "events"."member_id" WHERE "events"."user_id" = 1
Transformed to JOIN syntax (with table aliases to make it shorter and easier to read):
SELECT m.*
FROM events e
JOIN members m ON m.event_id = e.id
WHERE e.user_id = $1
I guess this will work.
Member.joins(:event).where("events.user_id = ?" , current_user.id)
You could do something like :
Member.joins(:event).where(events: {user_id: current_user.id})

Rails 3 has_one with join table

I have the following:
has_many :sports, :through => :user_sports
has_one :primary_sport, class_name: "UserSport", conditions: ["user_sports.primary = ?", true]
has_many :user_sports
When I run this in console:
athlete = Athlete.all.last
athlete.primary_sport
The record that is returned is the record from the join table instead of the record joining the sports table. Any way to return the actual sport from the join?
You might probably do something like this:
class UserSport < ActiveRecord::Base
has_many :athletes
has_many :sports
end
athlete = Athlete.all.last
athlete.primary_sport.sport
Didn't try it by myself, just check and see :)