Make application-wide settings accessible to the user via web forms - ruby-on-rails

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.

Related

Best practices regarding per-user settings and predefining options

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

Rails - Force model to be created via factory method

I'm using Rails 4. I have a class, Cart, which needs to be accessed within my application.
I want it accessed using the factory pattern:
class CartFactory
def self.obtain_cart_for_user(user)
...
end
end
I need this approach because sometimes, I want to return an existing cart and sometimes create a new one (based upon the age of the cart, its contents, whether the products in it are still available etc).
This is easy enough.
However, I also want to make sure some other future programmer doesn't instantiate a cart directly, or fetch one by any other means, including via model associations, such as:
Cart.new(...)
user.carts.new(...)
Cart.find(id)
Cart.find_by_attribute(blah: blah)
Cart.where(...).first
Is there any way to prevent that?
Well, it's possible to make the constructor private:
private_class_method :new
And of course, you can try making the ActiveRecord query methods (.find, .where etc.) private as well. But to me that sounds like a good way to end up with erratic behaviour. If you were to go this route, make sure your app is thoroughly tested first.
Another route would be for Cart not to extend ActiveRecord::Base (which I'm assuming it does), and instead include only the parts you need, like ActiveRecord::Persistence. If you are willing to dive in deep, check out the parts that are included in the source for ActiveRecord::Base.
Edit: Still one option would be to make Cart itself private within a module that only exposes CartFactory. There's no built-in syntax for a "private class", but it's possible to achieve since Ruby classes are just regular objects. Again, no idea how well ActiveRecord would deal with that.
But lastly there is of course the question of whether you want to do this at all. In general, Ruby is not very good at protecting you from yourself. :) As expressed in the latter linked answer, documentation and trust go a long way.

Rails: best practice to scope queries based on subdomain?

