Giter VIP home page Giter VIP logo

settingcrazy's Introduction

Settingcrazy

Similar to other settings gems, Settingcrazy allows you to set arbitrary settings to your ActiveRecord models. It also allows you inherit settings from parent objects, save namespaced settings, and use setting templates to specify possible setting keys and values, validations, and default values.

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 the 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!

A setting template can inherit enums from another setting template by inheriting from that template. This is useful if you want the enums in one template to be a superset of the enums in another template. Enums can be added or overwritten by specifying them as normal.

class Settings::Vehicle < SettingCrazy::Template::Base
	enum :passenger_capacity, 'Passenger Capacity', {} do
		value 1, 'One'
		value 2, 'Two'
		value 3, 'Three'
		value 4, 'Four'
		# ...
	end
end

class Settings::Car < Vehicle
	# New enums can be added
	enum :wheel_count, 'Wheel Count', {} do
		value 3, 'Three'
		value 4, 'Four'
	end
	
	# Existing Enums can be overwritten
	enum :passenger_capacity, 'Passenger Capacity', {} do
		value 2, 'Two'
		value 3, 'Three'
		value 4, 'Four'
		value 5, 'Five'
	end
end

Validation

Settings validation will only occur for a model that is using a template, or a namespaced template. By default, validation will only occur on a record that has been persisted, so settings may be invalid on object creation. To validate settings on creation, validate_settings_on_create true must be set in the model.

class House < ActiveRecord::Base
	include SettingCrazy
	validate_settings_on_create true
	...
end

When validating, Settingcrazy will always validate whether the value set for an option has been defined as a possible value for that option. As well as this automatic validation, there are a number of additional validation options that can be specified per enumeration.

# multiple (boolean) - Whether it is valid to save more than one entry for a single key
# 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
# required (boolean) - Whether a value must be set for this enum
# required_if ({ enum_key: setting_value }) - A value must 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.
# greater_than|greater_than_or_equal_to|less_than|less_than_or_equal_to|equal_to|not_equal_to ({ value: number } | { attribute: :attribute_name } | { attribute: :attribute_name, association: :association_name })

Due to the ability to namespace settings (discussed later), the validation errors for each object are placed in the hash, setting_errors, for each model. If validation of settings fails, the object will still be flagged as invalid, but the details will need to be retrieved from setting_errors. To allow multiple templates to contain settings of the same name, validation errors for settings will be listed under the setting template's class name of the invalid setting(s).

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 => { :base => ["Settings are invalid"] }
house.setting_errors => { 'Settings::House' => { :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
Numerical Validation

As shown above, a number of mathematical operators are available for numerical validation. These allow comparison with a static value, as well as with other settings of the current instance, and finally with settings of associated instance.

class Settings::House < SettingCrazy::Template::Base
  enum :window_count, 'Number of Windows', { type: 'text', greater_than: { value: 0 } } do
    value '', 'Number of Windows text value'
  end
end

class Settings::Room < SettingCrazy::Template::Base
  enum :window_count, 'Number of Windows', { type: 'text', less_than_or_equal_to: { association: :house, attribute: :window_count } } do
    value '', 'Number of Windows text value'
  end
end

house = House.create(...)
room = house.rooms.create(...)
house.settings.window_count => 0
house.valid? => false
house.setting_errors => {'Settings::House' => { :window_count => ["Setting, 'Number of Windows' must be greater than 0" ] } }
house.settings.window_count = 2
room.settings.window_count = 3
room.valid? => false
room.setting_errors => {'Settings::Room' => { :window_count => ["Setting, 'Number of Windows' must be less than or equal to the 'Number of Windows' of its House."] }}

When validating against an association whose settings are namespaced, the current instance must inherit these settings from the association, with the namespace passed to the 'settings_inherit_via' method.

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 }

Ignoring a Namespace

In some cases, if you have validations on a namespace but that namespace is not needed your validations will always fail. You can get around this by defining a method on your model called available_setting_namespaces. The method should simply return an array of strings or symbols of the namespaces you want to apply in this case. This is useful if a particular instance of you model only needs settings from one or some namespaces.

class User < ActiveRecord::Base
  include SettingCrazy

  setting_namespace :user, :template => UserSettings
  setting_namespace :admin, :template => AdminSettings

  def available_setting_namespaces
    if admin?
      %w(user admin)
    else
      %(user)
    end
  end
end

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Added some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

settingcrazy's People

Contributors

echannel-leon avatar timboc avatar steffylicious avatar syntheticdave avatar

Watchers

 avatar  avatar Dan Draper avatar  avatar James Cloos avatar  avatar Bart Smyth avatar  avatar  avatar Tom McLoughlin avatar apurvamishra avatar  avatar  avatar  avatar  avatar

settingcrazy's Issues

Calling settings.inspect drops all unsaved setting values

Two ways to reproduce this issue:

a) in settingcrazy gem, change line #10 to:

before    { subject.foo = "bar"; subject.inspect; model.save! }

and 3 examples would fail, or

b) fires up rails console of DC app, do the following:

vi = Scenario::Campaign::VendorInstance.last # make sure this instance doesn't have setting values at this point
vi.settings.platform = '1'
vi.settings # => {} not {:platform => '1'}
vi.save! # setting for platform is not saved

I believe line #70 in settings_proxy.rb causes this issue.

Model using setting_namespace fails the validation if not all namespaced templates pass

This behavior makes sense at first glance, but lacks flexibility if the model doesn't need all namespaced setting templates to be validated.

One use case describes as follows:

  • A scenario can be published to multiple search engines, e.g Google and Yahoo.
  • The Scenario model has both google and yahoo as setting namespace.
  • However, an individual scenario record may only wish to be published to Google, in which case validating Yahoo setting_values is not necessary and what even worse is the required option would fail the validation, such as Yahoo budget is not in place.

Need a mechanism to allow skipping of validations

Validations are used when we use a setting template. We should add if/unless options to setting namespace and use_setting_template to control when validations are applied. Eg;

use_setting_template FooTemplate, :validate_if => Proc.new { ... }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.