Rails: complicated design choice: polymorphism? STI? - ruby-on-rails

I have been trying different techniques while designing this application, which to me is very complicated as there are many solutions to chose from. Deciding which of these solutions is the best has become my full time job for the last few weeks.
Background:
I have a User model which contains some very basic authentication functionality. The application will have three different types of users; admins, buyers, and sellers. The user type will be determined during sign up, where a check box will ask if the user is a seller. If the box is checked, the user is a seller, and if the box is unchecked, it's assumed their just a buyer. I would rather not have a drop down menu to select the user type (Buyer, Seller). Easy so far.
Details:
Sellers have a profile, and users who visit the site will be able to browse through the different sellers and view their profile. Buyers do not have a profile, and should not be listed on the site for users to see. If that's not complicated enough, buyers should have the option to change their account type and become a seller. Similarly, sellers can change their account type and "deactivate" their seller account, removing them from the list of sellers.
Design options:
Single table inheritance:
What I came up with when attempting to implement this using STI was suboptimal. I was faced with two options: a messy controller, which made the decision of what type of user to create based on the check box mentioned above (one controller - UsersController), or two different sign up forms that were identical (two controllers - BuyersController, SellersController).
has_one or "has_none" Profile association:
class User < ActiveRecord::Base
has_one :profile # only if the user_type or role is "seller"
end
class Profile < ActiveRecord::Base
belongs_to :user
end
Here I would use something like CanCan or declarative_authorization, and let the user chose his/her role via the check box mentioned above. This introduces a security risk, as there will be an admin role and I don't want this to be open for mass assignment.
I guess either way you look at it I'll have a messy controller with conditionals on how to create the user. Unless I choose to have two check boxes ("I am a seller," and "I am a buyer"), but this seems redundant. It also seems as though whichever design I choose, I'll be faced with some hackish solutions.
Maybe introduce some model that lies between the User and user type?
Any opinions?

I don't really think you need either STI or Polymorphism. A single user model should suffice. I would add three methods to your user model (administrator?, buyer?, seller?) and also add scopes that will return only buyers, only sellers, or only admins.
In your case, it sounds like you have a real minimal number of roles for users (2 now, expanding to 3 with admins). I would probably use the technique Ryan discusses in the "Embedded Associations" Railscast.
I've done something very similar in my latest project, and then used CanCan (in my case, the 2.0 alpha branch, as I find it simpler) on top of that. Your ability class would look something like this (in 2.0 syntax, but you can do the same with 1.x, I'm sure)
class Ability
include CanCan::Ability
def initialize(user)
if user.seller?
can :create, :profile
else
...
end
end
end

So far it sounds like you have a flag that indicates their user type, or a column with type string, etc.
You haven't indicated any behavior that depends on this--not showing up in listings etc. can be handled with scopes. Admin can be handled with a flag.
I don't see the problem yet, at least not a problem of a few weeks scope.

A 3rd option.
...buyers should have the option to change their account type and become
a seller. Similarly, sellers can change their account type and
"deactivate" their seller account, removing them from the list of
sellers.
Have you considered not allowing switching of accounts? If a buyer wants to become a seller, then they create a new "seller" account. It would work the same for seller to buyer. It would mean having 2 controllers for the signup, etc. but it would keep the separation you're looking for. If the two must be mutually exclusive you could do a rudimentary email check to verify the user doesn't already have an account and if they do, they must de-activate the existing account prior to creating a new one.

Related

Rails App Single Sign In for Multiple Organization

Background
Devise User contain the organization_id as association belongs_to Organization.
Currently sign in url for user in Rails App is
http://localhost:3000/users/organizations/1/sign_in
Above Url help me to find the user on basis of username & organization while sign_in session.
User Model validates username unique in scope of organization.
Need to achieve
http://localhost:3000/users/sign_in
As Sign in Url for the all organization.
Current sign in url is Org dependent after acheiving above it will be converted to organization independent.
Can any one suggest a possible solution, which allow me to create the multiple user having same username in different organization, so on sign in it allows user to go their respective organization.
Other solution are also Welcome.
I assume that you have multiple organization and you want to separate data between them. If that is the case you may want to consider multi-tenancy. Simple implementation of that is acts_as_tenant gem.
Than you simply scope that "membership" that would contain your username for that particular organization. records scoped with organization will have default scope on organization_id, so they can duplicate everything else and stay "clean"
Maybe not the fastest setup, but if you want to have multiple organizations - the easiest I have tried.

