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, unknown 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.
Related
I want to save settings for my users and some of them would be one out of a predefined list! Using https://github.com/ledermann/rails-settings ATM.
The setting for f.e. weight_unit would be out of [:kg, :lb].
I don't really want to hardcode that stuff into controller or view code.
It's kind of a common functionality, so I was wondering: Did anyone come up with some way of abstracting that business into class constants or the database in a DRY fashion?
Usually, when I have to store some not important information which I don't care to query individually, I store them on a serialized column.
In your case you could create a new column in your users table (for example call it "settings").
After that you add to user model
serialize :settings, Hash
from this moment you can put whatever you like into settings, for example
user.settings = {:weight_unit => :kg, :other_setting1 => 'foo', :other_setting2 => 'bar'}
and saving with user.save you will get, in settings column, the serialized data.
Rails does also de-serialize it so after fetching a user's record, calling user.settings, you will get all saved settings for the user.
To get more information on serialize() refer to docs: http://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize
UPDATE1
To ensure that settings are in the predefined list you can use validations on your user model.
UPDATE2
Usually, if there are some pre-defined values it's a good habit to store them in a constant inside the related model, in this way you have access to them from model (inside and outside). Acceptable values does not change by instance so it makes sense to share them between all. An example which is more valuable than any word. Defining in your User model:
ALLOWED_SETTINGS = {:weight_unit => [:kg, :lb],
:eyes_color => [:green, :blue, :brows, :black],
:hair_length => [:short, :long]}
you can use it BOTH
outside the model itself, doing
User::ALLOWED_SETTINGS
inside your model (in validations, instance methods or wherever you want) using:
ALLOWED_SETTINGS
Based on your question, it sounds like these are more configuration options that a particular user will choose from that may be quite static, rather than dynamic in nature in that the options can change over time. For example, I doubt you'll be adding various other weight_units other than :kg and :lb, but it's possible I'm misreading your question.
If I am reading this correctly, I would recommend (and have used) a yml file in the config/ directory for values such as this. The yml file is accessible app wide and all your "settings" could live in one file. These could then be loaded into your models as constants, and serialized as #SDp suggests. However, I tend to err on the side of caution, especially when thinking that perhaps these "common values" may want to be queried some day, so I would prefer to have each of these as a column on a table rather than a single serialized value. The overhead isn't that much more, and you would gain a lot of additional built-in benefits from Rails having them be individual columns.
That said, I have personally used hstore with Postgres with great success, doing just what you are describing. However, the reason I chose to use an hstore over individual columns was because I was storing multiple different demographics, in which all of the demographics could change over time (e.g. some keys could be added, and more importantly, some keys could be removed.) It sounds like in your case it's highly unlikely you'll be removing keys as these are basic traits, but again, I could be wrong.
TL;DR - I feel that unless you have a compelling reason (such as regularly adding and/or removing keys/settings), these should be individual columns on a database table. If you strongly feel these should be stored in the database serialized, and you're using Postgres, check out hstore.
If you are using PostgreSQL, I think you can watch to HStore with Rails 4 + this gem https://github.com/devmynd/hstore_accessor
I am building a rails app and the data should be reset every "season" but still kept. In other words, the only data retrieved from any table should be for the current season but if you want to access previous seasons, you can.
We basically need to have multiple instances of the entire database, one for each season.
The clients idea was to export the database at the end of the season and save it, then start fresh. The problem with this is that we can't look at all of the data at once.
The only idea I have is to add a season_id column to every model. But in this scenario, every query would need to have where(season_id: CURRENT_SEASON). Should I just make this a default scope for every model?
Is there a good way to do this?
If you want all the data in a single database, then you'll have to filter it, so you're on the right track. This is totally fine, as data is filtered all the time anyway so it's not a big deal. Also, what you're describing sounds very similar to marking data as archived (where anything not in the current season is essentially archived), something that is very commonly done and usually accomplished (I believe) via setting a boolean flag on every record to true or false in order to hide it, or some equivalent method.
You'll probably want a scope or default_scope, where the main downside of a default_scope is that you must use .unscoped in all places where you want to access data outside of the current season, whereas not using a default scope means you must specify the scope on every call. Default scopes can also seem to get applied in funny places from time to time, and in my experience I prefer to always be explicit about the scopes I'm using (i.e. I therefore never use default_scope), but this is more of a personal preference.
In terms of how to design the database you can either add the boolean flag for every record that tells whether or not that data is in the current season, or as you noted you can include a season_id that will be checked against the current season ID and filter it that way. Either way, a scope of some sort would be a good way to do it.
If using a simple boolean, then either at the end of the current season or the start of the new season, you would have to go and mark any current season records as no longer current. This may require a rake task or something similar to make this convenient, but adds a small amount of maintenance.
If using a season_id plus a constant in the code to indicate which season is current (perhaps via a config file) it would be easier to mark things as the current season since no DB updates will be required from season to season.
[Disclaimer: I'm not familiar with Ruby so I'll just comment from the database perspective.]
The problem with this is that we can't look at all of the data at once.
If you need to keep the old versions accessible, then you should keep them in the same database.
Designing "versioned" (or "temporal" or "historized") data model is something of a black art - let me know how your model looks like now and I might have some suggestions how to "version" it. Things can get especially complicated when handling connections between versioned objects.
In the meantime, take a look at this post, for an example of one such model (unrelated to your domain, but hopefully providing some ideas).
Alternatively, you could try using a DBMS-specific mechanism such as Oracle's flashback query, but this is obviously not available to everybody and may not be suitable for keeping the permanent history...
I am new to RoR and working on an existing Rails 2 app. The app reports on a number of metrics, and I need to add a new metric that is a ratio of two of the others. I'd like to understand the "right" Rails way to do this kind of thing. But I also want to get it done quickly and with minimal risk to the rest of the code. I want to extend the reporting, summary, graphing, and aggregation logic on the existing metrics to the new calculated field, without having to re-implement everything in a slightly different way.
I could add a field to the DB and install a trigger procedure to maintain it. This has the advantage of pushing the work into the DB, and allowing me to index the field if necessary, but it consumes another field in the DB, and I don't think I really need an index on the field at the moment. What would the AR migration look like?
I could create a view that calculates the field. How do I just switch my model to use the new view that has the new field without disrupting the rest of the code? What does the AR migration look like?
I could add the new field into the queries ... SELECT my_table.*, a/b AS ab_ratio FROM my_table ... does this mean I need to examine all of the .find calls on that particular model and change those? Is there a better way to do it?
It would be very helpful to have some code examples of where and how this kind of thing is implemented and/or documented.
I recommend using events on the model to calculate a synthetic value and keeping any business logic/triggers out of the database:
class MyRecord < ActiveRecord::Base
before_save :update_ratio
private
def update_ratio
self.ratio = self.a / self.b
end
end
I've been looking around all afternoon to try and find the dominant convention for setting up user-modifiable application-wide settings in Rails... no dice so far.
I'm working on a simple application which is intended to be used by one organization, so there's no need for a user model, there's only a single administrator. That administrator needs to have the ability to modify certain site-wide preferences, things like the logo, color scheme, tagline, etc.
What's the best practice for creating this kind of application-wide settings in Rails 3.1, and making them easily accessible to the end-user? Bonus points for any example apps you can link to.
The dominant convention to store editable app-wide settings seems to be the concept of a key-value store, backed either by ActiveRecord or other mechanisms. And, as far as I know, there are at least two nice strategies for storing your app-wide settings, depending on your requisites.
If you want a generic approach, yet extremely flexible for defining a couple of (not) scoped settings that can be in association with AR Models, you have Rails-Settings (or its cached version Rails-Settings-Cached). I haven't tried using the plugin in Rails 3.1 but it works well on 3.0. It allows you to have things like:
Settings.main_color = '#3333CC'
Settings.logo_file_name = 'images/logo.png'
Setting['preferences.color'] = :blue
In case you want a robust approach, with Single-Table-Inheritance and allowing you to perform validations in certain settings as you would with actual AR Records, you have this nice article, written by Jeff Dean, which steps you through the process. This way you scope settings by grouping them into subclasses and you can have things like:
class ApplicationSettings::PageLayout < ApplicationSetting
validates :title, :presence => true
...
def title
value
end
def title=(value)
self.value = value
end
And I guess that with some simple tuning you can even have has_many and belongs_to associations in some of your settings (like a variable-sized list of phone numbers or e-mails).
Personally I prefer the latter approach (when settings are a big issue) because it gives you more control over the settings you store and keeps your code clean and DRY, allowing you to follow the MVC pattern.
I generally set up a Properties model with some basic scaffolding in the admin section - then call the relevant Property 'field' where required, say Property.find(:field => "EARLIEST_DATE:YEAR") which would have a user settable value.
Properties might not be the best name for a database table (tend to think there's too much chance of a reserved name collision somewhere down the line) - but you get the idea. Advantage is you can set up scopes to access the values set by the user.
I have a large Rails app that has 34 different types of Users through Single Table Inheritance. When the app was initially designed, it was assumed that the User would have different behaviors based on type. This assumption was wrong, so I'm looking to refactor.
The question is how would you refactor out the User STI?
Add a ton of boolean attributes to User (ie. User#is_employee?, User.is_contractor?)
Rename User#type to User#user_type and just do string matching based on the field
Some horrible lookup table solution
Something I'm missing...
Just to clarify, a User needs a 'type' for ACL reasons, but with STI they're really just empty models and STI causes issues with specing, general pain in the ass, etc.
It sounds like what you really need are roles rather than user types. I've found that this is usually the case when you end up with many user types, is because some users have more than one role.
A simple ACL model is: User has many roles, each role has many permissions.
The permission set for a user is the set of all permissions for all roles the user belongs to.
In more complicated cases a permission is often a calculated property rather than a row in a database, for the simple reason that permissions are some times target and time based (!).
#user.can_view_payroll_info_for?(#employee, 2.years.ago) ==> true
#user.can_view_payroll_info_for?(#employee, Time.now) ==> false
Most of the time though, a role is as high resolution as you need to get:
#john.has_role(:content_author) ==> true
#john.has_role(:moderator) ==> true
#benny.has_role(:content_author) ==> true
#benny.has_role(:moderator) ==> false
#michael.has_role(:moderator) ==> true
#michael.has_role(:content_author) ==> false
You can implement it as simply as a comma separated (validated) string 'roles' column for the user or a join table if you want roles to be normalized.
Are all 34 models empty?
If they are you could do:
self.inheritance_column = nil
At the top of your User model, that way you can just check type:
if #user.type == "Employee"
# do something
end
It's tough to answer the question without knowing in what way your 'ACL reasons' will be using the type, but...
In my experience I have always gone with User#user_type. It always turned out for me that the cases were rare enough that this was good. Though I have only ever had a few user types. Another option (which could be what you were alluding to with your second option) would be to use method_missing to handle the string matching, and allow it to behave like option 1.
For example:
def method_missing(method, *args, &block)
if(method.to_s.starts_with?('is_'))
self.user_type == method.to_s.gsub("is_", "").gsub("?", "")
end
end
this would return true for is_contractor? if the user_type is contractor.
Note that the snippet provided is untested, and that the chained gsub's are pretty awful looking. I'm sure this can be written as a nice little regular expression or in some other clever way.