rails - what is the best/most efficient way to associate these models - ruby-on-rails-3

In my application I have the following situation:
User belongs_to Group
Group has_many Users
Project belongs_to User
User has_many projects
The following is also true:
- Each Group will have one BaseCase
- Each Project will have multiple Scenarios and one BaseCase(depending on the Group the Project User belongs to)
- Scenario and BaseCase is the same type of object (let's call this Data)
- The default values for each Scenario are the BaseCase values for the Group, but the User can change these default values to create the specific Scenario
I am not sure how to capture all these relationships through associations efficiently, does anyone have any ideas? thanks

If I understand correctly, then something like that
class User
belongs_to :group
has_many :projects
end
class Group
has_many :users
has_many :projects, :through => :users
has_one :base_case
end
class Project
has_many :scenarios
has_one :base_case
belongs_to :user
has_one :group, :through => user
has_one :base_case, :through => :group
end
class Scenario
belongs_to :project
has_one :base_case, :through => :project
before_create do
self.attributes = self.base_case.attributes.except(:group_id, ...)
end
end
class BaseCase
belongs_to :group
end

Related

Best approach for assigning user roles for different groups in Rails 5?

Here are the basic models without roles:
group model
class Group < ApplicationRecord
has_many :memberships
has_many :users, through :memberships
end
user model
class User < ApplicationRecord
has_many :memberships
has_many :groups, through :memberships
end
memberships model
class Membership < ApplicationRecord
belongs_to :group
belongs_to :user
end
Each group should have 1 (and only one) owner, multiple admins (assigned by the owner), and the rest general members. Also, the ownership role needs to be transferable. My options, as I see them:
Create a single attribute role on the membership table and assign it a string value of "owner", "manager", or "general". [bad]
Create multiple booleans for "owner", "manager", and "general" on the membership table. [bad]
Create a Role model/table with 1 column ("name") and 3 rows ("owner", "manager", "general") then update my models like so:
Role model
class Role < ApplicationRecord
has_many :memberships
has_many :users, through :memberships
has_many :groups, through :memberships
end
group model
class Group < ApplicationRecord
has_many :memberships
has_many :users, through :memberships
has_many :roles, through :memberships
end
user model
class User < ApplicationRecord
has_many :memberships
has_many :groups, through :memberships
has_many :roles, through :memberships
end
memberships model
class Membership < ApplicationRecord
belongs_to :group
belongs_to :user
belongs_to :role
end
Create separate arrays for the various roles directly on the group model. This seems really stupid, as updating a role would require the pushing and splicing of multiple arrays, and I would have to concatenate multiple arrays to present a simple list of memberships.
group model
class Group < ApplicationRecord
has_many :general_memberships, class_name: 'Membership'
has_many :admin_memberships, class_name: 'Membership'
has_one :owner_membership, class_name: 'Membership'
has_many :users, through :memberships
end
user model
class User < ApplicationRecord
has_many :general_memberships, class_name: 'Membership'
has_many :admin_memberships, class_name: 'Membership'
has_many :owner_memberships, class_name: 'Membership'
has_many :groups, through :memberships
end
memberships model
class Membership < ApplicationRecord
belongs_to :group
belongs_to :user
end
I know there are gems out there like CanCanCan (Rails 5 compatible?) and Groupify, but I want to understand all my options first. I think option #3 is probably my best best, at least without without utilizing a gem. Wondering what the community considers best practice for my scenario.
UPDATE
I ended up going with the following solution and have been very satisfied with it in a production app. Since a group can only have one owner, it made since to use a belongs_to relationship for that role.
For the Membership model, I took advantage of active record enum. Since the enum array only holds two values, it actually would have been more efficient to simply add a "manager" boolean to the membership model. True means manager, false means general. But I went with the enum approach, because I anticipate needing additional roles in the near future, and using enum they will be VERY easy to add.
group model
class Group < ApplicationRecord
belongs_to :owner, class_name: 'User'
has_many :memberships
has_many :users, through :memberships
end
user model
class User < ApplicationRecord
has_many :owned_groups, class_name: 'Group', :foreign_key => 'owner_id'
has_many :memberships
has_many :groups, through :memberships
end
membership model
class Membership < ApplicationRecord
belongs_to :group
belongs_to :user
enum role: [:general, :manager]
end

Rails: NameError: uninitialized constant on join table

I can't seem to work around the name convention or if I'm joining them incorrectly.
This is the error I'm getting from the user model:
> user.companies
NameError: uninitialized constant User::CompaniesUser
And from the company model:
> company.users
NameError: uninitialized constant Company::CompaniesUser
user.rb
has_many :companies_users
has_many :companies, :through => :companies_users
company.rb
has_many :companies_users
has_many :users, :through => :companies_users
company_user.rb
class CompanyUser < ActiveRecord::Base
belongs_to :company
belongs_to :user
end
I've been looking up through examples but I honestly don't understand why it keeps exploding. If there's any additional information needed, I'll provide it, I appreciate any help given.
Your association companies_users will be mapped to class named CompaniesUser by Rails because "companies_users".classify will give you CompaniesUser. But the class you want to associate is CompanyUser, so the solution in this case would be to modify your associations to include class_name option as follows:
# user.rb
has_many :companies_users, class_name: CompanyUser
has_many :companies, :through => :companies_users
# company.rb
has_many :companies_users, class_name: CompanyUser
has_many :users, :through => :companies_users
Update: This is of course if you want to stick with the association name companies_users, otherwise #Babur has a solution for you.
It should be:
has_many :company_users
has_many :companies, :through => :company_users
Only last word should be pluralized
Because of your has_many :companies_users in your Company model, Rails tried to load a model class for that table, that be convention would be called CompaniesUser. To make your code work, you could either change your has_many declaration to
has_many :company_users
or even get rid of the CompanyUser model completely and use has_and_belongs_to_many instead.
class User
has_and_belongs_to_many :companies
end
class Company
has_and_belongs_to_many :users
end

Is a has_many :through relationship the right way to model this data?

I have a Client model that needs each client instance to own and create multiple addresses, email_ids, phone_numbers. A simple has_many relationship would not allow me to scale I presume so I thought I should go for a has_many :through relationship
I want to use a has_many :through relationship
class Client < ActiveRecord::Base
has_one :profile
has_many :addresses, :through => :profile
has_many :emails, :through => :profile
has_many :phonenumbers, :through => :profile
end
class Profile < ActiveRecord::Base
belongs_to :client
has_many :addresses
has_many :emailids
has_many :phonenumbers
end
class Address < ActiveRecord::Base
belongs_to :profile
end
class EmailId < ActiveRecord::Base
belongs_to :profile
end
class PhoneNumber < ActiveRecord::Base
belongs_to :profile
end
Will I then be able to execute queries like:
client.phonenumbers client.create_phonenumbers etc?
Or should I just stick to has_many belongs_to and put address, email_id and phone_number in the profile relationship and then say client has_many profiles? That doesn't sound right to me. Are there any benefits to the rich association that I have outlined above?
I think it seems to be fine stick with the has_many :through. Because we don't need to put an extra relationship between client and other tables i.e addresses etc and also an extra column client_id in those tables. only putting profile id will do the same.

Joining 3 tables in Ruby on Rails 3

I am trying to extract all Posts for a given User in the bellow relationships. Not sure whether I got them right, so I'll better explain. A User has Ownerships and Memberships in some Groups. A User can be either a Member or an Owner of the Group, but not both. Every Post has an id of the user and of the group. I think the problem is due to the relationships noted below. How can I get around it? One more thing. I have to also find all posts that were posted by other users in the user's groups. In other words, I have to pass through groups.
/-- Owner ---\
User -- -- Group -- Post
| \-- Member --/ |
|_______________________________|
class User < ActiveRecord::Base
has_many :posts, :dependent => :destroy
has_many :ownerships, :foreign_key => "user_id", :dependent => :destroy
has_many :memberships, :foreign_key => "user_id", :dependent => :destroy
# Problem with these two? I think so.
has_many :groups, :through => :memberships, :source => :user
has_many :groups, :through => :ownerships, :source => :user
class Ownership < ActiveRecord::Base
belongs_to :users, :class_name => "User"
belongs_to :groups, :class_name => "Group"
has_many :posts, :through => :groups, :source => :posts
class Membership < ActiveRecord::Base
belongs_to :users, :class_name => "User"
belongs_to :groups, :class_name => "Group"
has_many :posts, :through => :groups, :source => :posts
class Group < ActiveRecord::Base
has_many :posts, :dependent => :destroy
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :groups
The errors is coming from the line:
_groups = user.groups
The error as following:
Could not find the source association(s) :user in model Ownership. Try 'has_many :groups, :through => :ownerships, :source => '. Is it one of :users, :groups, or :postings?
First up: you're getting that error you're seeing because you've defined the associations in the Membership and Ownership table as this:
belongs_to :users
When they should belong to only one user, i.e. singular user:
belongs_to :user
But even then you will run into problems!
I think having a Membership model and an Ownership model are what will trip you up next. I don't understand what the purpose of having an Ownership model provides, other than signifying ownership of a group, which could be done by a field on the memberships table's records called owner for instance. It's over-engineering.
The problem with the Rails code you've got there is that you're defining that you have many posts through one association and then you're telling it that you have many posts through another association. In effect, you're doing this:
def posts
# find posts for the groups that I own
end
def posts
# find posts for the groups I belong to
end
It is not mistake here that there are two identically-named methods. This is exactly what you are doing by defining two has_many associations with the same name.
So hopefully now you can see why having an Ownership and a Membership model is the path to madness.
I would really recommend that you just have a Membership model that has a boolean attribute declaring an owner for a group. This would also mean that, if you wanted to, you could have new owners for a group in a very easy fashion: just flip the boolean. No need to create another record in another table.
One Membership model to rule them all.

Find all records NOT in a many-to-many association

In Rails 3 with ActiveRecord, I have 2 models (Users and Tasks). These models are linked together with a has_many :through association on another model, Assignments. How can I find all Tasks that are NOT associated to a particular user?
class User < ActiveRecord::Base
has_many :assignments
has_many :tasks, :through => :assignments
end
class Tasks < ActiveRecord::Base
has_many :assignments
has_many :users, :through => :assignments
end
class Assignments < ActiveRecord::Base
belongs_to :users
belongs_to :tasks
end
Short 'n sweet:
Task.all - user.tasks
Avoid loading user tasks:
Task.where('id not in (?)', user.task_ids)
I couldn't figure out how to do it with an outer join in AR.
I'm going to assume you want those tasks without any associated user, rather than not associated to a user in particular.
Tasks.joins('left outer join assignments on assignments.task_id = tasks.id').where('assignments.* is null')