How to remember the organization a user is logged in for? Switching between organizations

Background:
User and Organization have a many-to-many relationship through Relationship. So a user can be part of multiple organizations.
But a user can only be logged in for 1 organization.
Therefore the user has a screen where he can switch between the organizations he belongs to.
Also, user has a default organization, which is the organization the user initially logs in for. This is implemented using a has-one through relationship.
How to know/remember which organization a user is currently logged in for?
Now I'm a bit in a pickle how to implement how the app should know/remember which organization a user is currently logged in for. I see three options and am hoping for advice which should work best:
An additional column in the User db that stores the id of the organization (or relationship?) for which the user currently is logged in. A helper method logged_in_for could then find the organization based on the value in that column and return the organization the user is logged in for. Implementation using a db column also enables validation so that a user can't set the organization it is logged in for to an organization it is not even part of.
Use a cookie: session[:logged_in_for] that defines/sets the organization the user is currently logged in for. However, 1) I have doubts whether this is secure (not sure why), 2) I think the first option facilitates validation better, 3) I also don't think this would work in combination with the log in "remember me" option?
Implement an additional has-one through relationship that defines the organization a user is logged in for. This is basically an extension of the first option that adds a relationship. Since I already have so many relationships I don't prefer this option. Or is there no way around this addition for the first option?
Is the first option indeed an effective way to know/remember which organization a user is currently logged in for?
It really depends on which experience you're looking to create for the end user. In your cases:
This will allow remembering of organization between logins, which in your application, can be useful or complete non-sense. If indeed it's useful to remember an organization (i.e, the logistics of user choosing an organization after login is non-frequent), then this could create a better user experience.
session is secure, and you can use it like a hash and it would not collide with other features in your app (unless you use the same key). This case is suited for your application when user should always choose an organization after login, and thus it should be session based.
Ref: http://guides.rubyonrails.org/security.html
Like you mentioned, this is non-ideal, as you already know.
You can store the information in sessions hash, but not use cookie store, instead use Active Record store

Create a company and user in one, what's the standard?

I know a lot of startups and tech companies essentially allow you to register, and you end up registering a company and your user.
An example would be basecamp for example. I'd like to achieve the same thing, however I'm not quite certain on how they do it, and what the best way to do it is.
My thought is to have a user and company model, where on registration you register a company, and it accepts nested attributes for user. As in my head at least the relation is:
User belongs_to :company
Company has_many :users
and the registration is a Company#new with a company.user.build.
However for some reason this does feel a bit strange, as to me it would make more sense that you register a user, and create the company it belongs to.
I just want to lay the foundation right, so I don't start building anything massive on top of a system that isn't good.
More info:
The purpose is to make the person that registers itself and the company an admin, and only allow new users to be a part of a company by being invited by an admin. Everything that goes on within the project is company based only for those within the company. You can also group users that belongs to the company, and create segments or say departments of the company. Beside that the company has no function other that being the connector between all the users that belong to the same company.

Is this the right way to associate models in a domain with multiple users per account?

Using Rails and am new to it (and RDBMs). Have read lots of posts and articles on modeling and associations, but could really use a reality check on what I'm thinking for my particular case.
I have 3 main models: users, accounts, plans. The accounts are multi-user, with plans worked on by all users attached to the account (with varying privileges). If the account is destroyed I’ll also take down its users and plans.
Looks like the basic associations would be as follows. Is this correct?
users
belongs to - >
< - has many
accounts
has many ->
<- belongs to
plans
Is there any value in associating users with plans with “has many through”? I see that it would allow access like #user.plans and #plan.user[1], but can’t I access each via accounts, as in #user.account.plan?
Is it the case that with “has many through” the middle model simply belongs to the other two? All the examples I’ve seen show that. In my case, that would be inappropriate, since account actually owns the other two.
Is there a better way to model this (multiple users of an organization working on a set of one or more plans)?
Input is very much appreciated.
Your design is correct. The belongs_to terminology can indeed be a bit strange, but is proper. Use "has many through" if it makes your code more readable and obvious. (In other words, if the notion of a user having a plan makes sense, and is needed, go ahead and create the relationship. If it is more clear to conceive of the plan belonging to an account, then stick with user.account.plans.)
Your design should be sufficient so long as you don't need to restrict a user to a subsets of the plans belonging to an account, and so long as a user only belongs to a single account.

