Preferential allows you add preferences to your ActiveRecord models, the neat, simple and elegant way.
Preferential borrows heavily from Christopher J Bottaro’s plugin called [has_easy](github.com/cjbottaro/has_easy). So much that even the API hasn’t changed much barring a few minor differences.
-
The plugin is Rails 3 compatible
-
Minor typos that caused errors in the original plugin have been fixed
-
The plugin *drops support* for the aliases feature. Calling the same thing with many names just creates more problems.
What’s the difference between flags, preferences and options? Nothing really, they are just “has many” relationships. So there should be no reason to install a separate plugin for each one. This plugin can be used to add preferences, flags, options, etc to any ActiveRecord model.
git clone git://github.com/exceed/preferential.git vendor/plugins/preferential rails g install preferential:migration rake db:migrate rake db:test:prepare
class User < ActiveRecord::Base has_numerous :preferences do |p| p.define :color p.define :theme end has_numerous :flags do |f| f.define :is_admin f.define :is_spammer end end user = User.new # hash like access user.preferences[:color] = 'red' user.preferences[:color] # => 'red' # object like access user.preferences.theme? # => false, is theme is not set user.preferences.theme = "savage thunder" user.preferences.theme # => "savage thunder" user.preferences.theme? # => true, as theme is now set # easy access for form inputs user.flags_is_admin? # => false user.flags_is_admin = true user.flags_is_admin # => true user.flags_is_admin? # => true # save user's preferences user.preferences.save # will save silently and add validation errors to the user model user.errors.empty? # hopefully true # save user's flags user.flags.save! # will raise exception on validation errors
There are a lot of options that you can use with preferential:
-
default values
-
inheriting default values from parent associations
-
calculated default values
-
type checking values
-
validating values
-
preprocessing values
In this section, we’ll go over how to use each option and explain how it is used.
Very simple. It sets a default value for the preference even when none exist on the DB.
class User < ActiveRecord::Base has_numerous :options do |p| p.define :gender, :default => 'female' end end User.new.options.gender # => 'female'
Allows the model to inherit it’s default value from other associated models.
class Account < ActiveRecord::Base has_many :users has_numerous :options do |p| p.define :locale, :default => 'en' end end class User < ActiveRecord::Base belongs_to :client has_numerous :options do |p| p.define :gender, :default_through => :account end end account = Account.create user = account.users.create user.options.locale # => 'en' user.options.locale = 'es' client.options.save user.client(true) # reload association user.options.gender # => 'es' User.new.options.gender => 'en'
Allows for calculated default values.
class User < ActiveRecord::Base has_numerous 'prefs' do |t| t.define :likes_cheese, :default_dynamic => :defaults_to_like_cheese t.define :is_dumb, :default_dynamic => Proc.new{ |user| user.dumb_post_count > 10 } end def defaults_to_like_cheese cheesy_post_count > 10 end end user = User.new :cheesy_post_count => 5 user.prefs.likes_cheese? => false user = User.new :cheesy_post_count => 11 user.prefs.likes_cheese? => true user = User.new :dumb_post_count => 5 user.prefs.is_dumb? => false user = User.new :dumb_post_count => 11 user.prefs.is_dumb? => true
Allows type checking of values (for people who are into that).
class User < ActiveRecord::Base has_numerous :prefs do |p| p.define :theme, :type_check => String p.define :dollars, :type_check => [Fixnum, Bignum] end end user.prefs.theme = 123 user.prefs.save! # ActiveRecord::InvalidRecord exception raised with message like: # 'theme' for has_numerous('prefs') failed type check user.prefs.dollars = "hello world" user.prefs.save user.errors.empty? # => false user.errors.on(:prefs) # => 'dollars' for has_numerous('prefs') failed type check
Make sure that values fit some kind of criteria. If you use a Proc or name a method with a Symbol to do validation, there are three ways to specify failure:
-
return false
-
raise a HasEasy::ValidationError
-
return an array of custom validation error messages
class User < ActiveRecord::Base has_numerous :prefs do |p| p.define :foreground, :validate => ['red', 'blue', 'green'] p.define :background, :validate => Proc.new{ |value| %w[black white grey].include?(value) } p.define :midground, :validate => :midground_validator end def midground_validator(value) return ["msg1", msg2] unless %w[yellow brown purple].include?(value) end end user.prefs.foreground = 'yellow' user.prefs.save! # ActiveRecord::InvalidRecord exception raised with message like: # 'theme' for has_numerous('prefs') failed validation user.prefs.background = "pink" user.prefs.save user.errors.empty? => false user.errors.on(:prefs) => 'background' for has_numerous('prefs') failed validation user.prefs.midground = "black" user.prefs.save user.errors.on(:prefs)[0] => "msg1" user.errors.on(:prefs)[1] => "msg2"
Alter the value before it goes through type checking and/or validation. This is useful when working with forms and boolean values. CAREFUL!! This option only applies to the underscore accessors, i.e. prefs_likes_cheese=
, not prefs.likes_cheese=
or prefs[:likes_cheese]=
.
class User < ActiveRecord::Base has_numerous :prefs do |p| p.define :likes_cheese, :validate => [true, false], :preprocess => Proc.new{ |value| ['true', 'yes'].include?(value) ? true : false } end end user.prefs.likes_cheese = 'yes' # :preprocess NOT invoked; it only applies to underscore accessors!! user.prefs.likes_cheese => 'yes' user.prefs.save! # exception, validation failed user.prefs_likes_cheese = 'yes' # :preprocess invoked user.prefs.likes_cheese => true user.prefs.save! # no exception
Alter the value when it is read. This is useful when working with forms and boolean values. CAREFUL!! This option only applies to the underscore accessors, i.e. prefs_likes_cheese
, not prefs.likes_cheese
or prefs[:likes_cheese]
.
class User < ActiveRecord::Base has_numerous :prefs do |p| p.define :likes_cheese, :validate => [true, false], :postprocess => Proc.new{ |value| value ? 'yes' : 'no' } end end user.prefs.likes_cheese = true user.prefs.likes_cheese # :postprocess NOT invoked, it only applies to underscore accessors => true user.prefs_likes_cheese # :postprocess invoked => 'yes'
Suppose you have a has_numerous
field defined as a boolean and you want to use it with a checkbox in form_for
.
(model) class User < ActiveRecord::Base has_numerous :prefs do |p| p.define :likes_cheese, :type_check => [TrueClass, FalseClass], :preprocess => Proc.new{ |value| value == 'yes' }, :postprocess => Proc.new{ |value| value ? 'yes' : 'no' } end end (view) <% form_for(@user) do |f| %> <%= f.check_box 'user', 'prefs_likes_cheese', {}, 'yes', 'no' %> # invokes @user.prefs_likes_cheese which does the :postprocess <% end %> (controller) @user.update_attributes(params[:user]) # invokes @user.prefs_likes_cheese= which does the :preprocess @user.prefs.save @user.prefs.likes_cheese => true or false @user.prefs_likes_cheese # remember, only underscore accessors invoke the :preprocess and :postprocess options => 'yes' or 'no'
The general idea is that we make the form use prefs_likes_cheese=
and prefs_likes_cheese
accessors which in turn use the :preprocess and :postprocess options. Then in our normal code, we use prefs.likes_cheese
or prefs[:likes_cheese]
accessors to get our expected boolean values.
Copyright © 2008 Christopher J. Bottaro <[email protected]>, released under the MIT license