Skip to content

Commit

Permalink
Merge 58fe4a6 into 659160e
Browse files Browse the repository at this point in the history
  • Loading branch information
Jesus committed Jul 27, 2018
2 parents 659160e + 58fe4a6 commit c28709c
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 51 deletions.
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,6 @@ end
Every setting is handled by the class `RailsSettings::SettingObject`. You can use your own class, e.g. for validations:

```ruby
class Project < ActiveRecord::Base
has_settings :info, :class_name => 'ProjectSettingObject'
end

class ProjectSettingObject < RailsSettings::SettingObject
validate do
unless self.owner_name.present? && self.owner_name.is_a?(String)
Expand All @@ -65,6 +61,25 @@ class ProjectSettingObject < RailsSettings::SettingObject
end
```

Then you can use it like this:

```ruby
class Project < ActiveRecord::Base
has_settings :info, :class_name => 'ProjectSettingObject'
end
```

Or use it only on some of the settings:

```ruby
class Project < ActiveRecord::Base
has_settings do |s|
s.key :calendar # Will use the default RailsSettings::SettingObject
s.key :info, :class_name => 'ProjectSettingObject'
end
end
```

### Set settings

```ruby
Expand Down
42 changes: 31 additions & 11 deletions lib/rails-settings/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,13 @@ def self.included(base)
:as => :target,
:autosave => true,
:dependent => :delete_all,
:class_name => self.setting_object_class_name
:class_name => "RailsSettings::SettingObject"

def settings(var)
raise ArgumentError unless var.is_a?(Symbol)
raise ArgumentError.new("Unknown key: #{var}") unless self.class.default_settings[var]
raise ArgumentError.new("Unknown key: #{var}") unless self.class.setting_keys[var]

if RailsSettings.can_protect_attributes?
setting_objects.detect { |s| s.var == var.to_s } || setting_objects.build({ :var => var.to_s }, :without_protection => true)
else
setting_objects.detect { |s| s.var == var.to_s } || setting_objects.build(:var => var.to_s, :target => self)
end
fetch_settings_record(var)
end

def settings=(value)
Expand All @@ -36,11 +32,35 @@ def settings?(var=nil)
end

def to_settings_hash
settings_hash = self.class.default_settings.dup
settings_hash.each do |var, vals|
settings_hash[var] = settings_hash[var].merge(settings(var.to_sym).value)
Hash[self.class.setting_keys.map do |key, options|
[key, options[:default_value].merge(settings(key.to_sym).value)]
end]
end

private

def fetch_settings_record(var)
find_settings_record(var) or build_settings_record(var)
end

def find_settings_record(var)
setting_objects
.select { |s| s.var == var.to_s }
.map { |s| s.becomes self.class.setting_keys[var][:class_name].constantize }
.first
end

def build_settings_record(var)
build_args =
if RailsSettings.can_protect_attributes?
[{ :var => var.to_s }, :without_protection => true]
else
[:var => var.to_s, :target => self]
end

setting_objects.build(*build_args) do |record|
record.becomes self.class.setting_keys[var][:class_name].constantize
end
settings_hash
end
end
end
Expand Down
41 changes: 31 additions & 10 deletions lib/rails-settings/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,32 +1,53 @@
module RailsSettings
class Configuration
def initialize(*args, &block)
options = args.extract_options!
@default_options = args.extract_options!
validate_options @default_options
klass = args.shift
keys = args

raise ArgumentError unless klass

@klass = klass
@klass.class_attribute :default_settings, :setting_object_class_name
@klass.default_settings = {}
@klass.setting_object_class_name = options[:class_name] || 'RailsSettings::SettingObject'
@klass.class_attribute :setting_keys
@klass.setting_keys = {}

if block_given?
yield(self)
else
keys.each do |k|
key(k)
end
keys.each { |k| key(k) }
end

raise ArgumentError.new('has_settings: No keys defined') if @klass.default_settings.blank?
raise ArgumentError.new('has_settings: No keys defined') if @klass.setting_keys.empty?
end

def key(name, options={})
validate_name name
validate_options options
options = @default_options.merge(options)

@klass.setting_keys[name] = {
:default_value => (options[:defaults] || {}).stringify_keys.freeze,
:class_name => (options[:class_name] || 'RailsSettings::SettingObject')
}
end

private

def validate_name(name)
raise ArgumentError.new("has_settings: Symbol expected, but got a #{name.class}") unless name.is_a?(Symbol)
raise ArgumentError.new("has_settings: Option :defaults expected, but got #{options.keys.join(', ')}") unless options.blank? || (options.keys == [:defaults])
@klass.default_settings[name] = (options[:defaults] || {}).stringify_keys.freeze
end

def validate_options(options)
valid_options = [:defaults, :class_name]
options.each do |key, value|
unless valid_options.include?(key)
raise ArgumentError.new("has_settings: Invalid option #{key}")
end
end
if options[:class_name] && !options[:class_name].constantize.ancestors.include?(RailsSettings::SettingObject)
raise ArgumentError.new("has_settings: #{options[:class_name]} must be a subclass of RailsSettings::SettingObject")
end
end
end
end
6 changes: 3 additions & 3 deletions lib/rails-settings/setting_object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class SettingObject < ActiveRecord::Base
validate do
errors.add(:value, "Invalid setting value") unless value.is_a? Hash

unless _target_class.default_settings[var.to_sym]
unless _target_class.setting_keys[var.to_sym]
errors.add(:var, "#{var} is not defined!")
end
end
Expand Down Expand Up @@ -55,7 +55,7 @@ def sanitize_for_mass_assignment(attributes, role = nil)
private
def _get_value(name)
if value[name].nil?
_target_class.default_settings[var.to_sym][name]
_target_class.setting_keys[var.to_sym][:default_value][name]
else
value[name]
end
Expand All @@ -78,7 +78,7 @@ def _target_class
end

def _setting?(method_name)
_target_class.default_settings[var.to_sym].keys.include?(method_name.to_s)
_target_class.setting_keys[var.to_sym][:default_value].keys.include?(method_name.to_s)
end
end
end
52 changes: 35 additions & 17 deletions spec/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,32 @@ class Dummy
it "should define single key" do
Configuration.new(Dummy, :dashboard)

expect(Dummy.default_settings).to eq({ :dashboard => {} })
expect(Dummy.setting_object_class_name).to eq('RailsSettings::SettingObject')
expect(Dummy.setting_keys[:dashboard][:default_value]).to eq({})
expect(Dummy.setting_keys[:dashboard][:class_name]).to eq('RailsSettings::SettingObject')
end

it "should define multiple keys" do
Configuration.new(Dummy, :dashboard, :calendar)

expect(Dummy.default_settings).to eq({ :dashboard => {}, :calendar => {} })
expect(Dummy.setting_object_class_name).to eq('RailsSettings::SettingObject')
expect(Dummy.setting_keys[:dashboard][:default_value]).to eq({})
expect(Dummy.setting_keys[:calendar][:default_value]).to eq({})
expect(Dummy.setting_keys[:dashboard][:class_name]).to eq('RailsSettings::SettingObject')
expect(Dummy.setting_keys[:calendar][:class_name]).to eq('RailsSettings::SettingObject')
end

it "should define single key with class_name" do
Configuration.new(Dummy, :dashboard, :class_name => 'MyClass')
expect(Dummy.default_settings).to eq({ :dashboard => {} })
expect(Dummy.setting_object_class_name).to eq('MyClass')
Configuration.new(Dummy, :dashboard, :class_name => 'ProjectSettingObject')
expect(Dummy.setting_keys[:dashboard][:default_value]).to eq({})
expect(Dummy.setting_keys[:dashboard][:class_name]).to eq('ProjectSettingObject')
end

it "should define multiple keys with class_name" do
Configuration.new(Dummy, :dashboard, :calendar, :class_name => 'MyClass')
Configuration.new(Dummy, :dashboard, :calendar, :class_name => 'ProjectSettingObject')

expect(Dummy.default_settings).to eq({ :dashboard => {}, :calendar => {} })
expect(Dummy.setting_object_class_name).to eq('MyClass')
expect(Dummy.setting_keys[:dashboard][:default_value]).to eq({})
expect(Dummy.setting_keys[:calendar][:default_value]).to eq({})
expect(Dummy.setting_keys[:dashboard][:class_name]).to eq('ProjectSettingObject')
expect(Dummy.setting_keys[:calendar][:class_name]).to eq('ProjectSettingObject')
end

it "should define using block" do
Expand All @@ -38,8 +42,10 @@ class Dummy
c.key :calendar
end

expect(Dummy.default_settings).to eq({ :dashboard => {}, :calendar => {} })
expect(Dummy.setting_object_class_name).to eq('RailsSettings::SettingObject')
expect(Dummy.setting_keys[:dashboard][:default_value]).to eq({})
expect(Dummy.setting_keys[:calendar][:default_value]).to eq({})
expect(Dummy.setting_keys[:dashboard][:class_name]).to eq('RailsSettings::SettingObject')
expect(Dummy.setting_keys[:calendar][:class_name]).to eq('RailsSettings::SettingObject')
end

it "should define using block with defaults" do
Expand All @@ -48,18 +54,22 @@ class Dummy
c.key :calendar, :defaults => { :scope => 'all' }
end

expect(Dummy.default_settings).to eq({ :dashboard => { 'theme' => 'red' }, :calendar => { 'scope' => 'all'} })
expect(Dummy.setting_object_class_name).to eq('RailsSettings::SettingObject')
expect(Dummy.setting_keys[:dashboard][:default_value]).to eq({ 'theme' => 'red' })
expect(Dummy.setting_keys[:calendar][:default_value]).to eq({ 'scope' => 'all'})
expect(Dummy.setting_keys[:dashboard][:class_name]).to eq('RailsSettings::SettingObject')
expect(Dummy.setting_keys[:calendar][:class_name]).to eq('RailsSettings::SettingObject')
end

it "should define using block and class_name" do
Configuration.new(Dummy, :class_name => 'MyClass') do |c|
Configuration.new(Dummy, :class_name => 'ProjectSettingObject') do |c|
c.key :dashboard
c.key :calendar
end

expect(Dummy.default_settings).to eq({ :dashboard => {}, :calendar => {} })
expect(Dummy.setting_object_class_name).to eq('MyClass')
expect(Dummy.setting_keys[:dashboard][:default_value]).to eq({})
expect(Dummy.setting_keys[:calendar][:default_value]).to eq({})
expect(Dummy.setting_keys[:dashboard][:class_name]).to eq('ProjectSettingObject')
expect(Dummy.setting_keys[:calendar][:class_name]).to eq('ProjectSettingObject')
end
end

Expand Down Expand Up @@ -104,5 +114,13 @@ class Dummy
end
}.to raise_error(ArgumentError)
end

it "should fail with an invalid settings object" do
expect {
Configuration.new(Dummy) do |c|
c.key :dashboard, :class_name => "InvalidSettingObject"
end
}.to raise_error(ArgumentError)
end
end
end
8 changes: 4 additions & 4 deletions spec/settings_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

describe "Defaults" do
it "should be stored for simple class" do
expect(Account.default_settings).to eq(:portal => {})
expect(Account.setting_keys[:portal][:default_value]).to eq({})
end

it "should be stored for parent class" do
expect(User.default_settings).to eq(:dashboard => { 'theme' => 'blue', 'view' => 'monthly', 'filter' => true },
:calendar => { 'scope' => 'company'})
expect(User.setting_keys[:dashboard][:default_value]).to eq({ 'theme' => 'blue', 'view' => 'monthly', 'filter' => true })
expect(User.setting_keys[:calendar][:default_value]).to eq({ 'scope' => 'company'})
end

it "should be stored for child class" do
expect(GuestUser.default_settings).to eq(:dashboard => { 'theme' => 'red', 'view' => 'monthly', 'filter' => true })
expect(GuestUser.setting_keys[:dashboard][:default_value]).to eq({ 'theme' => 'red', 'view' => 'monthly', 'filter' => true })
end
end

Expand Down
7 changes: 5 additions & 2 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ class Account < ActiveRecord::Base
has_settings :portal
end

class Project < ActiveRecord::Base
has_settings :info, :class_name => 'ProjectSettingObject'
class InvalidSettingObject
end

class ProjectSettingObject < RailsSettings::SettingObject
Expand All @@ -72,6 +71,10 @@ class ProjectSettingObject < RailsSettings::SettingObject
end
end

class Project < ActiveRecord::Base
has_settings :info, :class_name => 'ProjectSettingObject'
end

def setup_db
ActiveRecord::Base.configurations = YAML.load_file(File.dirname(__FILE__) + '/database.yml')
ActiveRecord::Base.establish_connection(:sqlite)
Expand Down

0 comments on commit c28709c

Please sign in to comment.