Please help me simplify this simple Ruby on Rails function - ruby-on-rails

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

Related

Rails table join with extra details doesn't work

I have these 2 models, and I'm trying to add an association between them so I can add some extra details later:
class Ticket < ActiveRecord::Base
belongs_to :user
has_many :event_tickets
has_many :events, :through => :event_tickets
end
class Event < ActiveRecord::Base
has_many :event_tickets
has_many :tickets, :through => :event_tickets
end
class EventTicket < ActiveRecord::Base
belongs_to :event
belongs_to :ticket
end
I've tried adding an association like this, just to see it's working:
event_ticket = EventTicket.new(:ticket => ticket, :event => Event.all[0])
event_ticket.save!
This gives me:
can't write unknown attribute `ticket_id`
The ticket and event exist and are working fine, but can't get the event_ticket object working.
What am I doing wrong?
It sounds like you don't have an event_tickets table, or at least it doesn't have a ticket_id column. Are you sure you wrote the migration and ran it?

Rails has_many :through and has_one :through associations

First I'm using Rails 3.1 from the 3-1-stable branch updated an hour ago.
I'm developing an application where I have 3 essential models User, Company and Job, Here's the relevant part of the models:
class User < ActiveRecord::Base
has_many :companies_users, class_name: "CompaniesUsers"
has_many :companies, :through => :companies_users, :source => :company
end
class Company < ActiveRecord::Base
has_many :companies_users, class_name: "CompaniesUsers"
has_many :employees, :through => :companies_users, :source => :user
has_many :jobs, :dependent => :destroy
end
class Job < ActiveRecord::Base
belongs_to :company, :counter_cache => true
end
class CompaniesUsers < ActiveRecord::Base
belongs_to :company
belongs_to :user
end
The code works just fine, but I have been wondering if it's possible to:
I want to link a job with an employer, so think of this scenario: A user John who's an employee at Example, he posted the job Rails Developer, so I want to access #job.employer and it should get me back the user John, in other words:
#user = User.find_by_name('john')
#job = Job.find(1)
#job.employer == #user #=> true
So I thought of two possible solutions
First solution
class Job
has_one :employer, :through => :employers
end
class User
has_many :jobs, :through => :employers
end
class Employer
belongs_to :job
belongs_to :user
end
Second solution
class Job
has_one :employer, :class_name => "User"
end
class User
belongs_to :job
end
Which route should I go? Is my code right ?
I have another question, how to get rid of the class_name => "CompaniesUsers" option passed to has_many, should the class be Singular or Plural ? Should I rename it to something like Employees ?
P.S: I posted the same question to Ruby on Rails: Talk
Unless I'm missing something, I'd suggest simply doing
class Job
belongs_to :employer, :class_name => "User"
end
class User
has_many :jobs
end
This would give you methods like
user = User.first
user.jobs.create(params)
user.jobs # array
job = user.jobs.first
job.employer == user # true
You'll need an employer_id integer field in your Jobs table for this to work.
Typically you want to name your pass through model:
company_user
Then you don't need this:
class_name: "CompaniesUsers"
Just make sure the name of your database table is:
company_users
What you have works for you, so that's great. I just find when I don't follow convention I
run in to trouble down the road.

advanced (nexted) has_many :through queries in Ruby on Rails (double-JOIN)

I am having a somewhat too nested database layout, however, I seem to need it. That is, Our website visitors may each have a single account with maintaining multiple users (think of identities) within.
Now they may create tickets, which are grouped by ticket sections, and we have ticket manager (operator) to process the incoming tickets.
Not every ticket manager may see every ticket but only those this manager is a member of the given ticket section for.
Now, I am totally fine in querying via raw SQL statements, but I failed to verbalizing those two special queries the Rails way.
Here is the (abstract) model:
# account system
class Account < ActiveRecord::Base
has_many :users
has_many :tickets, :through => :users
has_many :managing_ticket_sections, ... # TicketSection-collection this account (any of its users) is operate for
has_many :managing_tickets, ... # Ticket-collection this account (any of its users) may operate on
end
class User < ActiveRecord::Base
belongs_to :account
has_many :tickets
has_many :managing_ticket_sections, ... # TicketSection-collection this user is operate for
has_many :managing_tickets, ... # Ticket-collection this user may operate on
end
# ticket system
class Ticket < ActiveRecord::Base
belongs_to :author, :class_name => "User"
belongs_to :assignee, :class_name => "User"
belongs_to :section, :class_name => "TicketSection"
end
class TicketSection < ActiveRecord::Base
has_many :tickets
has_many :operators
end
class TicketSectionManager < ActiveRecord::Base
belongs_to :manager, :class_name => "User"
belongs_to :section
end
I am aware of basic has_many :through-constructs, however, here, I am accessing more than three tables to get the tickets.
Something that actually works for in the User's model is:
class User < ActiveRecord::Base
has_many :managing_relations, :class_name => "TicketSectionManager" # XXX some kind of helper, for the two below
has_many :managing_sections, :class_name => "TicketSection", :through => :managing_relations, :source => :section
has_many :managing_tickets, :class_name => "Ticket", :through => :managing_relations, :source => :section
end
Here I am using a helper relation (managing_relations), which is absolutely never used except by the two has_many relations below.
I were not able to describe a User.managing_sections nor User.managing_tickets relation without this helper, which is, where I need an advice for.
Secondly, the customer is to have a look at all of the tickets he can manage on any User (think of an identity) he has logged in, so what I need, is a way to collect all tickets (/sections) this Account is permitted to manage (identified by being a member of the corresponding TicketSection)
Here I even were not able to express this relation the ruby way, and I had to work around it by the following:
class Account
def managing_tickets
Ticket.find_by_sql("SELECT t.* FROM tickets AS t
INNER JOIN ticket_section_managers AS m ON m.section_id = t.section_id
INNER JOIN users AS u ON u.id = m.user_id
WHERE u.account_id = #{id}")
end
end
I'd appreciate any kind of advice, and
many thanks in advance,
Christian Parpart.

Rails ActiveRecord 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

Deep relationships in Rails

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

Resources