Skip to content

Commit 7982297

Browse files
author
Jesús Burgos
committed
Allow definition of class name at setting level
Prior to this, only one class name could be used for all settings within a Rails model.
1 parent 659160e commit 7982297

File tree

6 files changed

+101
-47
lines changed

6 files changed

+101
-47
lines changed

lib/rails-settings/base.rb

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,13 @@ def self.included(base)
66
:as => :target,
77
:autosave => true,
88
:dependent => :delete_all,
9-
:class_name => self.setting_object_class_name
9+
:class_name => "RailsSettings::SettingObject"
1010

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

15-
if RailsSettings.can_protect_attributes?
16-
setting_objects.detect { |s| s.var == var.to_s } || setting_objects.build({ :var => var.to_s }, :without_protection => true)
17-
else
18-
setting_objects.detect { |s| s.var == var.to_s } || setting_objects.build(:var => var.to_s, :target => self)
19-
end
15+
fetch_settings_record(var).becomes(self.class.setting_keys[var][:class_name].constantize)
2016
end
2117

2218
def settings=(value)
@@ -36,11 +32,27 @@ def settings?(var=nil)
3632
end
3733

3834
def to_settings_hash
39-
settings_hash = self.class.default_settings.dup
40-
settings_hash.each do |var, vals|
41-
settings_hash[var] = settings_hash[var].merge(settings(var.to_sym).value)
35+
Hash[self.class.setting_keys.map do |key, options|
36+
[key, options[:default_value].merge(settings(key.to_sym).value)]
37+
end]
38+
end
39+
40+
private
41+
42+
def fetch_settings_record(var)
43+
find_settings_record(var) or build_settings_record(var)
44+
end
45+
46+
def find_settings_record(var)
47+
setting_objects.detect { |s| s.var == var.to_s }
48+
end
49+
50+
def build_settings_record(var)
51+
if RailsSettings.can_protect_attributes?
52+
setting_objects.build({ :var => var.to_s }, :without_protection => true)
53+
else
54+
setting_objects.build(:var => var.to_s, :target => self)
4255
end
43-
settings_hash
4456
end
4557
end
4658
end
Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,53 @@
11
module RailsSettings
22
class Configuration
33
def initialize(*args, &block)
4-
options = args.extract_options!
4+
@default_options = args.extract_options!
5+
validate_options @default_options
56
klass = args.shift
67
keys = args
78

89
raise ArgumentError unless klass
910

1011
@klass = klass
11-
@klass.class_attribute :default_settings, :setting_object_class_name
12-
@klass.default_settings = {}
13-
@klass.setting_object_class_name = options[:class_name] || 'RailsSettings::SettingObject'
12+
@klass.class_attribute :setting_keys
13+
@klass.setting_keys = {}
1414

1515
if block_given?
1616
yield(self)
1717
else
18-
keys.each do |k|
19-
key(k)
20-
end
18+
keys.each { |k| key(k) }
2119
end
2220

23-
raise ArgumentError.new('has_settings: No keys defined') if @klass.default_settings.blank?
21+
raise ArgumentError.new('has_settings: No keys defined') if @klass.setting_keys.empty?
2422
end
2523

2624
def key(name, options={})
25+
validate_name name
26+
validate_options options
27+
options = @default_options.merge(options)
28+
29+
@klass.setting_keys[name] = {
30+
:default_value => (options[:defaults] || {}).stringify_keys.freeze,
31+
:class_name => (options[:class_name] || 'RailsSettings::SettingObject')
32+
}
33+
end
34+
35+
private
36+
37+
def validate_name(name)
2738
raise ArgumentError.new("has_settings: Symbol expected, but got a #{name.class}") unless name.is_a?(Symbol)
28-
raise ArgumentError.new("has_settings: Option :defaults expected, but got #{options.keys.join(', ')}") unless options.blank? || (options.keys == [:defaults])
29-
@klass.default_settings[name] = (options[:defaults] || {}).stringify_keys.freeze
39+
end
40+
41+
def validate_options(options)
42+
valid_options = [:defaults, :class_name]
43+
options.each do |key, value|
44+
unless valid_options.include?(key)
45+
raise ArgumentError.new("has_settings: Invalid option #{key}")
46+
end
47+
end
48+
if options[:class_name] && !options[:class_name].constantize.ancestors.include?(RailsSettings::SettingObject)
49+
raise ArgumentError.new("has_settings: #{options[:class_name]} must be a subclass of RailsSettings::SettingObject")
50+
end
3051
end
3152
end
3253
end

lib/rails-settings/setting_object.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class SettingObject < ActiveRecord::Base
88
validate do
99
errors.add(:value, "Invalid setting value") unless value.is_a? Hash
1010

11-
unless _target_class.default_settings[var.to_sym]
11+
unless _target_class.setting_keys[var.to_sym]
1212
errors.add(:var, "#{var} is not defined!")
1313
end
1414
end
@@ -55,7 +55,7 @@ def sanitize_for_mass_assignment(attributes, role = nil)
5555
private
5656
def _get_value(name)
5757
if value[name].nil?
58-
_target_class.default_settings[var.to_sym][name]
58+
_target_class.setting_keys[var.to_sym][:default_value][name]
5959
else
6060
value[name]
6161
end
@@ -78,7 +78,7 @@ def _target_class
7878
end
7979

8080
def _setting?(method_name)
81-
_target_class.default_settings[var.to_sym].keys.include?(method_name.to_s)
81+
_target_class.setting_keys[var.to_sym][:default_value].keys.include?(method_name.to_s)
8282
end
8383
end
8484
end

