Support multiple models with Sorcery or Authlogic gems? - ruby-on-rails

I have users and contacts.
Users sign into their account and can create address books from which they add contacts to.
If they share something with a contact that contact needs to login to view it.
Users are unique in the db (stored in users table) and contacts are stored in the contacts table and are only unique per a given address book.
I'm currently using the Sorcery gem for users, which is working great. However, how can I extend this to support authentication for contact to login?
I've read a bit into doing this via STI or polymorphic setup, but unclear on what the general pattern is for something like this.
Can I simply have both models use Sorcery? Or is that an anti-pattern?
Thanks in advance!

Why are you using a separate model for contacts? Why not just set up a self-join like:
has_many :contact_entries, :class_name => "ContactEntry"
has_many :contacts, :through => :contact_entries
Your user table would look the same, but you would have a join model like:
class ContactEntry < ActiveRecord::Base
belongs_to :user
belongs_to :contact, :foreign_key => "contact_id", :class_name => "User"
end
Which would have a user_id and contact_id field.
Update
Okay, I see your issue now. I don't think this will be possible with sorcery, at least not without making substantial edits to sorcery itself. Sorcery defines a single authenticatable model in the file initializer.rb.
Authlogic, however, can be brought into any model via "acts_as_authentic", so it is a plausible solution to your needs. The drawback is that authlogic doesn't seem to be actively developed. It had a fair amount of activity 10 days ago, however, so it's definitely worth looking in to.

Related

Rails Design Pattern for Multiple Devise Log-Ins Managing One Set of Data

EDIT: What's the design pattern for an app that does the following: A business sets up an account with the app. The business then creates "employees" in the app that can log in separately and CRUD the business's data, except for what their employer marks as off limits?
I've run into a problem with the hostel app. Here we go:
My app, a hostel management SaaS, has many users. These are hostels. The owner of the hostel signs up for my app via Devise. current_user is always the hostel itself.
Hostels have_many guests and each guest belongs_to a user(the hostel). These are people who call/email and want to spend a night in the hostel. Thanks a ton to everyone for helping me with the availability calendar.
All is fine and dandy now. Normally, the hostel owner or manager logs into my app, books rooms, emails guests, sends invoices, upload financials, you name it. However, many have been requesting the ability to add employees that can log in separately and create reservations, send emails, etc, but NOT view financial info. Enter CanCan.
Here's where I'm stuck. It's easy enough to delegate abilities and authorizations. Devise also gives me the ability to set up multiple devise models. However, I'm stuck with how I can give the employee, once they log in, access to their employer's data. The current_user.id is going to be different than their employer's ID(the business that signed up), so how can I tell Devise to use the ID of their user?
class User
has_many :employees
end
#should I do something like this?
class Employee
belongs_to :user
has_many :guests, :through => users
has_many :reservations, :through => users
has_many :rooms, :through => users
end
I thought about doing something like this below:
current_user.id = current_employee.user.id
The only problem is, it smells. There must be a better way. Once the employee logs in, everything is going to look the exact same as when their boss logs in(show all reservations, all guests, all emails, etc), the employee will just be restricted from certain areas.
The same current_user on multiple models in devise is mostly workarounds and hacks. https://leanpub.com/multi-tenancy-rails seemed kind of in the right direction, but it's a bit too much for what I need. There must be a specific design pattern for this situation, but I can't seem to find it or even get Googling in the right direction.
Basically, how do Rails apps give their users the ability to create sub-users, and let those sub-users view the user's data with restrictions?
Any help getting my thoughts straightened out is much appreciated.
Is there anything wrong with a simple override of the current_user method?
class ApplicationController
def current_user
super || current_employee&.user # safe operator for ruby 2.3+, otherwise `current_employee.try(:user)`
end
end

Rails STI design - user can be of 2 types

