I'm trying to apply a rule to an ability class
Each user in my application (except admins, but we'll ignore that) belongs_to a practice, defined in another model.
The practice may want to be suspended from access for one reason or another.
I want to say in my ability model
if user.practice.suspended?
can :read, Client, :practice_id => user.practice_id
else
can :manage, CLient, :practice_id => user.practice_id
etc....
But for some reason, whilst I can use
user.practice_id
in the ability model
I can't use
user.practice.<attribute>
Any ideas for a way around that?
models/user.rb
belongs_to :practice
models/practice.rb
A practice has many users.
The only user who won't have a practice are those with role :admin
has_many :users
I think you have the relationships backwards.
It should be that "User has_many (or has_one) practice" and "Practice belongs_to user". This would signify that the foreign_key (in this case user_id) is actually in the practice table instead of the user table.
Try this:
User.rb
has_one :practice
Practice.rb
belongs_to :user
Create a migration to add user_id to practice
add_column :practices, :user_id, :integer
Then you can call practice from user like so:
user.practice.x
Related
Im trying to make my first side project using rails to learn would you kindly help me understand this?
The basic idea is to have a betting game where one user generates a new bet that can only be accepted by another user (only 2 competitors assigned for each bet, the creator and the other player).
I'm thinking about 2 tables:
users
bets
Normally I would just have a one to many relationship for the user that created the bet. But I'm confused about the 'competitor' column where another user is also a user with a user_id. How can I express this relationship better than this:
After thinking it through it doesn't look like a good setup because I'm renaming a column where I'm storing the user_id and having a many to many 'through' model doesn't make sense since it is a "only one competitor can participate in that bet".
I was thinking about a 'one to one through' creating a 'competitors' table like so:
Could you explain to me how to build it in a better way?
Many thanks!
just an idea, you can do this with 2 foreign_keys
so user can be as creator or competitors, you can also makesure that creator_id and competitor_id cannot be same value since user cannot bet with self
class Bet < ActiveRecord::Base
belongs_to :creator, foreign_key: "creator_id", class_name: "User"
belongs_to :competitor, foreign_key: "competitor_id", class_name: "User"
end
class User < ActiveRecord::Base
# as creator to create bet
has_many: creator_bets, foreign_key: :creator_id, class_name: "Bet"
# as competitor to create bet
has_many: competitor_bets, foreign_key: :competitor_id, class_name: "Bet"
end
#user = User.first
#user.creator_bets.build(...)
# this to create bet as creator
#user.competitor_bets.build(...)
# this to create bet as competitor
having a many to many 'through' model doesn't make sense since it is a
"only one competitor can participate in that bet".
Actually it does. Its is in many ways simpler than having muliple assocations pointing to the same table as you don't have to deal with the situation where a user could be in either column which requires something like:
# this gets much messier as you have to deal with more complex problems
Bet.where('bets.user_id = :id OR bets.competitor_id = :id', id: params[:user_id])
A many to many association also gives you the option of removing that limitation later with minimal redesign.
Given the following associations:
# This represents an event that users can bet on
# for example Elon Musk being the first man on mars.
class Event < ApplicationRecord
has_many :bets
has_many :users, through: :bets
end
class User < ApplicationRecord
has_many :bets
has_many :events, through: :bets
end
# This is the "join model" that joins User and Event
# columns:
# - user_id [bigint, fk]
# - event_id [bigint, fk]
# - amount [decimal]
class Bet < ApplicationRecord
belongs_to :user
belongs_to :event
end
You can simply create a bet by:
#event = Event.create!(description: "Elon Musk will be the first man on mars.")
#event.bets.new(user: User.first, amount: 500)
You can of course cap the number of users to two by adding a custom validation or in your controllers. If you only have two you can assume that event.users.first is the creator and event.users.last is the competitor.
I may be going about this the wrong way but after reading various SO articles and the Rails docs on associations and scopes, I'm not much wiser.
I have a many-to-may relationship expressed like so:
class User < ActiveRecord::Base
has_many :user_program_records
has_many :programs, through: :user_program_records
end
class Program < ActiveRecord::Base
has_many :user_program_records
has_many :users, through: :user_program_records
end
class UserProgramRecord < ActiveRecord::Base
belongs_to :user
belongs_to :program
# has a field "role"
end
The idea is that there are many users in the system and many programs. Programs have many users in them and users may belong to multiple programs. However - within a given program, a user can only have one role.
What I'd really like to be able to write is:
Program.first.users.first.role
and have that return me the role (which is just a String).
What's the cleanest way to do this? Basically, once I've scoped a user to a given program, how do I cleanly access fields on the relevant join table?
You are thinking about it slightly wrong:
user.role
Would be very ambiguous as a user can have different roles in different programs. Instead you need to think of the join entity as a thing of its own.
The easiest way is to select the join model directly:
program = Program.includes(:user_program_records, :users).first
role = program.user_program_records
.find_by(user: program.users.first)
.role
You can use stuff like association extensions and helper methods to make this a bit sexier.
So I have an application with users/user-profiles. Presently, users can view others profiles, send messages, and favorite user profiles. I'd like to also add the feature to allow users to 'hide-user' profiles so that they won't see the user ever again in search, or anything. And provide the option in 'settings' to 'un-hide' the user as well.
How might I do this? I haven't a clue as to where to begin with this feature.
(If you need any code examples, models, controllers please ask and I will happily provide)
Kind regards,
Sonny
There are probably a couple of ways to do this, the first approach that comes to mind would be to establish a self-referencing many to many relationship.
You will need to create the join table (I shall call it suppressed_users). I will show the rails model, as the migration wouldn't have anything other than the foreign keys.
class SuppressedUser < ActiveRecord::Base
belongs_to :user
belongs_to :suppressed_user, :class_name => "User", :foreign_key=>"suppressed_user_id"
end
And in the User model, in order to help DRY up your code, you can use a scope to easily filter all the users that this target user has decided to suppress (or hide):
class User < ActiveRecord::Base
has_many :suppressed_users // Optional
scope :without_hidden_users, -> (target_user) do
where.not("exists (?)",
SuppressedUser.select("1")
.where("users.id = suppressed_users.suppressed_user_id AND suppressed_users.user_id = ?", target_user))
end
end
Note about the scope: What I'm doing here is creating a dependent (or correlated) subquery, in which I check whether the target user has suppressed (or hidden) the user we're looking at at the moment. In other words, the dependent subquery is executed for each row in the users result set (those not filtered by other where or join conditions and such). With proper indexing, this should not have an impact on performance.
Note about has_many :suppressed_users: This is technically not needed for the query I've shown, so if it is not relevant for anything in your system, you should be safe to remove it.
So, if I am presently logged in, and I want to search for a list of users meeting some condition, in your controller you would do something like this:
User.without_hidden_users(#current_user.id)...other conditions and such as needed
Assuming #current_user represents the currently logged in user.
I believe one approach would be to create a many_to_many relationship via the has_many :through association between users and hidden users. And likewise another many_to_many relationship between users and hiders (i.e. users who are hiding the user). In the end, you should be able to do something like this:
some_user.hidden_users and some_user.hiders (i.e. all the users that are hiding some user). You probably won't need the second one most of the time.
So the first thing to do would be to create a join table called hiders_hidden_users (or whatever you want) which would contain only two fields: hider_id and hidden_user_id.
Note, that in reality, those ids will both refer to a User record. Your HidersHiddenUser model will look something like this:
class HidersHiddenUsers < ActiveRecord::Base
belongs_to :hidden_user, class_name: "User", foreign_key: "hidden_user_id"
belongs_to :hider, class_name: "User", foreign_key: "hider_id"
end
Then, you need to set up the relationships in the User class:
class User < ActiveRecord::Base
has_many :hiders_join, class_name: "HidersHiddenUser", foreign_key: "hider_id"
has_many :hidden_users_join, class_name: "HidersHiddenUser", foreign_key: "hidden_user_id"
has_many :hiders, through: :hiders_join
has_many :hidden_users, through: :hidden_users_join
end
Note, that you have to specify the class name and foreign key when writing the has_many relationship with the join table. Note, also that you have to specify the relationship twice with the same model.
Then, say some_user wants to hide user1 and user2. All you would need to do is something like this:
some_user.hidden_users << user1
some_user.hidden_users << user2
Although I have not tested this, I believe it should work.
I know how to create an admin role/user : https://github.com/plataformatec/devise/wiki/How-To:-Add-an-Admin-role
What I am wondering though is if there are any advantages or disadvantages to the two options to consider when deciding between them. Can anyone supply any insight on this please?
Let me muddle the water a bit. I prefer to this via a Role table and a join table UserRole. This way I can define more than one role without adding another column/table to db.
class User
has_many :user_roles
has_many :roles, :through => :user_roles
def role?(role)
role_names.include? role
end
def role_names
#role_names ||= self.roles.map(&:name)
end
def role=(role)
self.roles << Role.find_or_create_by_name(role)
end
end
class UserRole
# integer: user_id, integer: role_id
belongs_to :user
belongs_to :role
end
class Role
# string: name
has_many :user_roles
has_many :users, :through => :user_roles
end
It really depends on what you wish to do with your admin role. The first option, I would say is a bit secure as the admin role is a unique model in itself.
The second option is straightforward and would help you get going with the least effort. However, if your users figure out the boolean variable and a way to set it, any user can become an admin and access areas you don't want them to.
I have a rails application that has three different types of users and I need them all to share the same common profile information. However, each different user also has unique attributes themselves. I'm not sure how to separate out the different fields.
Admin (site wide admin)
Owner (of a store/etc)
Member (such as a member of a co-op)
I'm using devise for authentication and cancan for authorization. Therefore I have a User model with a set of roles that can be applied to the user. This class looks this:
class User < ActiveRecord::Base
# ... devise stuff omitted for brevity ...
# Roles association
has_many :assignments
has_many :roles, :through => :assignments
# For cancan: https://github.com/ryanb/cancan/wiki/Separate-Role-Model
def has_role?(role_sym)
roles.any? { |r| r.name.underscore.to_sym == role_sym }
end
end
Each user has a profile that includes:
First & Last Name
Address Info (city/st/zip/etc)
Phone
I do not want to pollute the User model with this info so I'm throwing it into a Profile model. This part is fairly simple. This turns the User model into something like this:
class User < ActiveRecord::Base
# ... devise stuff omitted for brevity ...
# ... cancan stuff omitted for brevity ...
has_one :profile
end
The additional fields is where I have some uneasy feelings about how to model are ...
If a user is an admin, they'll have unique fields such as:
admin_field_a:string
admin_field_b:string
etc
If a user is a Owner they'll have unique fields ...
stripe_api_key:string
stripe_test_api_key:string
stripe_account_number:string
has_one :store # AR Refence to another model that Admin and Member do not have.
If a user is a member they'll have a few additional fields as such:
stripe_account_number:string
belongs_to :store # the store that they are a member of
has_many :note
...
and a Store model will contain a has_many on the members so we work the the members of the store.
The issue is around the additional fields. Do I set these up as different classes? Put them into a different
I've currently tried a few different ways to set this up:
One way is to set up the User Model as aggregate root
class User < ActiveRecord::Base
# ...
# Roles association
has_many :assignments
has_many :roles, :through => :assignments
# Profile and other object types
has_one :profile
has_one :admin
has_one :owner
has_one :member
# ...
end
The benefit of this approach is the User model is the root and can access everything. The downfall is that if the user is a "owner" then the "admin" and "member" references will be nil (and the cartesian of the other possibilities - admin but not owner or member, etc).
The other option I was thinking of was to have each type of user inherit from the User model as such:
class User < ActiveRecord::Base
# ... other code removed for brevity
has_one :profile
end
class Admin < User
# admin fields
end
class Owner < User
# owner fields
end
class Member < User
# member fields
end
Problem with this is that I'm polluting the User object with all kinds of nil's in the table where one type doesn't need the values from another type/etc. Just seems kind of messy, but I'm not sure.
The other option was to create each account type as the root, but have the user as a child object as shown below.
class Admin
has_one :user
# admin fields go here.
end
class Owner
has_one :user
# owner fields go here.
end
class Member
has_one :user
# member fields go here.
end
The problem with the above is I'm not sure how to load up the proper class once the user logs in. I'll have their user_id and I'll be able to tell which role they are (because of the role association on the user model), but I'm not sure how to go from user UP to a root object. Methods? other?
Conclusion
I have a few different ways to do this, but I'm not sure what the correct "rails" approach is. What is the correct way to model this in rails AR? (MySQL backend). If there is not a "right" approach, whats the best of the above (I'm also open to other ideas).
Thanks!
My answer assumes that a given user can only be one type of user - e.g. ONLY an Admin or ONLY a Member. If so, this seems like a perfect job for ActiveRecord's Polymorphic association.
class User < ActiveRecord::Base
# ...
belongs_to :privilege, :polymorphic => true
end
This association gives User an accessor called 'privilege' (for lack of a better term and to avoid naming confusion which will become apparent later). Because it is polymorphic, it can return a variety of classes. The polymorphic relationship requires two columns on the corresponding table - one (accessor)_type and (accessor)_id. In my example, the User table would gain two fields: privilege_type and privilege_id which ActiveRecord combines to find the associated entry during lookups.
Your Admin, Owner and Member classes look like this:
class Admin
has_one :user, :as => :privilege
# admin fields go here.
end
class Owner
has_one :user, :as => :privilege
# owner fields go here.
end
class Member
has_one :user, :as => :privilege
# member fields go here.
end
Now you can do things like this:
u = User.new(:attribute1 => user_val1, ...)
u.privilege = Admin.new(:admin_att1 => :admin_val1, ...)
u.save!
# Saves a new user (#3, for example) and a new
# admin entry (#2 in my pretend world).
u.privilege_type # Returns 'Admin'
u.privilege_id # Returns 2
u.privilege # returns the Admin#2 instance.
# ActiveRecord's SQL behind the scenes:
# SELECT * FROM admin WHERE id=2
u.privilege.is_a? Admin # returns true
u.privilege.is_a? Member # returns false
Admin.find(2).user # returns User#3
# ActiveRecord's SQL behind the scenes:
# SELECT * FROM user WHERE privilege_type='Admin'
# AND privilege_id=2
I would recommend you make the (accessor)_type field on the database an ENUM if you expect it to be a known set of values. An ENUM, IMHO, is a better choice than a VARCHAR255 which Rails would normally default to, is easier/faster/smaller to index but makes changes down the road more difficult/timeconsuming when you've got millions of users. Also, index the association properly:
add_column :privilege_type, "ENUM('Admin','Owner','Member')", :null => false
add_column :privilege_id, :integer, :null => false
add_index :user, [:privilege_type, :privilege_id], :unique => true
add_index :user, :privilege_type
The first index allows ActiveRecord to rapidly find the reverse association (e.g. find the user that has a privilege of Admin#2) and the second index allows you to find all Admins or all Members.
This RailsCast is a bit dated but a good tutorial on polymorphic relationships nonetheless.
One last note - in your question, you indicated Admin, Owner or Member was the user's type which is appropriate enough, but as you probably see, I'd have to explain that your user table would then have a user_type_type field.
I'm probably not going to give you a rails approved suggestion, but ..
Separating your profile is a good call. Consider using the Decorator pattern for the a role. You can have an AdminUserDecorator, OwnerUserDecorator, or MemberOwnerDecorator. You could also dynamically add the additional fields directly on the instance (it is Ruby after all), but I think that would get ugly and complicated. (If you really want to do bad things, use a visitor object to give you an instance of your decorator from a method on the user class.)
Also, why put the stripe or payment config on the Owner instead of being part of the store information? Unless perhaps an owner can have multiple stores and use the same payment info for each store?
UPDATE: I should also suggest using TDD to flush out what works.
You've already accepted an answer, but for what it's worth, I have a similar situation and chose to go with your "User Model as aggregate root" approach. My User model contains all the "profile" info, and the User, to use a fictitious example, has_one :buyer and has_one :seller. I use a simple tinyint field as bitflags for which roles the user holds, since users could be both buyers and sellers (or TBD other roles I need in the future). If a bit is set, you can assume the corresponding association is not nil (which hasn't been an issue for me since I always check the bitflags before using the association reference). I don't actually have too many unique fields in my real subordinate models, but it's very useful to keep things decluttered when each subordinate model has additional associations, like if seller has_one :merchant_account" and a buyer has_one :purchase_history, etc. I haven't gone live yet, but when I do, I'll follow this post up with any issues I encounter.