diff --git a/README.md b/README.md index 228497c..e4e2e39 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,15 @@ Sometimes it's handy to keep track of various settings or attributes in ActiveRe The only problem is, that it would be necessary to either keep hundreds of columns in the corresponding table or create a serialized attribute which would grow over time and generally be hard to manage. -This gem consists of a global key-value-store, allowing to use both global and record bound settings. +This gem consists of a global key-value-store, allowing to use both global and record bound settings +and ActiveRecord integration to add virtual columns to models without having to change the database layout. Installation ------------ Add this line to your application's Gemfile: -``` +```ruby gem 'setting_accessors' ``` @@ -43,20 +44,18 @@ $ rails g setting_accessors:install MODEL_NAME If no model name is given, the default one (`Setting`) is used. -Usage +Usage as a global key-value-store (globally defined and anonymous settings) ----- In the following, the model name will be assumed as `Setting`, though you may choose its name freely using the provided generator (see above). Settings can either be global or assigned to an instance of ActiveRecord::Base. They consist of a name which is unique either in the global scope or within the instance and a value which can be everything that's serializable through YAML. -### Usage as global key-value-store without predefined settings - The easiest way to use the settings model is as a global key-value-store without validations and typecasting. Values can be easily set and retrieved by using their names as class method names on `Setting` itself: -``` +```ruby Setting.the_meaning_of_life = 42 Setting.the_meaning_of_life #=> 42 @@ -66,7 +65,7 @@ If the name contains characters which are not allowed due to ruby naming rules, Therefore, the following getter methods are equivalent: -``` +```ruby Setting.meaning_of_life Setting[:meaning_of_life] Setting.get(:meaning_of_life) @@ -74,8 +73,8 @@ Setting.get(:meaning_of_life) For the corresponding setters: -``` -Setting.meaning_of_life = 42 +```ruby +Setting.meaning_of_life = 42 Setting[:meaning_of_life] = 42 Setting.create_or_update(:meaning_of_life, 42) ``` @@ -91,7 +90,7 @@ As stated above, the initializer will generate a file called `settings.yml` in y An example would be a simple string setting: -``` +```yaml a_string: type: string default: "I am a string!" @@ -101,20 +100,149 @@ a_string: If a setting is defined with a type, automatic type conversion will happen, similar to what ActiveRecord does: -``` +```ruby Setting.a_string = 42 #=> "42" ``` The default value is used by the functions `Setting.get_or_default` and `Setting.create_default_setting` and a fallback option for assigned settings (see below). -Validations currently only support +The built-in validations currently support, this might be extended in the future. | Base Validation | Options | |:----------------|:---------------------------| | `presence` | `allow_nil`, `allow_blank` | | `numericality` | `only_integer` | -| `boolean` | | +| `boolean` |   | + + +### Assigned Records + +Both globally defined settings and "anonymous" settings can also be assigned to +a instances of `ActiveRecord::Base` without having to define them in the model first. + +An example would be the above mentioned saving of "items per page" values for +various views: Let's say we have an events view and want to save how many +rows/items each user would like to display. + +As there might be many views requiring this functionality, it wouldn't make +sense to define global settings for each of them. Instead, we would use anonymous +settings with a generated key based on the current view: + +```ruby +def items_per_page + key = [controller_path, action_name].join('_') + default_value = 30 + Setting.get(key, current_user) || default_value +end +``` + +ActiveRecord Integration +------------------------ + +The gem adds the method `setting_accessor` to each class which inherits from `ActiveRecord::Base`. + +`setting_accessor` takes a setting name and a set of options to customize the accessor's behaviour: + +```ruby +class MyModel < ActiveRecord::Base + setting_accessor :a_string, :fallback => :default +end +``` + +This automatically adds most of the helper methods to the model instances which are available for database columns: + +```ruby +my_model = MyModel.new + +#Setter +my_model.a_string = 1234 + +#Getter +my_model.a_string +#=> "1234" + +#Value before type cast +my_model.a_string_before_type_cast +#=> 1234 + +#Old value +my_model.a_string_was +#=> "I am a string!" (fallback value, see below) + +#Check if the value was changed +my_model.a_string_changed? +#=> true +``` + +The setting records are only persisted if all settings *and* the main record were valid. + +### Integration of globally defined settings + +If a setting with the given name is defined in `config/settings.yml`, validations and type settings are automatically fetched from it, in this case, only the `:fallback` option is allowed. + +As of now, this means that custom validations are also not supported for globally defined settings, this may be changed in the future. + +### Class-wise definition of settings + +If a setting is defined using `setting_accessor` which is not part of `config/settings.yml`, it will only be available in this class. It however may be defined in multiple classes independently. + +Defining a class setting accepts the same options as the config file: + +```ruby +class MyModel < ActiveRecord::Base + setting_accessor :my_setting, :type => :boolean, :default => false +end +``` + +### Options + +```ruby +:type => :string | :integer | :boolean | :polymorphic +``` + +`:type` defines the setting's data type. If no type is given, `:polymorhpic` +is used automatically. + +For every other type, the setting's value is automatically converted accordingly +upon setting it, mimicking ActiveRecord's behaviour regarding database columns. +Please note that `:polymorphic` values are saved as is, meaning that you can +store everything that's serializable. + +More types will be added in the future, most likely all database types +ActiveRecord can handle as well. + +```ruby +:default => "I am a string!" +``` + +`:default` sets the setting's default value. It can be retrieved either by +calling `Setting.get_or_default(...)` or defining a `:fallback` on a class setting. + +```ruby +:fallback => :global | :default | Object +``` + +The `:fallback` option specifies which value should be returned in case the +setting did not receive a value yet: + +- `:global` will try to retrieve a global setting (`Setting.NAME`) with the same + name as the accessor and return `nil` if it isn't defined. +- `:default` will return the default value set up either in the accessor method + or `config/settings.yml` for globally defined settings. +- Every other value will be returned as is + + +```ruby +:validations => {:presence => true} +:validations => {:numericality => {:only_integer => true}} +:validations => {:custom => [lambda {|setting| setting.errors.add(:not_42) if setting.value != 42}]} +:validations => {:custom => [:must_be_42]} +``` + +There are some built-in validations (see above), but you may also define custom +validations either by passing in anonymous functions or symbols representing +class methods either in the setting class or the assigned model. Contributing ------------ diff --git a/lib/tasks/ar_mailer_revised_tasks.rake b/lib/tasks/setting_accessors_tasks.rake similarity index 100% rename from lib/tasks/ar_mailer_revised_tasks.rake rename to lib/tasks/setting_accessors_tasks.rake diff --git a/setting_accessors.gemspec b/setting_accessors.gemspec index a4c657c..4749613 100644 --- a/setting_accessors.gemspec +++ b/setting_accessors.gemspec @@ -8,8 +8,9 @@ Gem::Specification.new do |spec| spec.version = SettingAccessors::VERSION spec.authors = ["Stefan Exner"] spec.email = ["stex@sterex.de"] - spec.summary = %q{Attributes without database changes. The future? (JK)} - spec.description = %q{Longer description.} + spec.summary = %q{A global key-value-store and virtual model columns} + spec.description = %q{Adds a global key-value-store to Rails applications and allows adding typed columns + to model classes without having to change the database layout.} spec.homepage = 'https://www.github.com/stex/setting_accessors' spec.license = "MIT"