spec/configuration_spec.rb

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,32 @@ class Dummy
88
it "should define single key" do
99
Configuration.new(Dummy, :dashboard)
1010

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

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

18-
expect(Dummy.default_settings).to eq({ :dashboard => {}, :calendar => {} })
19-
expect(Dummy.setting_object_class_name).to eq('RailsSettings::SettingObject')
18+
expect(Dummy.setting_keys[:dashboard][:default_value]).to eq({})
19+
expect(Dummy.setting_keys[:calendar][:default_value]).to eq({})
20+
expect(Dummy.setting_keys[:dashboard][:class_name]).to eq('RailsSettings::SettingObject')
21+
expect(Dummy.setting_keys[:calendar][:class_name]).to eq('RailsSettings::SettingObject')
2022
end
2123

2224
it "should define single key with class_name" do
23-
Configuration.new(Dummy, :dashboard, :class_name => 'MyClass')
24-
expect(Dummy.default_settings).to eq({ :dashboard => {} })
25-
expect(Dummy.setting_object_class_name).to eq('MyClass')
25+
Configuration.new(Dummy, :dashboard, :class_name => 'ProjectSettingObject')
26+
expect(Dummy.setting_keys[:dashboard][:default_value]).to eq({})
27+
expect(Dummy.setting_keys[:dashboard][:class_name]).to eq('ProjectSettingObject')
2628
end
2729

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

31-
expect(Dummy.default_settings).to eq({ :dashboard => {}, :calendar => {} })
32-
expect(Dummy.setting_object_class_name).to eq('MyClass')
33+
expect(Dummy.setting_keys[:dashboard][:default_value]).to eq({})
34+
expect(Dummy.setting_keys[:calendar][:default_value]).to eq({})
35+
expect(Dummy.setting_keys[:dashboard][:class_name]).to eq('ProjectSettingObject')
36+
expect(Dummy.setting_keys[:calendar][:class_name]).to eq('ProjectSettingObject')
3337
end
3438

3539
it "should define using block" do
@@ -38,8 +42,10 @@ class Dummy
3842
c.key :calendar
3943
end
4044

41-
expect(Dummy.default_settings).to eq({ :dashboard => {}, :calendar => {} })
42-
expect(Dummy.setting_object_class_name).to eq('RailsSettings::SettingObject')
45+
expect(Dummy.setting_keys[:dashboard][:default_value]).to eq({})
46+
expect(Dummy.setting_keys[:calendar][:default_value]).to eq({})
47+
expect(Dummy.setting_keys[:dashboard][:class_name]).to eq('RailsSettings::SettingObject')
48+
expect(Dummy.setting_keys[:calendar][:class_name]).to eq('RailsSettings::SettingObject')
4349
end
4450

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

51-
expect(Dummy.default_settings).to eq({ :dashboard => { 'theme' => 'red' }, :calendar => { 'scope' => 'all'} })
52-
expect(Dummy.setting_object_class_name).to eq('RailsSettings::SettingObject')
57+
expect(Dummy.setting_keys[:dashboard][:default_value]).to eq({ 'theme' => 'red' })
58+
expect(Dummy.setting_keys[:calendar][:default_value]).to eq({ 'scope' => 'all'})
59+
expect(Dummy.setting_keys[:dashboard][:class_name]).to eq('RailsSettings::SettingObject')
60+
expect(Dummy.setting_keys[:calendar][:class_name]).to eq('RailsSettings::SettingObject')
5361
end
5462

5563
it "should define using block and class_name" do
56-
Configuration.new(Dummy, :class_name => 'MyClass') do |c|
64+
Configuration.new(Dummy, :class_name => 'ProjectSettingObject') do |c|
5765
c.key :dashboard
5866
c.key :calendar
5967
end
6068

61-
expect(Dummy.default_settings).to eq({ :dashboard => {}, :calendar => {} })
62-
expect(Dummy.setting_object_class_name).to eq('MyClass')
69+
expect(Dummy.setting_keys[:dashboard][:default_value]).to eq({})
70+
expect(Dummy.setting_keys[:calendar][:default_value]).to eq({})
71+
expect(Dummy.setting_keys[:dashboard][:class_name]).to eq('ProjectSettingObject')
72+
expect(Dummy.setting_keys[:calendar][:class_name]).to eq('ProjectSettingObject')
6373
end
6474
end
6575

@@ -104,5 +114,13 @@ class Dummy
104114
end
105115
}.to raise_error(ArgumentError)
106116
end
117+
118+
it "should fail with an invalid settings object" do
119+
expect {
120+
Configuration.new(Dummy) do |c|
121+
c.key :dashboard, :class_name => "InvalidSettingObject"
122+
end
123+
}.to raise_error(ArgumentError)
124+
end
107125
end
108126
end

spec/settings_spec.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22

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

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

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

spec/spec_helper.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ class Account < ActiveRecord::Base
6060
has_settings :portal
6161
end
6262

63-
class Project < ActiveRecord::Base
64-
has_settings :info, :class_name => 'ProjectSettingObject'
63+
class InvalidSettingObject
6564
end
6665

6766
class ProjectSettingObject < RailsSettings::SettingObject
@@ -72,6 +71,10 @@ class ProjectSettingObject < RailsSettings::SettingObject
7271
end
7372
end
7473

74+
class Project < ActiveRecord::Base
75+
has_settings :info, :class_name => 'ProjectSettingObject'
76+
end
77+
7578
def setup_db
7679
ActiveRecord::Base.configurations = YAML.load_file(File.dirname(__FILE__) + '/database.yml')
7780
ActiveRecord::Base.establish_connection(:sqlite)

0 commit comments

Comments
 (0)