I'm working on a Rails (currently 2.3.4) app that makes use of subdomains to isolate independent account sites. To be clear, what I mean is foo.mysite.com should show the foo account' content and bar.mysite.com should show bar's content.
What's the best way to ensure that all model queries are scoped to the current subdomain?
For example, one of my controllers looks something like:
#page = #global_organization.pages.find_by_id(params[:id])
(Note #global_organization is set in the application_controller via subdomain-fu.)
When what I would prefer is something like:
#page = Page.find_by_id(params[:id])
where the Page model finds are automatically scoped to the right organization. I've tried using the default_scope directive like this: (in the Page model)
class Page < ActiveRecord::Base
default_scope :conditions => "organization_id = #{Thread.current[:organization]}"
# yadda yadda
end
(Again, just to note, the same application_controller sets Thread.current[:organization] to the organization's id for global access.) The problem with this approach is that the default scope gets set on the first request and never changes on subsequent requests to different subdomains.
Three apparent solutions thus far:
1 Use separate vhosts for each subdomain and just run different instances of the app per subdomain (using mod_rails). This approach isn't scalable for this app.
2 Use the original controller approach above. Unfortunately there are quite a number of models in the app and many of the models are a few joins removed from the organization, so this notation quickly becomes cumbersome. What's worse is that this actively requires developers to remember and apply the limitation or risk a significant security problem.
3 Use a before_filter to reset the default scope of the models on each request. Not sure about the performance hit here or how best to select which models to update per-reqeust.
Thoughts? Any other solutions I'm missing? This would seem to be a common enough problem that there's gotta be a best practice. All input appreciated, thanks!
Be careful going with default scope here, as it will lead you into a false sense of security, particularly when creating records.
I've always used your first example to keep this clear:
#page = #go.pages.find(params[:id])
The biggest reason is because you also want to ensure this association is applied to new records, so your new/create actions will look like the following, ensuring that they are properly scoped to the parent association:
# New
#page = #go.pages.new
# Create
#page = #go.pages.create(params[:page])
Have you tried defining the default_scope a lambda? The lambda bit that defines the options get evaluated every time the scope is used.
class Page < ActiveRecord::Base
default_scope lambda do
{:conditions => "organization_id = #{Thread.current[:organization]}"}
end
# yadda yadda
end
It's essentially doing your third option, by working in tandem with your before filter magic. But it's a little more aggressive than that, kicking in on every single find used on the Page model.
If you want this behaviour for all models you could add the default_scope to ActiveRecord::Base, but you mention a few being a couple of joins away. So if you go this route, you'll have to override the default scopes in those models to address the joins.
We've been using https://github.com/penguincoder/acts_as_restricted_subdomain for the last 2 years, but it only works with Rails 2.3.
We are currently trying to upgrade (and gemmify) the plugin to work with Rails 3. I'm curious on how you worked your problem out.
You might be better of having a database per account and switching the database connection based on the subdomain.
In addition to the above link if you have a model (in your case Account) that you want to use the default database just include establish connection in the model.
class Account < ActiveRecord::Base
# Always use shared database
establish_connection "shared_#{RAILS_ENV}".to_sym
I've found the acts_as_tenant gem [github] works great for this feature. It does the scoping for you with minimal extra effort, and also makes bypassing the scoping difficult.
Here's the initial blog post about it: http://www.rollcallapp.com/blog/2011/10/03/adding-multi-tenancy-to-your-rails-app-acts-as-tenant

Where is the best place to store application parameters : database, file, code...?

I am developing a Ruby on Rails website and I have an "architectural" question : my application needs some parameters and I'm wondering where to store them.
In concrete terms, my application receive some requests which are evaluated and then sent. So, the Request model must have attributes concerning these treatments : a validation status and a sending status. For instance, validation status can be "accepted", "rejected" or "waiting". Sending status can be "sent", "waiting", "error during sending" or stuff like that. I have to store those status codes parameters somewhere, but I don't know what is the best solution.
I could create a model for each one and store them in the database (and having an active record model ValidationStatus for instance) but : wouldn't it be a bite excessive to create a database/model for storing data like that?
I could also just use them in the code without "storing" them, I could store them in a YAML file...
So, a more simpler question: how do you deal with your application parameters in RoR?
There are lots of global configuration plugins, most of them revolve around the idea of loading a YAML file at some point. Check this page, this plugin and even this Railscast.
I put them in the database. I have a lot of these, and they are all pretty straightforward lists of strings. The tables are all the same - id, name, description.
I generate models for them rather than having an actual model file for each one. In app/models I have a file called active_record_enums.rb, which in your case would look something like this:
ACTIVE_RECORD_ENUMS = %w{
ValidationStatus
SendingStatus
}
ACTIVE_RECORD_ENUMS.each do |classname|
eval "class #{classname} < ActiveRecord::Base; end"
classname.constantsize.class_eval do
# Add useful methods - id_for(name) and value_for(id) are handy
end
end
This file has to be required in a config file somewhere; other than that it's pretty straightforward.
(Have since viewed that rails cast mentioned above [episode 85] - it looks like a bit more 'the rails way' than below)
Another approach is to build on the existing configuration mechanism in Rails.
Lets presume there are two types of configs:
App wide configs common to dev/test/prod environments
Configs specific to envrionments dev/test/prod
For the first scenario, items in "RAILS_ROOT + '/config/environment.rb'" work. Just see that the names are captialised so they are Ruby constants. A variation to this is have a reference to another file in that environment.rb file ...
require RAILS_ROOT + '/config/appConfigCommon.rb'
and place relevant config items in that file. This has the advantage of being able to be referenced independant of Rails.
For scenario 2, a similar approach can be taken. Place items for development in "RAILS_ROOT + '/config/environments/development.rb'" or something like
require RAILS_ROOT + '/config/environments/appConfigDev.rb'
and place environment specific items in that required file, making sure they start with caps. And follow the same pattern for test/prod (and others if need be).
The config items are directly accessible in views and controllers (not sure about models) by simply using the constant name.
I am not using Ruby but I will tell you that I started out (in ASP.NET) placing lots of settings in a Web.Config file (similar to a YAML). As time went on, though, the system evolved to the point where different instances needed different settings. So, almost all of them have migrated to the database. So...if you'll be deploying multiple instances of your site, I'd strongly recommend keeping settings in a table of your database (mine has just one record, with fields for various settings). If I had done this to start, I'd have saved a significant amount of time.
I tend to just make a string column for each, and use validates_inclusion_of to set what's acceptable for them.
class Request < ActiveRecord::Base
validates_inclusion_of :validation_status, :in => ['accepted','rejected','waiting']
validates_inclusion_of :sending_status, :in => ['sent','waiting','...']
end
If you need to have things happen (ie. emails sent) when the status changes, look into using the Acts As State Machine plugin to manage it.

How to choose a single model and persist that choice?

I have a simple model called Party with a corresponding table called parties. There's also a controller with all the usual CRUD actions and so on. This model is used in a website and only one admin user is allowed to edit the parties - everyone else is allowed to call GET actions (index, show). Nothing special so far.
Now I need to do the following: The admin would like to choose a single Party at a time for special presentation (the selected Party is showing up on the start page of the application). The most important thing is, that there's only ONE party at time selected.
How would you solve this problem? Boolean Flag in Party model? Save the selection (id of the party) somewhere outside the database? Implement a new model with a has_one relation to Party (seems like overkill to me)?
I hope my explanation is good enough to understand the issue.
A simple "front_page" attribute would suffice or another model like you mentioned, using the has_one relationship would be fine as well.
Using another model would allow you to maintain some more information, like how long should it remain on the front page (expiration date?) or how many times it was featured (assuming a party can be featured twice). It really depends on other requirements for your system.
You also might be able to get away with a simple implementation of the Singleton pattern as well. There's a quick description on the Rails Wiki of making an ActiveRecord object a Singleton (see below): http://wiki.rubyonrails.org/rails/pages/TipsAndTricks
Making a singleton ActiveRecord object
If you have a table with just one
entry, useful for keeping track of a
number sequence for databases without
sequences, you can use the singleton
module included with ruby like so:
require 'singleton'
class Master < ActiveRecord::Base
include Singleton
def initialize(args=nil) super(args) if record = Master.find(:first)
self.attributes = record.attributes end end def next_tracking_number increment!
(:current_tracking_number) current_tracking_number end def
self.next_tracking_number instance.next_tracking_number
end
end
Update:
This is a very poor code example (was copied and pasted from the Rails Wiki, which had no formatting). I would highly recommend the [Ruby Design Patterns] book which tackles many of the GoF design patterns in greater detail (while making them applicable to Ruby applications). But Google should return you some good resources for using the Singleton pattern in Ruby.2
I would go for the boolean flag and create nested singleton resource (promoted), which I would implement in PartiesController itself (set_promoted_party and get_promoted_party actions). For these I would create two new routes:
PUT /parties/promoted/:party_id # to set the promoted party
GET /parties/promoted/:party_id # to get the promoted_party
I would add a second model that had a has_one relationship in order to keep the app RESTful and simple. Also, this way, you can keep a history of special Parties, and track other meaningful information related to the special parties.
Personally I'm very strong on data integrity being enforced by my database so would probably add that extra table and enforce it as a foreign key constraint there.
It can seem like overkill, but is the only* solution that prevents data integrity issues.
Could you maybe add it as a field to the admin table/model - which would be an enforced foreign key to the party table?
*Another solution would be a database trigger that checks no other rows are the selected party but I tend to shy away from such solutions.
Keep it simple. Put a promoted_party.yml file in your config directory that the controllers write to and read from. The contents can be as simple as this:
---
party_id: 123
Done. If you need more integrity or fancier relationships later, implement that later, not now.
For deployments, just make sure the file is symlinked to a shared directory to survive application upgrades.

Resources