Is Single-Table Inheritance the right solution for my Rails problem?
Greetings, all,
I'm working on an application in Ruby on Rails where we need to keep track of a bunch of external services for each user (for example, Facebook, MySpace, Google, SalesForce, Twitter, WordPress, etc) that the app will access on behalf of the user. For some services, we will need to store an (encrypted) username and password, for some we will need to save OAuth data, for some OAuth2 data, and so on. As the app grows, we will need to support even more types of accounts, each with its own set of authentication data.
Each user can create posts within the application, and we will take those posts and send them to the external services to be published for the user. We then track the response to the published post (retweets on Twitter, likes/shares on Facebook, and so on).
So:
class User < ActiveRecord::Base
has_many :services
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :publishes
end
class Publish < ActiveRecord::Base
has_one :service
belongs_to :post
end
class Service < ActiveRecord::Base
belongs_to :user
belongs_to :publish
end
I am debating between using Single-Table Inheritance for my Service
types (for example, WordpressService
, FacebookService
, TwitterService
, and simply serializing a simple hash to save the authentication data) and using a traditional, normalized scheme where every type of service is its o开发者_JS百科wn model and table. I would like to be able to easily iterate over all the Services associated with a User, and a Publish needs to be able to be associated to any type of Service (a Publish might be sent to WordPress, Facebook, or Twitter, for example).
Can I achieve that kind of model relationship using a traditional normalized approach? Or is this exactly what STI was meant to solve?
Thanks.
You may want to check out the omniauth plugin which is pretty easy to setup and handles storing authentication credentials for a number of services out of the box. There are a couple of railscasts that show how to set it up. If nothing else, you could see how they suggest storing things.
As an alternative to STI, you could use polymorphic associations:
class AccountAuth < AR::Base
belongs_to :account
belongs_to :authentication, :polymorphic => true
end
# account_id :integer
# authentication_id :integer
# authentication_type :string
module Auth
#common logic
end
class FacebookAuth < AR::Base
include Auth
has_one :account_auth,:as=>:authentication
has_one :account, :through => :account_auth
end
class Account < AR::Base
has_many :account_auths
has_many :authentications, :through => :account_auths
end
This and this may help you.
How many millions of users will you store, how many times per second will you be querying the table? I'd say in general that your physical design would suffer from that type of storage but hardware will overcome those deficiencies of design for a large number of applications. If you're not operating on large scale date or high volume transaction, you'll be fine with anything you whip up.
Though I'm still not sure if it's the "right" way to solve this problem, I've decided on using Single-Table Inheritance so that I can easily get a list of all the services that another model has_many
of (since each subclass of Service
is also a Service
, I can call model_instance.services
to get them all).
To solve the problem of code duplication, I created a module for use in any model that should has_many :services
as well as each type of service:
module HasServices
extend ActiveSupport::Concern
included do
has_many :services
has_many :facebook_services
has_many :twitter_services
has_many :wordpress_services
end
end
Service
is also aware of its subclasses, so that menus, etc. can easily be created:
class Service < ActiveRecord::Base
@child_classes = []
...
protected
def self.inherited(child)
@child_classes << child
super
end
def self.child_classes
@child_classes
end
end
精彩评论