I have some projects. Those projects have users through memberships.
However, those users belong to companies. Question is, how do I find out which companies can access a project?
Ideally I'd be able to do project.users.companies, but that won't work.
Is there a nice, pleasant way of doing this?
I'm assuming that you have this:
class Project < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :projects
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :users
end
And you want to get project.companies. The less painful one I can imagine is:
class Project < ActiveRecord::Base
has_and_belongs_to_many :users
def companies
Company.all(
:joins => {:users => :projects},
:conditions => {'projects_users.project_id' => self.id}
).uniq
end
end
Notice the uniq at the end. It will remove duplicated companies.
The other answers appear to be neglecting memberships that you mentioned. If those are actual objects which you have a recording of, then what you choose to do depends on the size of your tables. If they aren't terribly huge, then the "more OO" solution would probably look something like this:
class Project < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
def user_companies
self.users.map {|user| user.companies}.flatten.uniq
end
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
class User < ActiveRecord::Base
has_many :memberships
has_many :projects, :through => :memberships
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :users
end
This might not perform great, as it pulls a lot of data from the database and then does all the filtering in memory, but it's pretty intuitive to read. If you wanted to shove all the computing down into the database, I think a good solution would likely look something like this in the Project class instead:
def user_companies
Company.find_by_sql("SELECT company.*
FROM companies, users, memberships
WHERE companies.id = users.company_id
AND users.id = memberships.user_id
AND memberships.project_id = #{self.id}")
end
It's a little less clean but will put most of the processing nearest the data, and at only a three table join should not end up generating such a huge number of tuples that your DBMS falls apart at the seems.
I think you can do something like this.
class Project < ActiveRecord::Base
def self.companies
Company.all(:conditions => { :users => { :project_id => #project.id } }, :include => :users)
end
end
But it's been a while since I have used these features, so I may be rusty.
Edit: this may not work. Not sure if I got the :include or :join right. But like I said, I'm rusty.
You should be able to set the relationships up to allow: project.users.companies.
The associations are:
Project has_one User belongs_to Company
Related
Could someone tell me how to simplify the following function in my Invoice model? It should return all Invoices related to a particular Client (both connected via a Projects table).
def self.search_by_client_id(client_id)
if client_id
projects = Project.where(:client_id => client_id)
Invoice.where(:project_id => projects)
else
scoped
end
end
I really can't get my head around this. Thanks for any input!
This seems like the perfect opportunity for a scope!
scope :client, lambda{|id| includes(:projects).where('projects.client_id = ?', id)}
Invoice.client(4).all # returns all invoices for the client with the specified ID.
If I understand your question correctly, you should be able to do it using ActiveRecord associations:
class Client < ActiveRecord::Base
has_many :projects
has_many :invoices, :through => :project
end
class Project < ActiveRecord::Base
has_many :invoices
belongs_to :client
end
class Invoice < ActiveRecord::Base
belongs_to :project
has_one :client, :through => :project
end
Then, all Invoices related to a particular Client:
#client.invoices
To get a Client, associated to a particular invoice:
#invoice.client
I'm not quite sure what to even search for to address this specific question.
So here goes...
My current design:
User < AR
has_many :items
has_many :projects
Project < AR
has_many :groups
Group < AR
has_many :items
Given a #project and a #user how do I get Groups for a project (#project.groups) but have those Groups only contain items for the given #user?
This seems like something that should be taken care of in a model, rather than as controller logic, but I'm not sure what the best Rails way for doing this would be. I've investigated scope and custom finders, but it seems unnecessarily complicated. Perhaps it's a flaw in the relationship between the models.
Edit: Perhaps this helps?
User < AR
has_many :items
has_and_belongs_to_many :projects
Project < AR
has_many :groups
has_and_belongs_to_many :users
Group < AR
has_many :items
I think you're looking for has_many :through:
class User < ActiveRecord::Base
has_many :projects
has_many :groups, :through => :projects
has_many :items, :through => :groups
end
class Project < ActiveRecord::Base
has_many :groups
end
class Group < ActiveRecord::Base
has_many :items
end
Then you can just call:
#user.items
to get all of the items for a given user.
Note that nested has_many :throughs only work since Rails 3.1; otherwise, you'll probably have to write a method!
This has a little raw SQL, but should work:
def Project
def groups_for_user u
self.groups.select("DISTINCT groups.*").join(:items).where("items.user_id = ?,u.id)
end
end
The reason for the "DISTINCT groups.*" is to prevent repeats in the result because you're joining the lower level items table.
If a user has many things and a thing has many stats, it seems like there's only way Rails-y way to expose the stats through the user.
class User < ActiveRecord::Base
has_many :things do
def stats
Stat.where(thing_id: proxy_association.owner.things_id)
end
end
end
class Thing < ActiveRecord::Base
belongs_to :user
has_many :stats
end
class Stat < ActiveRecord::Base
belongs_to :thing
has_one :user, through: :thing
end
User.first.things.stats == Stat.where(thing_id: User.first.thing_ids)
I'm trying to determine whether there are any other options... Some people on my team complain that this doesn't feel natural. I feel like this is the most natural expression of the relationship you could devise.
Does anyone have a better suggestion? I'll say, I've tried instance methods and they don't smell right.
I might use a has_many :stats, :through => :things in the User class.
Check out this: http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
Okay, so here is my question. I have a 3 different models, People, Roles, Client, and Store. Clients have many Stores and can also have many people. Stores have many people. People have various roles. 1 Person can work at multiple stores, and they may have different roles at each store.
For example. Joe may be an assistant manager at one store and a manager at another store. What I would like to be able to do is pull the correct roles by doing something like Store.find(1).people.find(1).roles (would return 'assistant manager' for example) or
Store.find(2).people.find(1).roles (would return 'manager' for example). Is this possible to do in ActiveRecord?
I've created a table :roles_people which has the following definition:
create_table :roles_people, :id => false do |t|
t.references :role
t.references :person
t.references :store
t.references :client
end
However i can't figure out how to get associations to work properly using this table. Can anyone point me in the right direction?
Thanks
class People
belongs_to :client
has_many :store_roles
end
class Roles
has_many :store_roles
end
class StoreRole
belongs_to :role
belongs_to :people
belongs_to :store
end
class Client
has_many :stores
has_many :people
end
class Store
belongs_to :client
has_many :store_roles
has_many :roles, :through => :store_roles
end
Assume that all of those classes inherit from ActiveRecord::Base ;)
You're going to need to setup the migration and database structure to mirror these relationships. For each belongs_to there is an :object_id field on the table reference the appropriate table's id.
Your query is going to need to look something like:
Store.find(1).roles.find(:all, :conditions => ["store_roles.person_id = ?", 1])
I would probably add a method to the store model to make this a little easier:
def roles_for(person_id)
roles.find(:all, :conditions => ["store_roles.person_id = ?", person_id])
end
This way you can find the roles using:
Store.find(1).roles_for(1)
Or, better yet:
def self.roles_for(store_id, person_id)
Role.find(:all, :joins => :store_roles, :conditions => ["store_roles.store_id = ? AND store_roles.person_id = ?", store_id, person_id])
end
Which changes our finder to:
Store.roles_for(1, 1)
I would say that this last method is the most ideal since it causes only a single query, while each of the other options execute two queries to the database per role look-up (one to find the store, and one to get the roles for a person_id). Of course if you already have the Store object instantiated then it's not a big deal.
Hopefully this answer was sufficient :)
I think what you want is has_many :through
class Person < ActiveRecord::Base
has_many :roles_people
has_many :roles, :through => :roles_people
end
class Store < ActiveRecord::Base
has_many :roles_people
has_many :people, :through => roles_people
end
You'll also need to add relationships to RolePerson:
class RolePerson < ActiveRecord::Base
belongs_to :store
belongs_to :person
has_one :role
end
Is that what you were looking for?
Very helpful link #blog.hasmanythrough.com
has_and_belongs_to_many is your friend.
class Person < ActiveRecord::Base
has_and_belongs_to_many :roles
end
That way, you can get all roles the person has by calling Person.roles.all. The resulting query is going to use the people_roles table. You can also use has_many :through but have to build model classes for the join table yourself and maintain all the associations yourself. Sometimes it's necessary, sometimes it's not. Depends on the complexity of your actual model.
Nice question. You can't do exactly what you wanted, but i guess we can come close.
For completeness, i am going to recap your datastructure:
class Client
has_many :stores
end
class Store
has_many :people
has_many :roles
end
class Person
has_many :roles
has_many :stores
end
class Role
belongs_to :store
belongs_to :person
end
You see that the role does not need the link to the client, because that can be found straightaway from the store (i am assuming a stored is "owned" by only one client).
Now a role is linked both to a person and a store, so a person can have different roles per store.
And to find these in a clean way, i would use a helper function:
class Person
has_many :roles
has_many :stores
def roles_for(store)
roles.where("store_id=?", store.id)
end
end
So you can't write something like
store.people.first.roles
to get the roles of the first person working for that store.
But writing something like:
store.people.first.roles_for(store)
is not too hard i hope.
The reason why this is so is because in the context of the person (-> store.people.first) we no longer have any notion of the store (how we got there).
Hope this helps.
You need to change your table name in people_roles and you can drop both store and client references:
create_table :roles_people, :id => false do |t|
t.references :role
t.references :person
t.references :store
end
Role is something that belongs only to people.
You then need to use has_and_belongs_to_many:
class Person < ActiveRecord::Base
has_many :roles
has_many :stores, :through => :people_roles
end
class Store < ActiveRecord::Base
has_many :roles
has_many :people, :through => :people_roles
end
Than you can query:
Store.find(1).people.find(1).roles
I have a situation where I have Products, Suppliers, ShoppingLists and Valuations.
A shopping_list consist of many valuations each with a product, an specific supplier and a price.
My models are as follows:
class Product < ActiveRecord::Base
has_many :valuations
has_many :shopping_lists, :through => :valuations
end
class Supplier < ActiveRecord::Base
has_many :valuations
has_many :shopping_lists, :through => :valuations
end
class ShoppingList < ActiveRecord::Base
has_many :valuations
has_many :products, :through => :valuations
has_many :suppliers, :through => :valuations
end
class Valuation < ActiveRecord::Base
belongs_to :product
belongs_to :supplier
belongs_to :shopping_list
end
My routes.rb is:
map.resources :shopping_lists do |shopping_list|
shopping_list.resources :valuations
end
map.resources :product
map.resources :supplier
I wonder if this could be the best solution, anyway what I want is that the user can create as many lists as he wish, each with several valuations.
The first time a shopping list is created its also filled with one valuation at least. Then, the user can add/remove valuations to/from the shopping_list.
I would like a simple and elegant solution, without Ajax callbacks.
What is the best way to do this, from the controllers/views/routes perspectives?
Or should I completley change my schema ?
Thanks!
Just found two excelent resources from Ryan Bates:
http://asciicasts.com/episodes/196-nested-model-form-part-1
http://asciicasts.com/episodes/197-nested-model-form-part-2
Let's see if that do the job!
// UPDATE: Worked great!