Settingcrazy
TODO: Write a gem description
Installation
Add this line to your application's Gemfile:
gem 'settingcrazy'
And then execute:
$ bundle
Or install it yourself as:
$ gem install settingcrazy
Finally, create a migration and run:
$ rails g settingcrazy:setting_values_migration
$ rake db:migrate
Usage
To use simply include the SettingCrazy module in any ActiveRecord model that you want to have settings:
class User < ActiveRecord::Base
include SettingCrazy
end
Your model will now have a settings method which you can use to get and set values.
user = User.first
user.settings => {}
user.settings.my_setting = "foo"
user.settings.my_setting => "foo"
user.settings => { "my_setting" => "foo" }
To persist, call save or save! on the parent. Eg;
user.save
Serializable Values
TODO
Setting Inheritance
Your settings can inherit from the settings of a parent.
class House < ActiveRecord::Base
include SettingCrazy
has_many :rooms
end
class Room < ActiveRecord::Base
include SettingCrazy
belongs_to :house
settings_inherit_via :house
end
house = House.create(...)
house.settings.color = "blue"
house.save!
room = house.rooms.create
room.settings.color => "blue"
Setting Templates
Available settings are specified through the use of setting templates. These tell settingcrazy the options that exist, and what possible values they can take.
class House < ActiveRecord::Base
include SettingCrazy
has_many :rooms
use_setting_template Settings::House
end
use_setting_template can also be passed a block, in case your model needs to use different templates, depending on the record itself.
class Room < ActiveRecord::Base
include SettingCrazy
belongs_to :house
settings_inherit_via :house
# Use the template associated with the room type of this room
use_setting_template do |record|
record.room_type.template
end
end
Enums
The template itself is a collection of enums. Any attempt to get or set a setting that is not defined in a template will result in an ActiveRecord::UnknownAttributeError. The basic structure of an enum is:
enum :key, 'name', validation_options do
value 'value', 'key'
...
end
class Settings::House < SettingCrazy::Template::Base
enum :bedroom_count, 'Room Count', {} do
value 1, 'One'
value 2, 'Two'
value 3, 'Three'
end
end
house = House.create(...)
house.settings.bedroom_count => nil
house.settings.bedroom_count = 1
house.save!
Validation
Note: Validation does not work with namespaces yet.
Settingcrazy always validates whether the value for an option is defined in the enums. There are a number of additional validation options available.
multiple (boolean) - Whether it is valid to save more than one entry for a single key
required (boolean) - Whether a value must be set for this enum
dependent ({ enum_key: setting_value }) - A value may only be set for this option if all of the options it is dependent on are set to the specified values
type (string) - Only current available value is 'text'. This causes settingcrazy to skip the range validation, so any value for this option will be valid.
class Settings::House < SettingCrazy::Template::Base
enum :is_furnished, 'Furnished', { multiple: false, required: true } do
value false, 'Not Furnished'
value true, 'Is Furnished'
end
enum :has_dining_table, 'Has Dining Table', { dependent: { is_furnished: true } } do
value false, 'No Dining Table'
value true, 'Dining Table'
end
end
house = House.create(...)
house.valid? => false
house.errors => { :is_furnished => ["Setting, 'Furnished', is required"] }
house.settings.is_furnished = false
house.valid? => true
house.settings.has_dining_table = false
house.valid? => false ("'Has Dining Table' can only be specified if 'Furnished' is set to 'Not Furnished'")
house.settings.is_furnished = true
house.valid? => true
house.settings.has_dining_table = 3
house.valid? => false ("'3' is not a valid setting for 'Has Dining Table'")
Defaults
Defaults enable both the ability to ensure the user starts with a valid object, and that the most common values are set on creation. To define defaults, create a class method in your template that returns a hash of default settings.
class Settings::House < SettingCrazy::Template::Base
# Assuming the enums for all these settings are defined below
def self.defaults
{
bedroom_count: 2,
is_furnished: false
}
end
end
house = House.create(...)
house.settings => {}
house.settings.bedroom_count => 2
house.settings.bedroom_count = 3
house.settings => { :bedroom_count => 3 }
house.is_furnished => false
house.has_dining_table => nil
Mass Assignment and Usage in Forms
You can easily bulk set settings using a hash.
house.settings = { :foo => "bar", :fruit => "apple" }
This extends to mass assignment:
house.attributes = {
:settings => { :foo => "bar", :fruit => "apple" }
}
If using in a form you can use fields for (eg in Haml):
= form.fields_for :settings, @house.settings do |setting|
= setting.text_field :foo
Setting Namespaces
If you have a model that needs settings to be divided in some way (perhaps by functional area) you can use a namespace.
class Scenario < ActiveRecord::Base
include SettingCrazy
setting_namespace :weekdays
setting_namespace :weekends
end
Now you can access your settings like so:
scenario = Scenario.find(...)
scenario.settings.weekends.foo = "bar"
scenario.settings.weekends.foo => "bar"
scenario.settings.weekdays.foo => nil
scenario.settings.weekends => { :foo => "bar" }
Not providing a namespace still works and will access all settings:
scenario.settings.foo => "bar"
Setting namespaces work with bulk setting and mass assignment too:
scenario.settings.weekends = { :foo => "bar" }
scenario.attributes = {
:settings => {
:weekends => {
:foo => "bar
},
},
}
You can also set templates to work per namespace:
setting_namespace :weekdays, :template => WeekDayTemplate
When inheriting via a parent with namespaces you may like to specify a namespace to inherit from instead of just the settings as a whole.
settings_inherit_via :house, :namespace => :a_namespace
Or, you can even use a proc
settings_inherit_via :house, :namespace => Proc.new { |room| room.parent_setting_namespace }
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Added some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request