Advice on RoR database schema and associations - ruby-on-rails

New to RoR, but am having a stab at building an app - and I'm looking for some advice on my db structure.
I've got 4 models/tables: Users > Clients > Jobs > Tasks
The app will work as follows:
Users will login
They can add Clients
They can add Jobs to Clients
They can add Tasks to Jobs
So, Tasks belong to Jobs, Jobs belong to Clients, and Clients belong to Users.
I want to query the DB for any one of a Client, a Job, or a Task and also make sure that it belongs to the currently logged-in User. I'm struggling to write a 'railsy' join query and design my associations so I can do this.
I know this would be super easy if I had a user_id field in every table, but that doesn't seem like the right way to do it.
I've read the guide at http://guides.rubyonrails.org/association_basics.html, but am still in the dark a little. Can anyone shed some light on how I might structure my DB - and more importantly my associations?
Thx.

It seems you have your associations set up right from one side, all you have to do is add the other end of the associations using has_many:
class Task < ActiveRecord::Base
belongs_to :job
end
class Job < ActiveRecord::Base
belongs_to :client
has_many :tasks
end
class Client < ActiveRecord::Base
belongs_to :user
has_many :jobs
has_many :tasks, :through => :jobs
end
class User < ActiveRecord::Base
has_many :clients
has_many :jobs, :through => :clients
has_many :tasks, :through => :jobs
end
Now ActiveRecord will take care of the joins for you. It's true that in a pure db schema, you should not have the user_id in more than one place(here that would the clients table). However sometimes it would be added also to the tasks and jobs table for a performance boost, because then the db queries would not be so big. Nevertheless you have to put more effort into making your data consistent - you have to ensure that a job has the same user_id as its client for example.
Then you would be able to define shortcut associations like:
class Task < ActiveRecord::Base
belongs_to :user
end
But in this case I would not do it unless you notice the queries are too slow for your needs. Premature optimization is evil :-)

I'm not sure, if I understood your question correctly, but I believe this can be a solution:
If you write your associations like this, ActiveRecord will create automatically the joins if you ask for user.jobs or user.tasks.
class User < ActiveRecord::Base
has_many :clients
has_many :jobs, :through => :clients
has_many :tasks, :through => :jobs
end
For more Information see the Rails-Api-Documentation
If you want to get everything for a user in one request. You can do this:
user.clients.joins(:jobs => :tasks)

Related

Ruby on Rails ActiveRecord task belongs to company & has_many users

I'm new to Ruby on Rails and was wonder if this is a good setup, or if there is a better configuration.
Background:
The system will be used to assign tasks to users, track the assignor, and allow multiple people to be assigned the task.
Create a company model, user model, task model, and a user_tasks model.
Company Class
has_many :users
has_many :tasks
User Class
belongs_to :company
has_many :user_tasks
has_many :tasks, through: :user_tasks
Task Class
belongs_to :company
has_many :user_tasks
has_many :users, through: :user_tasks
UserTasks Class
belongs_to :user
belongs_to :task
*Tracks assignor with boolean
I think this is perfect. There is one big advantage of having a has_many (model1), through: (model2) association when compared to has_and_belongs_to_many association in that you can access the join model (UserTasks your case) through the ActiveRecord query interface (UserTask.where() or UserTask.find_by(user_id: 1) and so forth). Querying the join table directly can sometimes shorten your queries. If you use the has_and_belongs_to_many association you will have a database table that you cannot directly access in Rails without resorting to SQL.
You probably don't need the UserTasks class if it is just a habtm table. So just create a migration for that table, but skip adding the model. Then in User, do has_and_belongs_to_many :tasks and in Task do has_and_belongs_to_many :users
The other thing that I see is that company is set for both tasks and users. You might have business rules to why this has to be, but if not, you might just be able to say Company -> has_many :tasks, through: :users
I favor :has_many, :through over has_many_and_belongs_to_many or HABTM. For example, I can be assigned to a Task twice, one as a programmer and another time as a designer. So I need some kind of "JobDescription" column on the UserTask column, and I'd need two rows of the UserTask table to represent me being a programmer on a task, and another to represent me being a designer on a task.
You want to write software that is easier to change, even though HABTM is shorter, you might change the software in the future, like in the case of a person doing two subtasks in the same task. Remember that a row in a database is a piece of data, or "a thing", if that makes sense. At the very least, you get timestamps.

Various belongs_to many associations

