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.
Files changed (47) hide show
  1. data/.gitignore +17 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +22 -0
  5. data/README.md +245 -0
  6. data/Rakefile +2 -0
  7. data/TODO +10 -0
  8. data/db/safeattributes.db +0 -0
  9. data/lib/generators/settingcrazy/setting_values_migration/setting_values_migration_generator.rb +18 -0
  10. data/lib/generators/settingcrazy/setting_values_migration/templates/migration.rb +15 -0
  11. data/lib/settingcrazy.rb +23 -0
  12. data/lib/settingcrazy/class_methods.rb +32 -0
  13. data/lib/settingcrazy/inheritor.rb +31 -0
  14. data/lib/settingcrazy/instance_methods.rb +20 -0
  15. data/lib/settingcrazy/namespace.rb +14 -0
  16. data/lib/settingcrazy/namespaced_settings_proxy.rb +18 -0
  17. data/lib/settingcrazy/setting_value.rb +11 -0
  18. data/lib/settingcrazy/settings_proxy.rb +107 -0
  19. data/lib/settingcrazy/settings_validator.rb +43 -0
  20. data/lib/settingcrazy/template.rb +2 -0
  21. data/lib/settingcrazy/template/base.rb +66 -0
  22. data/lib/settingcrazy/template/enum.rb +19 -0
  23. data/lib/settingcrazy/version.rb +3 -0
  24. data/settingcrazy.gemspec +21 -0
  25. data/spec/inheritance_spec.rb +76 -0
  26. data/spec/mass_assignment_spec.rb +69 -0
  27. data/spec/namespaced_settings_proxy_spec.rb +70 -0
  28. data/spec/settingcrazy_spec.rb +78 -0
  29. data/spec/settings_proxy_spec.rb +55 -0
  30. data/spec/settings_validator_spec.rb +121 -0
  31. data/spec/spec_helper.rb +8 -0
  32. data/spec/support/models.rb +10 -0
  33. data/spec/support/models/campaign.rb +12 -0
  34. data/spec/support/models/case.rb +11 -0
  35. data/spec/support/models/clever_campaign.rb +13 -0
  36. data/spec/support/models/duck.rb +13 -0
  37. data/spec/support/models/farm.rb +10 -0
  38. data/spec/support/models/note.rb +12 -0
  39. data/spec/support/models/scenario.rb +13 -0
  40. data/spec/support/models/setting_value.rb +8 -0
  41. data/spec/support/models/templated_campaign.rb +11 -0
  42. data/spec/support/models/templated_scenario.rb +11 -0
  43. data/spec/support/models/vendor_instance.rb +9 -0
  44. data/spec/support/templates.rb +3 -0
  45. data/spec/support/templates/example_campaign_template.rb +24 -0
  46. data/spec/support/templates/example_template.rb +12 -0
  47. metadata +161 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@settingcrazy --create
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in settingcrazy.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
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
@@ -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
@@ -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