(Rails) Managing USER PREFS with a MASTER PREFS fallback...?
I'm looking for a mechanism by which to facilitate user preferences. I also want to have a set of "master" prefs that are used if the currently logged in user doesn't have a specific pref set. I see several questions similar to this, but they seem to get into theory instead of simply proposing a quality solution.
Basically I'm looking for input on management as well as storage -- models, controllers, etc. Initially I was considering simply going with a normalized table of 50+ columns (for performance etc.). However, I plan on adding various, unk开发者_如何学Gonown preferences in the future and, performance aside, I could imagine multiple columns getting out of hand. Thoughts?
If you don't need to manipulate or sort by individual preferences in the database, then you might want to use a single bitmask (integer) column. Basically, a bitmask is a set of on/off switches represented as a binary number. For example, let's say we have three preferences:
- view subscriptions
- view colors
- view full names
Let's say a user has 1 and 3 on and 2 off. Using 1s for on and 0s for off, the bitmask for this is:
101
(on off on)
This gets stored in the database as 5
because 101
is 5 in binary. Bitmasks are easy to store in the database (use a single integer column) and are easy to manipulate once you know the operators (for merging a user's preferences into the site defaults). Ryan Bates has a great tutorial on using bitmasks in Rails: Emmbedded Association. Hopefully that will give you the concrete example you're looking for.
In my mind, the best way to define and use defaults is to add another row in the user preferences table, and load it as a class variable in your model. Then override the accessors to find the defaults if the preference hasn't been found. Something like this:
class UserPreference < ActiveRecord::Base
# load default preferences as a class variable
@@defaults ||= find(1).attributes
# redefine accessors or each column to load default if nil
column_names.each do |column|
method_name = "#{column}_with_default".to_sym
send :define_method, method_name do
value = send("#{column_without_default}")
case value
when nil
@@defaults[column]
else
value
end
end
alias_method_chain column, :default
end
...
end
Essentially the default preferences (loaded from row 1) are stored in the Model as a class variable. All the accessors are redefined and made part of an alias method chain so that the default would be returned if the returned value was nil. I wanted to use || instead of case, but that would cause problems in the event that the user had set a boolean preference to false.
Edit: N.B. I don't know of a good way to update the defaults in a rails app without restarting the server.
精彩评论