Rails associations with self joining class - ruby-on-rails

I doing a rails project where I'm using a self joining one-to-many setup.
I have a class called User, which has many customers.
Each customer has many order lists, like this:
class User < ActiveRecord::Base
has_many :customers, class_name: 'User', foreign_key: 'owner_id'
belongs_to :owner, class_name: 'User'
has_many :order_lists, dependent: :destroy
Now when I opened my rails console. I tried to do this:
user.customers.order_lists
I got this:
NoMethodError: undefined method `order_lists'
While when I try to do this:
user.customers.first.order_lists
I do receive the order lists of that customer.
But how could I recieve all the orderlists of all my customers?
Anybody an idea?

So you want all order_lists for all customers associated with a user?
This will do the trick:
user.customers.map(&:order_lists).flatten
Or you could make a scope on OrderList that takes a set of customer_ids like so:
scope :for_customer_ids, ->(customer_ids) {
where("user_id IN (?)", customer_ids)
}
And then
OrderList.for_customer_ids(user.customer_ids)

In a has many relationship when you call:
user.customers
it returns an array of all the customers the user "has", so when you are calling
user.customers.order_lists
order_lists is being called on an array of customers. (which doesn't have the method order_lists). The first method gives you the first customer in the array so it will work. You could also do something like:
user.customers[0].order_lists
if you wanted. (using first is better though)
Getting the order_lists of all customers would be to loop through all customers and get them like that.

Related

Fetch all rows that join via a list of values in Rails

I have two tables(models):
Students
Courses
They are both connected via a table called course_enrollments
In rails they are set up like so:
Student.rb:
has_many :courses, through: course_enrollments
Course.rb:
has_many :students, through: course_entrollments
CourseEntrollment.rb:
belongs_to :student, foreign_key...
belongs_to :course, foreign_key..
I would like to fetch all Students who are enrolled to all courses from a specific list of Courses, something like this:
courses_list = current_professor.courses_teaching
student_list = Student.eager_load(:some_table_I_have_to_load).join_with_all_courses(courses_list).order(sort_attributes).paginate....
My question is how do I go about doing it? I'm not too strong in SQL and am breaking my head trying to figure out what I need.
I figured I need to put some kind of join, or where clause before the order but I'm having some difficulty figuring out what and how to do it properly
Thanks a lot!
If courses_list is a relation. it's quite simple to do it:
student_list = Student.joins(:course_enrollments).where(course_enrollments: { course_id: courses_list.ids })

Has_and_belong_to_many relation with the same model

I am trying to implement a has_and_belong_to_many relation with the same model, but I don't know how.
For Example, a user should be able to follow other users.
Also, I have multiple fields of the same model in that model; how would I give it another name?
There are two scenarios and two different implementations:
The 'Friends' model
Let's say one User can have many :friends where each friend is also an object of the User model. You can do it this way:
has_and_belongs_to_many :friends, class_name: 'User'
This tells rails that an object of User class can have a many-to-many relation with itself as friends. So you can call something like this:
#user_a.friends
#=> [#user_x, #user_y, #user_z] # Array of User Objects
#user_x.friends
#=> [#user_a, #user_b, #user_c] # Array of User Objects
The 'Followers/Following' model
Let's say one User can follow other users as well have other users follow him. This is how you'll implement it:
has_many :followers, class_name: 'User', inverse_of: :following
belongs_to :following, class_name: 'User', inverse_of: :followers
This tells rails that each user can have many followers which is an Array of other User objects, and that user is accessible to others as an object in the following array. For example, if #user2 follows #user1, it would look like this:
#user1.followers
#=> [#user2, #user3]
#user2.following
#=> [#user1]

Rails 4 and referencing parent id in 'has_many' SQL

TL;DR: How do I use the ID of the respective parent object in a has_many SQL clause to find child objects?
Long version:
I have the following example code:
class Person < AR::Base
has_many :purchases, -> {
"SELECT * from purchases
WHERE purchase.seller_id = #{id}
OR purchase.buyer_id = #{id}"
}
This was migrated from Rails 3 which worked and looked like
has_many :purchases, :finder_sql => proc { #same SQL as above# }
I want to find all purchases associated with a Person object in one association, no matter whether the person was the one selling the object or buying it.
Update: I corrected the SQL, it was inside out. Sorry! Also: The association only needs to be read-only: I am never going to create records using this association, so using id twice should be OK. But I do want to be able to chain other scopes on it, e.g. #person.purchases.paid.last_5, so creating the sum of two associations in a method does not work (at least it didn't in Rails 3) since it doesn't return an AM::Relation but a simple Array.
When using this above definition in Rails 4.2, I get
> Person.first.purchases
undefined method `id' for #<Person::ActiveRecord_Relation:0x...>
The error is clear, but then how do I solve the problem?
Since this is only an example for much more complicated SQL code being used to express has_many relationships (e.g. multiple JOINS with subselects for performance), the question is:
How do I use the ID of the parent object in a has_many SQL clause?
I don't think your code will work at all. You are defining an association with two foreign keys ... that'd mean that in case you want to create a new Person from a present Purchase, what foreign key is to be used, seller_id or buyer_id? That just don't make sense.
In any case, the error you are getting is clear: you are calling a variable id which is not initialized in the block context of the SQL code.
A better approach to the problem I understand from your question would be to use associations in the following way, and then define a method that gives you all the persons, both buyers and sellers that a product has. Something like this:
class Purchase < ActiveRecord::Base
belongs_to :buyer, class_name: 'Person'
belongs_to :seller, class_name: 'Person'
def persons
ids = (buyer_ids + seller_ids).uniq
Person.where(ids: id)
end
end
class Person < ActiveRecord::Base
has_many :sold_purchases, class_name: 'Purchase', foreign_key: 'buyer_id'
has_many :buyed_purchases, class_name: 'Purchase', foreign_key: 'seller_id'
end
Im my approach, buyer_id and seller_id are purchase's attributes, not person's.
I may have not understood correctly, in that case please clarify.