I've found good answers here, here, and here but I'm having trouble generalizing that to what I'm after.
I have multiple categories, that will be curated and selectable. So, users will be able to select cat1, cat2, and cat3, but not type a custom category.
A category can have many posts, a post can have many categories.
A post can have many comments.
A user can have many posts, and many comments.
For the post/category relationship, I'm thinking this will work, but the user/post/comment relationship is where I'm scratching my head...
# app/models/category.rb
class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
end
# app/models/post.rb
class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
belongs_to :user
has_many :comments
end
# app/models/user.rb
class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
# app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
Does this look close? Do I need any foreign keys anywhere to handle all this? Thanks in advance, I'm sure this is simple and I'm missing something obvious in my understanding.
And then I have to worry about how to write the tests for all this! That's for another day though...
EDIT: I should point out, I haven't started this yet. Just trying to map it out before I start, so it should simplify things, fewer migrations, etc.
EDIT AGAIN: Implemented suggested changes so far. Thanks!
why not start with the specs first? is a good practice on rails with all the power you have with rspec
Your Item should be called Post, why Item? is there any reason? if you want to call it "Item" you need to specify that on the associations
belongs_to :post, class_name: 'Item'
but you are better with Post instead of Item
A comment belongs to a user so the the user has_many :comments, you don't need the ", through: :posts" part
has_many :category_posts
has_many :posts, :through => :category_posts #or would has_and_belongs_to_many work better?
this depends on you, you need extra behavior on the CategoriesPosts? (Categories, in plural) if not, just use has_and_belongs_to_many
Really, i would suggest you start with the specs, you will end up with the implementations without thinking it too much and then you already have it tested and then you can add more specs and refactor it. Read something about TDD and BDD, it's hard at first but it's really good when you get it.
The only change I think I would make to this, other than actually naming Item Post, would be on your user model:
# app/models/user.rb
class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
You don't need a through association there. You could add other scoped comments to be something like comments_on_my_posts, through: :posts, class_name: "Comment", but for the above association on comments, it should be direct (commenter <=> comment).

Modeling Relationships with Four or More Models in Rails

I have these models in my Rails app:
group.rb
has_many :group_members
has_many :group_foo_types
group_member.rb
# fields
:group_id, :user_id
belongs_to:group
has_many :group_foo_types, :through=>:groups
has_one :user
has_many :foo, :through=>:user
has_many :bar, :through=>:user
group_foo_type.rb
# fields
:group_id, :foo_type_id
belongs_to :group
belongs_to :foo_type
user.rb
has_many :foo
has_many :bar
foo.rb
# fields
:user_id, :foo_type_id
belongs_to :user
belongs_to :foo_type
bar.rb
# fields
:user_id, :foo_type_id
belongs_to :user
belongs_to :foo_type
What I'm trying to do is, for a group, find the foos and bars per user taking into account the foo_type_id for the group (from group_foo_type).
However, I'm not sure if I have this modeled correctly.
From this, it seems like I can do group.group_members.user to get the users and then user.foos' and 'user.bars to get a user's foos and bars. This doesn't take into account the foo_type_id for the group though (from group_foo_type).
Can someone please help recommend an approach to accomplish what I want to do? Thank you!
As mentioned in this answer, since Rails 3.1 you can nest has_many :through associations. So to have easy access to all of a group's foos and bars, you could add the following associations:
# group.rb
has_many :users, through: :group_members
has_many :foos, through: :users
has_many :bars, through: :users
With this, group.foos would give you all of the group's foos (via group_member and user).
The association methods in rails can be used with the various finder methods, so to limit the foos by group_foo_type:
group_foos = group.foos.where(foo_type_id: group.group_foo_type_ids)
(group_foo_type_ids being another useful association method, check the Association Basics guide for more info.)
A caution: there are a lot of steps that have to go on in the background to achieve any of this stuff, even if it does get relatively easy to actually code. You may want to keep an eye on the queries that get generated (either through the console or logs) and how they perform, as they're likely to get fairly involved. It looks like you have a fairly complex set of associations there, so it may be worth looking at whether you can simplify anything there. Just something to keep in mind!

Newbie here: conflict associations to the same table. has_many:invoices, has_many :invoices, through: user_invoice_viewers

this seems pretty basic stuff here, but actually i'm finding it a bit harsh to define this scenario with Rails...
Perhaps any of you can provide some guidance?
So I have three Tables, Users, Invoices, and User_Invoice_Viewers (these basically map users that have viewer access to an invoice)
Now my models :
User.rb :
has_many :invoices
has_many :user_invoice_viewers
has_many :invoices, through :user_invoice_viewers
Invoice.rb
belongs_to user_invoice_viewers
belongs_to :user
User_Invoice_Viewers.rb
belongs_to :users
belongs_to :invoices
Now this just seems wrong... I repeat has_many :invoices on User model, so i expect conflict when executing : User.invoices ...
What would be the best solution for this? I had thought of putting it all on a user_invoice table, but since i expect to have more owners than viewers, for performance reasons, i decided to build a direct dependency between invoice and its owner...
Thanks
I would consider using the :class_name option on the association, so that the two relationships are named differently. Something like this:
class User < ActiveRecord::Base
has_many :invoices
has_many :user_invoice_viewers
has_many :viewable_invoices, through :user_invoice_viewers, :class_name => "Invoice"
...
end

Submitting job applications rails

Sorry for the fairly general question but I am looking to find out what would be the best way to implement a job application system in rails.
What I currently have is a user model and job model. What I would like to happen is that when a user submits an application for the job, most likely through a seperate application model, the user who posted the job will receive the application in their "applications area" and will also receive an email to the job owner's email address.
Is the best way to set this up to associate applications with users through jobs? Also would I need a seperate database table to handle the application or would it be possible to just set this up using Actionmailer?
Any help would be much appreciated! Thanks!
Sounds like a pretty straightforward has_many through association
class User < ActiveRecord::Base
has_many :applications
has_many :jobs, :through => :applications
end
class Application < ActiveRecord::Base
belongs_to :user
belongs_to :job
end
class Job < ActiveRecord::Base
has_many :applications
has_many :users, :through => :applications
end
Then in their application area you can just query user.jobs or user.applications depending on which you want to display.

Resources