What's the best way to store global application settings in a Rails application?

I want to handle two kinds of global configuration settings:
Settings which can be altered by the user, like if notification mails for certain events are sent or not.
Settings which are tied to a specific product edition, like disabling a feature in a free version, which is only available in the commercial version.
What's the best way to store these settings? Database, configuration file, hardcoded in the source, ...?
For both cases database. You're going to be using the same structures for multiple people/products so it makes sense. Also it allows you to change things without restarting the server.
I've handled it this way in the past:
For settings specific to the user, I've created a UserSettings model/table, that has a one-to-one to relationship with a user. The reasoning for this is that the majority of my operations involving users do no not require these settings to be loaded, so they're only included on user loads from the database when I need them.
When I do this, I'll usually group my column names, so that I can write helpers that dynamically create based on the names. Meaning that I won't have to modify my views to incorporate new settings unless I add one with a different naming scheme.
For the settings specific to a product, well that depends on how you are doing things. And there are a couple of ways to interpret your question.
The way I read it is that you want to decide on a product level. What settings users can overriding or disabling a user's setting. And possibly define some product specific settings.
I would use a one-to-many product to setting relationship. The setting table would be something simplistic (product_id, setting_name, setting_default_value, allow_user_change)
This does a number of things. It lets you have a variable list of settings for different products (Perfect for the case where you're offering many different products instead of varying tiers of access to services). It also lets you define what settings a user can/can't change and give values for that product type. That can be changed from an administrator view without restarting the application. It's also not tied to user settings, to the point where if a user doesn't have a setting listed in the product_settings there will be no problems.
The downside is you will have multiple common settings in this table. I would move settings that every product will have a different value to a field in the product table.
You will also have to write validations to ensure that a user does not change a setting their product says they can't. You will also have to write helper methods to merge settings from the product and user sides.
class Flag < ActiveRecord::Base
# id, user_id, name, value (serialized probably)
belongs_to :user
DEFAULTS = {
"newsletter" => false
}
def self.lookup(user, flag)
# Please involve memcached here
case flag
when "ssl_enabled"
# Check if user has paid for sufficient access to SSL
return false
else
stored_flag = self.find_by_user_id_and_name(user.id, flag)
if stored_flag
return stored_flag.value
else
return DEFAULTS[flag]
end
end
end
end
class User < ActiveRecord::Base
has_many :flags
def flag(name)
return Flag.lookup(self, name)
end
end
For stuff that's product edition based, you probably can't really store things in the database, because the flag is going to be based on some piece of authorization code, rather than static data.
Here's my experience with this kind of stuff: don't override behavior.
You see, your first thought is going to be something like this:
Hmm.... There are system-wide settings that may or may not be overridden by users (or products). Hey! I know this! It's composition!
And technically, you'd be correct. So, you'll make a Settings table and put all your settings in there. And then you'll have a user_settings table, where you will override those settings if the user so decides. And it'll work fine.
Until you add a setting to one table and not the other.
Or you get a bug that Setting X can't be overridden at the user or product level and it takes more than 5 seconds to figure out exactly where that setting is set.
And then you'll realize:
Hey, I'm keeping track of all these settings in at least two different places. That seems kinda dumb.
And you'd be right.
So, yes. Go ahead and keep the settings in the DB, but save them distinctly for each user or product. Use smart default values on row creation and it'll keep things nice and simple.
For the first kind of settings, I would keep them in the User model (Users table).
The second kind of settings, would go to the database again. For example if a user had a free account, that would be somehow saved in the database. I would have some helpers in Application, for example "free?" or "commercial?". These helpers could find out if they are true or false, asking the currently connected User/Account model. You could then use these helpers across different parts in your application to decide if you show or hide certain functionality.

Resources