Complex has_many :through with block conditions

I'm having a bit of difficulty figuring out how to do this in the "Rails" way, if it is even possible at all.
Background: I have a model Client, which has a has_many relationship called :users_and_managers, which is defined like so:
has_many :users_and_managers, -> do
Spree::User.joins(:roles).where( {spree_roles: {name: ["manager", "client"]}})
end, class_name: "Spree::User"
The model Users have a has_many relationship called credit_cards which is merely a simple has_many - belongs_to relationship (it is defined in the framework).
So in short, clients ---has many---> users ---has many---> credit_cards
The Goal: I would like to get all the credit cards created by users (as defined in the above relationship) that belong to this client.
The Problem: I thought I could achieve this using a has_many ... :through, which I defined like this:
has_many :credit_cards, through: :users_and_managers
Unfortunately, this generated an error in relation to the join with the roles table:
SQLite3::SQLException: no such column: spree_roles.name:
SELECT "spree_credit_cards".*
FROM "spree_credit_cards"
INNER JOIN "spree_users" ON "spree_credit_cards"."user_id" = "spree_users"."id"
WHERE "spree_users"."client_id" = 9 AND "spree_roles"."name" IN ('manager', 'client')
(Emphasis and formatting mine)
As you can see in the generated query, Rails seems to be ignoring the join(:roles) portion of the query I defined in the block of :users_and_managers, while still maintaining the where clause portion.
Current Solution: I can, of course, solve the problem by defining a plain 'ol method like so:
def credit_cards
Spree::CreditCard.where(user_id: self.users_and_managers.joins(:credit_cards))
end
But I feel there must be a more concise way of doing this, and I am rather confused about the source of the error message.
The Question: Does anyone know why the AR / Rails seems to be "selective" about which AR methods it will include in the query, and how can I get a collection of credit cards for all users and managers of this client using a has_many relationship, assuming it is possible at all?
The joins(:roles) is being ignored because that can't be appended to the ActiveRecord::Relation. You need to use direct AR methods in the block. Also, let's clean things up a bit:
class Spree::Role < ActiveRecord::Base
scope :clients_and_managers, -> { where(name: %w{client manager}) }
# a better scope name would be nice :)
end
class Client < ActiveRecord::Base
has_many :users,
class_name: "Spree::User",
foreign_key: :client_id
has_many :clients_and_managers_roles,
-> { merge(Spree::Role.clients_and_managers) },
through: :users,
source: :roles
has_many :clients_and_managers_credit_cards,
-> { joins(:clients_and_managers_roles) },
through: :users,
source: :credit_cards
end
With that setup, you should be able to do the following:
client = # find client according to your criteria
credit_card_ids = Client.
clients_and_managers_credit_cards.
where(clients: {id: client.id}).
pluck("DISTINCT spree_credit_cards.id")
credit_cards = Spree::CreditCard.where(id: credit_card_ids)
As you can see, that'll query the database twice. For querying it once, check out the following:
class Spree::CreditCard < ActiveRecord::Base
belongs_to :user # with Spree::User conditions, if necessary
end
credit_cards = Spree::CreditCard.
where(spree_users: {id: client.id}).
joins(user: :roles).
merge(Spree::Role.clients_and_managers)

