settingcrazy 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +245 -0
- data/Rakefile +2 -0
- data/TODO +10 -0
- data/db/safeattributes.db +0 -0
- data/lib/generators/settingcrazy/setting_values_migration/setting_values_migration_generator.rb +18 -0
- data/lib/generators/settingcrazy/setting_values_migration/templates/migration.rb +15 -0
- data/lib/settingcrazy.rb +23 -0
- data/lib/settingcrazy/class_methods.rb +32 -0
- data/lib/settingcrazy/inheritor.rb +31 -0
- data/lib/settingcrazy/instance_methods.rb +20 -0
- data/lib/settingcrazy/namespace.rb +14 -0
- data/lib/settingcrazy/namespaced_settings_proxy.rb +18 -0
- data/lib/settingcrazy/setting_value.rb +11 -0
- data/lib/settingcrazy/settings_proxy.rb +107 -0
- data/lib/settingcrazy/settings_validator.rb +43 -0
- data/lib/settingcrazy/template.rb +2 -0
- data/lib/settingcrazy/template/base.rb +66 -0
- data/lib/settingcrazy/template/enum.rb +19 -0
- data/lib/settingcrazy/version.rb +3 -0
- data/settingcrazy.gemspec +21 -0
- data/spec/inheritance_spec.rb +76 -0
- data/spec/mass_assignment_spec.rb +69 -0
- data/spec/namespaced_settings_proxy_spec.rb +70 -0
- data/spec/settingcrazy_spec.rb +78 -0
- data/spec/settings_proxy_spec.rb +55 -0
- data/spec/settings_validator_spec.rb +121 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/models.rb +10 -0
- data/spec/support/models/campaign.rb +12 -0
- data/spec/support/models/case.rb +11 -0
- data/spec/support/models/clever_campaign.rb +13 -0
- data/spec/support/models/duck.rb +13 -0
- data/spec/support/models/farm.rb +10 -0
- data/spec/support/models/note.rb +12 -0
- data/spec/support/models/scenario.rb +13 -0
- data/spec/support/models/setting_value.rb +8 -0
- data/spec/support/models/templated_campaign.rb +11 -0
- data/spec/support/models/templated_scenario.rb +11 -0
- data/spec/support/models/vendor_instance.rb +9 -0
- data/spec/support/templates.rb +3 -0
- data/spec/support/templates/example_campaign_template.rb +24 -0
- data/spec/support/templates/example_template.rb +12 -0
- metadata +161 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.3@settingcrazy --create
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Dan Draper
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
# Settingcrazy
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'settingcrazy'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install settingcrazy
|
18
|
+
|
19
|
+
Finally, create a migration and run:
|
20
|
+
|
21
|
+
$ rails g settingcrazy:setting_values_migration
|
22
|
+
$ rake db:migrate
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
To use simply include the SettingCrazy module in any ActiveRecord model that you want to have settings:
|
27
|
+
|
28
|
+
class User < ActiveRecord::Base
|
29
|
+
include SettingCrazy
|
30
|
+
end
|
31
|
+
|
32
|
+
Your model will now have a settings method which you can use to get and set values.
|
33
|
+
|
34
|
+
user = User.first
|
35
|
+
user.settings => {}
|
36
|
+
user.settings.my_setting = "foo"
|
37
|
+
user.settings.my_setting => "foo"
|
38
|
+
user.settings => { "my_setting" => "foo" }
|
39
|
+
|
40
|
+
To persist, call save or save! on the parent. Eg;
|
41
|
+
|
42
|
+
user.save
|
43
|
+
|
44
|
+
### Serializable Values
|
45
|
+
|
46
|
+
TODO
|
47
|
+
|
48
|
+
### Setting Inheritance
|
49
|
+
|
50
|
+
Your settings can inherit from the settings of a parent.
|
51
|
+
|
52
|
+
class House < ActiveRecord::Base
|
53
|
+
include SettingCrazy
|
54
|
+
has_many :rooms
|
55
|
+
end
|
56
|
+
|
57
|
+
class Room < ActiveRecord::Base
|
58
|
+
include SettingCrazy
|
59
|
+
belongs_to :house
|
60
|
+
settings_inherit_via :house
|
61
|
+
end
|
62
|
+
|
63
|
+
house = House.create(...)
|
64
|
+
house.settings.color = "blue"
|
65
|
+
house.save!
|
66
|
+
|
67
|
+
room = house.rooms.create
|
68
|
+
room.settings.color => "blue"
|
69
|
+
|
70
|
+
### Setting Templates
|
71
|
+
|
72
|
+
Available settings are specified through the use of setting templates. These tell settingcrazy the options that exist, and what possible values they can take.
|
73
|
+
|
74
|
+
class House < ActiveRecord::Base
|
75
|
+
include SettingCrazy
|
76
|
+
has_many :rooms
|
77
|
+
use_setting_template Settings::House
|
78
|
+
end
|
79
|
+
|
80
|
+
use_setting_template can also be passed a block, in case your model needs to use different templates, depending on the record itself.
|
81
|
+
|
82
|
+
class Room < ActiveRecord::Base
|
83
|
+
include SettingCrazy
|
84
|
+
belongs_to :house
|
85
|
+
settings_inherit_via :house
|
86
|
+
|
87
|
+
# Use the template associated with the room type of this room
|
88
|
+
use_setting_template do |record|
|
89
|
+
record.room_type.template
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
####Enums
|
94
|
+
|
95
|
+
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.
|
96
|
+
The basic structure of an enum is:
|
97
|
+
|
98
|
+
enum :key, 'name', validation_options do
|
99
|
+
value 'value', 'key'
|
100
|
+
...
|
101
|
+
end
|
102
|
+
|
103
|
+
class Settings::House < SettingCrazy::Template::Base
|
104
|
+
enum :bedroom_count, 'Room Count', {} do
|
105
|
+
value 1, 'One'
|
106
|
+
value 2, 'Two'
|
107
|
+
value 3, 'Three'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
house = House.create(...)
|
112
|
+
house.settings.bedroom_count => nil
|
113
|
+
house.settings.bedroom_count = 1
|
114
|
+
house.save!
|
115
|
+
|
116
|
+
####Validation
|
117
|
+
|
118
|
+
Note: Validation does not work with namespaces yet.
|
119
|
+
|
120
|
+
Settingcrazy always validates whether the value for an option is defined in the enums. There are a number of additional validation options available.
|
121
|
+
|
122
|
+
multiple (boolean) - Whether it is valid to save more than one entry for a single key
|
123
|
+
required (boolean) - Whether a value must be set for this enum
|
124
|
+
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
|
125
|
+
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.
|
126
|
+
|
127
|
+
class Settings::House < SettingCrazy::Template::Base
|
128
|
+
enum :is_furnished, 'Furnished', { multiple: false, required: true } do
|
129
|
+
value false, 'Not Furnished'
|
130
|
+
value true, 'Is Furnished'
|
131
|
+
end
|
132
|
+
|
133
|
+
enum :has_dining_table, 'Has Dining Table', { dependent: { is_furnished: true } } do
|
134
|
+
value false, 'No Dining Table'
|
135
|
+
value true, 'Dining Table'
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
house = House.create(...)
|
140
|
+
house.valid? => false
|
141
|
+
house.errors => { :is_furnished => ["Setting, 'Furnished', is required"] }
|
142
|
+
house.settings.is_furnished = false
|
143
|
+
house.valid? => true
|
144
|
+
house.settings.has_dining_table = false
|
145
|
+
house.valid? => false ("'Has Dining Table' can only be specified if 'Furnished' is set to 'Not Furnished'")
|
146
|
+
house.settings.is_furnished = true
|
147
|
+
house.valid? => true
|
148
|
+
house.settings.has_dining_table = 3
|
149
|
+
house.valid? => false ("'3' is not a valid setting for 'Has Dining Table'")
|
150
|
+
|
151
|
+
|
152
|
+
#####Defaults
|
153
|
+
|
154
|
+
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.
|
155
|
+
|
156
|
+
class Settings::House < SettingCrazy::Template::Base
|
157
|
+
# Assuming the enums for all these settings are defined below
|
158
|
+
def self.defaults
|
159
|
+
{
|
160
|
+
bedroom_count: 2,
|
161
|
+
is_furnished: false
|
162
|
+
}
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
house = House.create(...)
|
167
|
+
house.settings => {}
|
168
|
+
house.settings.bedroom_count => 2
|
169
|
+
house.settings.bedroom_count = 3
|
170
|
+
house.settings => { :bedroom_count => 3 }
|
171
|
+
house.is_furnished => false
|
172
|
+
house.has_dining_table => nil
|
173
|
+
|
174
|
+
### Mass Assignment and Usage in Forms
|
175
|
+
|
176
|
+
You can easily bulk set settings using a hash.
|
177
|
+
|
178
|
+
house.settings = { :foo => "bar", :fruit => "apple" }
|
179
|
+
|
180
|
+
This extends to mass assignment:
|
181
|
+
|
182
|
+
house.attributes = {
|
183
|
+
:settings => { :foo => "bar", :fruit => "apple" }
|
184
|
+
}
|
185
|
+
|
186
|
+
If using in a form you can use fields for (eg in Haml):
|
187
|
+
|
188
|
+
= form.fields_for :settings, @house.settings do |setting|
|
189
|
+
= setting.text_field :foo
|
190
|
+
|
191
|
+
### Setting Namespaces
|
192
|
+
|
193
|
+
If you have a model that needs settings to be divided in some way (perhaps by functional area) you can use a namespace.
|
194
|
+
|
195
|
+
class Scenario < ActiveRecord::Base
|
196
|
+
include SettingCrazy
|
197
|
+
|
198
|
+
setting_namespace :weekdays
|
199
|
+
setting_namespace :weekends
|
200
|
+
end
|
201
|
+
|
202
|
+
Now you can access your settings like so:
|
203
|
+
|
204
|
+
scenario = Scenario.find(...)
|
205
|
+
scenario.settings.weekends.foo = "bar"
|
206
|
+
scenario.settings.weekends.foo => "bar"
|
207
|
+
scenario.settings.weekdays.foo => nil
|
208
|
+
|
209
|
+
scenario.settings.weekends => { :foo => "bar" }
|
210
|
+
|
211
|
+
Not providing a namespace still works and will access all settings:
|
212
|
+
|
213
|
+
scenario.settings.foo => "bar"
|
214
|
+
|
215
|
+
Setting namespaces work with bulk setting and mass assignment too:
|
216
|
+
|
217
|
+
scenario.settings.weekends = { :foo => "bar" }
|
218
|
+
|
219
|
+
scenario.attributes = {
|
220
|
+
:settings => {
|
221
|
+
:weekends => {
|
222
|
+
:foo => "bar
|
223
|
+
},
|
224
|
+
},
|
225
|
+
}
|
226
|
+
|
227
|
+
You can also set templates to work per namespace:
|
228
|
+
|
229
|
+
setting_namespace :weekdays, :template => WeekDayTemplate
|
230
|
+
|
231
|
+
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.
|
232
|
+
|
233
|
+
settings_inherit_via :house, :namespace => :a_namespace
|
234
|
+
|
235
|
+
Or, you can even use a proc
|
236
|
+
|
237
|
+
settings_inherit_via :house, :namespace => Proc.new { |room| room.parent_setting_namespace }
|
238
|
+
|
239
|
+
## Contributing
|
240
|
+
|
241
|
+
1. Fork it
|
242
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
243
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
244
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
245
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/TODO
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
* think some more about saving and record consistency. Settings don't get saved until we save the parent record. That may be OK but calling a getter should perhaps return the correct (albeit unsaved) value?
|
2
|
+
- implement new_record? changed? (maybe even changes) methods
|
3
|
+
- active record makes this hard to do in current form. We would actually need to build a second setting value when we change something and then replace the entire collection when we save the owner
|
4
|
+
* RDoc
|
5
|
+
* Serialize only for arrays (reject blanks too) - may need to add a serialize column
|
6
|
+
* Template Validators
|
7
|
+
* Template docs
|
8
|
+
* More validations in validator
|
9
|
+
- an enum_value is only available if another enum_value matches certain criteria, e.g, delivery_method can be set to 'ACCELERATED' only if bid_setting is 'manualCPC'
|
10
|
+
- validates format, e.g daily_budget and initial_key_cpc can only be decimals
|
Binary file
|
data/lib/generators/settingcrazy/setting_values_migration/setting_values_migration_generator.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rails'
|
2
|
+
|
3
|
+
module Settingcrazy
|
4
|
+
module Generators
|
5
|
+
class SettingValuesMigrationGenerator < Rails::Generators::Base
|
6
|
+
include Rails::Generators::Migration
|
7
|
+
source_root File.expand_path('../../../../../lib/generators/settingcrazy/setting_values_migration/templates/', __FILE__)
|
8
|
+
|
9
|
+
def self.next_migration_number(path)
|
10
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_setting_values_migration_file
|
14
|
+
migration_template 'migration.rb', 'db/migrate/create_settings_values'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateSettingsValues < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :setting_values do |t|
|
4
|
+
t.string :value
|
5
|
+
t.string :key
|
6
|
+
t.string :namespace
|
7
|
+
t.references :settable, :polymorphic => true
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.down
|
13
|
+
drop_table :setting_values
|
14
|
+
end
|
15
|
+
end
|
data/lib/settingcrazy.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
autoload :ActiveRecord, 'active_record'
|
2
|
+
|
3
|
+
require "settingcrazy/version"
|
4
|
+
require "settingcrazy/namespace"
|
5
|
+
require "settingcrazy/settings_proxy"
|
6
|
+
require "settingcrazy/namespaced_settings_proxy"
|
7
|
+
require "settingcrazy/setting_value"
|
8
|
+
require "settingcrazy/class_methods"
|
9
|
+
require "settingcrazy/instance_methods"
|
10
|
+
require "settingcrazy/inheritor"
|
11
|
+
require "settingcrazy/template"
|
12
|
+
require "settingcrazy/settings_validator"
|
13
|
+
|
14
|
+
module SettingCrazy
|
15
|
+
def self.included(base)
|
16
|
+
base.class_eval <<-EVAL
|
17
|
+
has_many :setting_values, :class_name => "SettingCrazy::SettingValue", :as => :settable, :autosave => true
|
18
|
+
validates_with SettingsValidator
|
19
|
+
EVAL
|
20
|
+
base.extend ClassMethods
|
21
|
+
base.send(:include, InstanceMethods)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module SettingCrazy
|
2
|
+
module ClassMethods
|
3
|
+
def use_setting_template(template = nil, &block)
|
4
|
+
@template = template || block
|
5
|
+
end
|
6
|
+
|
7
|
+
def settings_inherit_via(reflection, options = {})
|
8
|
+
@inheritor = Inheritor.new(reflection, options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def setting_namespace(name, options = {})
|
12
|
+
@setting_namespaces ||= {}
|
13
|
+
@setting_namespaces[name.to_sym] = Namespace.new(name, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def _inheritor
|
17
|
+
@inheritor
|
18
|
+
end
|
19
|
+
|
20
|
+
def _setting_namespaces
|
21
|
+
@setting_namespaces
|
22
|
+
end
|
23
|
+
|
24
|
+
def setting_template(record)
|
25
|
+
case @template
|
26
|
+
when String then @template.constantize
|
27
|
+
when Class then @template
|
28
|
+
when Proc then @template.call(record)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
module SettingCrazy
|
3
|
+
class Inheritor
|
4
|
+
def initialize(via, options = {})
|
5
|
+
# TODO: Do a reflection look up to make sure its valid
|
6
|
+
@via = via
|
7
|
+
@parent_namespace = options[:namespace]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns the appropriate parent settings
|
11
|
+
def parent_settings_for(model)
|
12
|
+
return nil if model.send(@via).blank?
|
13
|
+
association = model.send(@via)
|
14
|
+
if pn = parent_namespace(model)
|
15
|
+
association.settings.send(pn)
|
16
|
+
else
|
17
|
+
association.settings
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
def parent_namespace(model)
|
23
|
+
case @parent_namespace
|
24
|
+
when nil then nil
|
25
|
+
when String, Symbol then @parent_namespace
|
26
|
+
when Proc
|
27
|
+
@parent_namespace.call(model)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
module SettingCrazy
|
3
|
+
module InstanceMethods
|
4
|
+
def assign_attributes(values, options = {})
|
5
|
+
if values.has_key?(:settings)
|
6
|
+
self.settings = values.delete(:settings)
|
7
|
+
end
|
8
|
+
|
9
|
+
super(values, options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def settings
|
13
|
+
@settings ||= SettingsProxy.new(self, self.class.setting_template(self))
|
14
|
+
end
|
15
|
+
|
16
|
+
def settings=(attributes)
|
17
|
+
settings.bulk_assign(attributes)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|