I need some advice on my user model. I came across the STI design and implemented it using devise. This is the basic setup:
class Message < ActiveRecord
has_one :sender
has_one :recipient
# Devise user
class User < ActiveRecord
class Sender < User
belongs_to :message
class Recipient < User
belongs_to :message
My conundrum is that the same user can be both sender and recipient in different scenarios. I originally set up this domain model such that a message record had a sender_id and recipient_id both of which were simply user_ids without any Railsy relationships defined or devise extras.
My previous solution seemed more flexible but the STI design seems more elegant and if possible I'd like to make it work. As I understand it the convention is that the type field discerns which user is returned. Is there a common solution for this using STI?
Broadly speaking, every time I've ended up implementing multiple types of user model, I've ended up regretting it. As you've already noticed, very quickly you end up with people who want to be both types of user, and suddenly you've got to manage different accounts, repeat logins, duplicate emails, etc.
I recommend instead setting up a single type of user that has_and_belongs_to_many roles. For simple cases, you can simply create your own role models and logic (which is what I've usually done), but it looks like this gem is pretty well supported as well: https://github.com/RolifyCommunity/rolify

Find what user edited what post in RoR

Right now I have a simple blog website setup with devise which allows users to edit posts. I also have activeadmin installed on the backend. What I want is when a user signs in and they edit a post I want that users email to be tied to that post. Then I could go into active admin and setup the column to view the user later. Trouble im having is that im not sure how to automatically tag a users email to a specific post when they edit it, also my user and post model are on different tables in the database.
Thanks for any help.
http://guides.rubyonrails.org/association_basics.html#the-has_and_belongs_to_many-association
Just set up a join table for a has_and_belongs_to_many association on each model. This is a standard active record association which should be well-documented; see the link above for a start.
Then in your update method for the PostsController you can add a line like:
#post.users << current_user
(obviously the specific code will vary depending on the names of your variables & associations -- i'd probably rename the association to "editors" or something like that)
I don't know anything about active admin, so I can't tell you how to make these associations viewable there. But it shouldn't be too hard once the association is set up properly.
Two approaches I use to create user_stamps.
paper_trail gem that records all modifications in Version table.
Works great with Active Admin.
Adding updated_by_id and created_by_id columns to all tables (paper_trail needed)
# In each Model.
belongs_to :updated_by, :class_name => "AdminUser", :foreign_key => "updated_by_id"
belongs_to :created_by, :class_name => "AdminUser", :foreign_key => "created_by_id"
after_create { |i| i.update_column(:created_by_id, PaperTrail.whodunnit) }
after_save { |i| i.update_column(:updated_by_id, PaperTrail.whodunnit) }
These columns will be redundant but a great compliment to Version table and is faster and better for many reports and scopes.

Forem gem: how to link a forum to other models

I have groups (Group model) in my app, which represent groups of people.
I want each group to have its own forum.
Should I just have the forum id in the groups table? It doesn't feel right. If I did it myself, the forum would have a polymorphic association to a "forumable" element (groups in this case, but I have other models that would need a forum).
Any opinions on what I should do? Modify the gem to fit my needs, or just have the forum_id in my models that need a forum? Or another solution maybe?
I'm the guy who started Forem (its the volunteers who did most of the hard work, though!), I think I can answer this question.
If you want only certain groups to have access to one and only one forum then you can put the forum_id field on the groups table and do it that way. What you can do then is override the can_read_forem_forum? method in your User model to perform a permission check for that user:
def can_read_forem_forum?(forum)
groups.where(:forum_id => forum.id).any?
end
This is used in Forem's ability model to determine whether or not a person can access a forum. What this method is going to do is that it will only return groups for that user that have link that specific forum. If there are any, then it's known that the user can access that forum.
Now if you're going the other route where a group may have access to many forums, well then you'd define a joins table between groups and forem_forums (called forum_groups) and define it as an association in your Group model like this:
has_many :forum_groups
has_many :forums, :through => :forum_groups, :class_name => "Forem::Forum"
You would need to also define a new model inside your application for this forum_groups association, it would be called ForumGroup and go a little like this:
class ForumGroup < ActiveRecord::Base
belongs_to :forum, :class_name => "Forem::Forum"
belongs_to :group
end
We're doing it this way so you have an easy way to manage the associations between forums and groups. If you did has_and_belongs_to_many, it generally only provides a gigantic pain in the ass when you want to delete one specific record from that join table.
Now, with that all nicely set up, the method you want to define in your User model is this one:
def can_read_forem_forum?(forum)
groups.joins(:forums).where("forem_forums.id = ?", forum.id).any?
end
Same thing, except this time we find all the groups that are linked to a specific forum through that association we set up earlier. This will do an INNER JOIN on the forum_groups table, and then another on the forem_forums table, getting the data required.
I hope this helps you, and thanks for using Forem!

Handling different user types in Rails

I'm designing an application that has two [three including administrators] user types: Buyers and Sellers. When a user signs up, it's assumed that they're signing up to simply purchase something [a Buyer account]. However, if they wish to become a Seller, there should be an option for them to post their items for sale, [become a Seller]. This becomes important, as users can switch back and forth between account types.
Of course, a Seller can buy items as well.
The problem
The problem I'm facing is that Sellers have profile pages where buyers can go to view their items for sale, whereas Buyers do not. However, both user types have a My Account page that they can use to update their information.
Possible design choices
Single table inheritence
class User < ActiveRecord::Base
has_one :profile
end
class Seller < User
has_many :sale_items
end
class Buyer < User
# nothing here.. I guess this is the "default" user type
end
I thought about this approach because then I could clearly separate the user types. For example, the show page for each user is clearly separated. However, this could lead to repeated code in each controller, and introduce a problem when switching between user types.
Just use declarative_authorization or CanCan to add functionality to the base user type
class User < ActiveRecord::Base
has_one :profile
has_many :sale_items # only for Sellers
# if the user is a seller, allow them to add sale_items
end
I thought of this approach because a Seller is basically a Buyer with additional functionality, such as posting items for sale. This could lead to a lot of view logic, though. For example if #user.role == "seller" render _some_seller_partial. I also don't like the idea of checking for a hard coded string in the view. Well, I guess I could do if #user.seller?
Other design choices?
I'm really interested in hearing how other people would model this application. I've been thinking about this for a couple days now.
I would use the second option, but with declarative_authorization instead of cancan, and I'd use the role_model Gem to store the role in the user model.
class User < ActiveRecord::Base
has_one :profile
has_many :sale_items # only for Sellers
# if the user is a seller, allow them to add sale_items
# t.integer :roles_mask , :default => 0 # for role_model
end
declarative_authorization is a bit more powerful than CanCan, and I found it to scale better once a project needs more / more complex roles..
If you store your roles using the role_model Gem, it stores them as a bitmap in the roles_mask attribute.
This means that a user can have many roles, e.g. can be a Seller and a Buyer at the same time, and an Admin or Moderator if you like.
See:
http://railscasts.com/episodes/188-declarative-authorization
http://railscasts.com/episodes/189-embedded-association
http://railscasts.com/episodes/192-authorization-with-cancan
http://railscasts.com/episodes/193-tableless-model
And:
http://everydayrails.com/2011/10/06/rails-authorization.html
http://www.tonyamoyal.com/2010/07/28/rails-authentication-with-devise-and-cancan-customizing-devise-controllers/
I would pick the cancan option. Is a wonderful gem, well documented, easy to use, actively mantained, and you have lots of tutorials (since it a gem created by Ryan Bates, from Railscasts, you can be sure that you have an episode for it)
I would go with the cancan option, and use the draper gem for the view logic. There are Railscasts for both gems.
http://railscasts.com/episodes/286-draper

Resources