Custom scope on has_many, :through association (Rails 4)

CONTEXT:
In my setup Users have many Communities through CommunityUser, and Communities have many Posts through CommunityPost. If follows then, that Users have many Posts through Communities.
User.rb
has_many :community_users
has_many :communities, through: :community_users
has_many :posts, through: :communities
Given the above User.rb, Calling "current_user.posts" returns posts with one or more communities in common with current_user.
QUESTION:
I'm looking to refine that association so that calling "current_user.posts" returns only those posts whose communities are a complete subset of the current_user's communities.
So, given a current_user with community_ids of [1,2,3], calling "current_user.posts" would yield only those posts whose "community_ids" array is either 1, [2], [3], [1,2], [1,3], [2,3], or [1,2,3].
I've been researching scopes here, but can't seem to pinpoint how to accomplish this successfully...
Nice question...
My immediate thoughts:
--
ActiveRecord Association Extension
These basically allow you to create a series of methods for associations, allowing you to determine specific criteria, like this:
#app/models/user.rb
has_many :communities, through: :community_users
has_many :posts, through: :communities do
def in_community
where("community_ids IN (?)", user.community_ids)
end
end
--
Conditional Association
You could use conditions in your association, like so:
#app/models/user.rb
has_many :posts, -> { where("id IN (?)", user.community_ids) }, through: :communities #-> I believe the model object (I.E user) is available in the association
--
Source
I originally thought this would be your best bet, but looking at it more deeply, I think it's only if you want to use a different association name
Specifies the source association name used by has_many :through
queries. Only use it if the name cannot be inferred from the
association. has_many :subscribers, through: :subscriptions will look
for either :subscribers or :subscriber on Subscription, unless a
:source is given.
Being honest, this is one of those questions which needs some thought
Firstly, how are you storing / calling the community_ids array? Is it stored in the db directly, or is it accessed through the ActiveRecord method Model.association_ids?
Looking forward to helping you further!
You don't show the model and relationship definitions for for Community or CommunityPost so make sure you have a has_many :community_posts and a has_many :posts, through: :community_posts on your Community model and a belongs_to :community and a belongs_to :post on CommunityPost. If you don't need to track anything on ComunityPost you could just use a has_and_belongs_to_many :posts on Community and a join table communities_posts containing just the foreign keys community_id and post_id.
Assuming you have the relationships setup as I describe you should be able to just use current_user.posts to get a relation that can be further chained and which returns all posts for all communities the user is associated with when you call .all on it. Any class methods (such as scope methods) defined your Post model will also be callable from that relation so that is where you should put your refinements unless those refinements pertain to the Community or CommunityPost in which case you would put them on the Community or CommunityPost models respectively. Its actually rare to need an AR relationship extension for scopes since usually you also want to be able to refine the model independently of whatever related model you may use to get to it.

Resources