store_configurable 3.2.5 → 4.0.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d61bb9df70dd5012cf9cde13b68df65aad18c70c
4
+ data.tar.gz: a69008e6cc5a32f28a37498f26b840c2c0d9c8ca
5
+ SHA512:
6
+ metadata.gz: f3c8818369b854dd0f183fc921869726057ecbdb73f0a753535e3850e1a058080f45160e971138aa06b7dc81420e2f27575feb67d4abd10259a52d4d5dab991b
7
+ data.tar.gz: 2695771a7c648d5369c3ff7754a42de79036d2547802bd403ae96f0b3f72bb701178d5d177eb7386c99e0bcf88a1d3a14a9c7080e4d4ae7e808a6cc8bd8666cd
@@ -1,4 +1,4 @@
1
1
  rvm:
2
- - 1.8.7
3
2
  - 1.9.3
4
- - ree
3
+ - 2.0.0
4
+ - 2.1.0
@@ -0,0 +1,8 @@
1
+
2
+ appraise 'activerecord40' do
3
+ gem 'activerecord', '~> 4.0.0'
4
+ end
5
+
6
+ appraise 'activerecord41' do
7
+ gem 'activerecord', '~> 4.1.0'
8
+ end
data/Gemfile CHANGED
@@ -1,2 +1,2 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
  gemspec
data/README.md CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  # StoreConfigurable
3
2
 
4
3
  A zero-configuration recursive Hash for storing a tree of options in a serialized ActiveRecord column. Includes self aware hooks that delegate dirty/changed state to your configs owner. Read my article [*A Lesson In Recursion In Ruby*](http://metaskills.net/2012/03/12/store-configurable-a-lesson-in-recursion-in-ruby/) if you are interested in how this library works.
@@ -12,9 +11,11 @@ A zero-configuration recursive Hash for storing a tree of options in a serialize
12
11
  Install the gem with bundler. We follow a semantic versioning format that tracks ActiveRecord's minor version. So this means to use the latest 3.2.x version of StoreConfigurable with any ActiveRecord 3.2 version.
13
12
 
14
13
  ```ruby
15
- gem 'store_configurable', '~> 3.2.0'
14
+ gem 'store_configurable', '~> 4.0.0'
16
15
  ```
17
16
 
17
+ Our `4.0.x` versions target both Rails 4.0 and 4.1 only.
18
+
18
19
 
19
20
  ## Setup
20
21
 
@@ -125,8 +126,25 @@ StoreConfigurable persists your configuration data in YAML format to the `_confi
125
126
  * [StoreField](https://github.com/kenn/store_field) - Similar approach but no dirty tracking and still requires manual key configs.
126
127
 
127
128
 
129
+ ## Contributing
130
+
131
+ StoreConfigurable is fully tested with ActiveRecord 3.2 to 4.0 and upward. If you detect a problem, open up a github issue or fork the repo and help out. After you fork or clone the repository, the following commands will get you up and running on the test suite.
132
+
133
+ ```shell
134
+ $ bundle install
135
+ $ bundle exec appraisal update
136
+ $ bundle exec appraisal rake test
137
+ ```
138
+
139
+ We use the [appraisal](https://github.com/thoughtbot/appraisal) gem from Thoughtbot to help us generate the individual gemfiles for each ActiveSupport version and to run the tests locally against each generated Gemfile. The `rake appraisal test` command actually runs our test suite against all ActiveRecord versions in our `Appraisal` file. If you want to run the tests for a specific ActiveRecord version, use `rake -T` for a list. For example, the following command will run the tests for Rails 3.2 only.
140
+
141
+ ```shell
142
+ $ bundle exec appraisal activerecord40 rake test
143
+ ```
144
+
145
+
128
146
  ## License
129
147
 
130
148
  * Released under the MIT license thanks to Decisiv, Inc.
131
- * Copyright (c) 2011 Ken Collins
149
+ * Copyright (c) 2014 Ken Collins
132
150
 
data/Rakefile CHANGED
@@ -1,13 +1,9 @@
1
- require 'bundler'
1
+ require 'bundler/gem_tasks'
2
2
  require 'rake/testtask'
3
3
 
4
- Bundler::GemHelper.install_tasks
5
-
6
- desc 'Test the StoreConfigurable gem.'
7
- Rake::TestTask.new do |t|
4
+ Rake::TestTask.new do |t|
8
5
  t.libs = ['lib','test']
9
- t.test_files = Dir.glob("test/**/*_test.rb").sort
6
+ t.test_files = Dir.glob('test/**/*_test.rb').sort
10
7
  t.verbose = true
11
8
  end
12
9
 
13
- task :default => [:test]
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 3.2.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,44 @@
1
+ PATH
2
+ remote: ../
3
+ specs:
4
+ store_configurable (3.2.5)
5
+ activerecord (>= 3.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (3.2.16)
11
+ activesupport (= 3.2.16)
12
+ builder (~> 3.0.0)
13
+ activerecord (3.2.16)
14
+ activemodel (= 3.2.16)
15
+ activesupport (= 3.2.16)
16
+ arel (~> 3.0.2)
17
+ tzinfo (~> 0.3.29)
18
+ activesupport (3.2.16)
19
+ i18n (~> 0.6, >= 0.6.4)
20
+ multi_json (~> 1.0)
21
+ appraisal (1.0.0)
22
+ bundler
23
+ rake
24
+ thor (>= 0.14.0)
25
+ arel (3.0.3)
26
+ builder (3.0.4)
27
+ i18n (0.6.9)
28
+ minitest (5.3.3)
29
+ multi_json (1.9.2)
30
+ rake (10.3.0)
31
+ sqlite3 (1.3.9)
32
+ thor (0.19.1)
33
+ tzinfo (0.3.39)
34
+
35
+ PLATFORMS
36
+ ruby
37
+
38
+ DEPENDENCIES
39
+ activerecord (~> 3.2.0)
40
+ appraisal
41
+ minitest
42
+ rake
43
+ sqlite3
44
+ store_configurable!
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.0.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,60 @@
1
+ PATH
2
+ remote: ../
3
+ specs:
4
+ store_configurable (4.0.0)
5
+ activerecord (>= 4.0, < 4.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (4.0.12)
11
+ activesupport (= 4.0.12)
12
+ builder (~> 3.1.0)
13
+ activerecord (4.0.12)
14
+ activemodel (= 4.0.12)
15
+ activerecord-deprecated_finders (~> 1.0.2)
16
+ activesupport (= 4.0.12)
17
+ arel (~> 4.0.0)
18
+ activerecord-deprecated_finders (1.0.3)
19
+ activesupport (4.0.12)
20
+ i18n (~> 0.6, >= 0.6.9)
21
+ minitest (~> 4.2)
22
+ multi_json (~> 1.3)
23
+ thread_safe (~> 0.1)
24
+ tzinfo (~> 0.3.37)
25
+ appraisal (1.0.2)
26
+ bundler
27
+ rake
28
+ thor (>= 0.14.0)
29
+ arel (4.0.2)
30
+ builder (3.1.4)
31
+ coderay (1.1.0)
32
+ i18n (0.6.11)
33
+ method_source (0.8.2)
34
+ minitest (4.7.5)
35
+ minitest-focus (1.1.0)
36
+ minitest (>= 4, < 6)
37
+ multi_json (1.10.1)
38
+ pry (0.10.1)
39
+ coderay (~> 1.1.0)
40
+ method_source (~> 0.8.1)
41
+ slop (~> 3.4)
42
+ rake (10.4.1)
43
+ slop (3.6.0)
44
+ sqlite3 (1.3.10)
45
+ thor (0.19.1)
46
+ thread_safe (0.3.4)
47
+ tzinfo (0.3.42)
48
+
49
+ PLATFORMS
50
+ ruby
51
+
52
+ DEPENDENCIES
53
+ activerecord (~> 4.0.0)
54
+ appraisal
55
+ minitest
56
+ minitest-focus
57
+ pry
58
+ rake
59
+ sqlite3
60
+ store_configurable!
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.1.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,59 @@
1
+ PATH
2
+ remote: ../
3
+ specs:
4
+ store_configurable (4.0.0)
5
+ activerecord (>= 4.0, < 4.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (4.1.8)
11
+ activesupport (= 4.1.8)
12
+ builder (~> 3.1)
13
+ activerecord (4.1.8)
14
+ activemodel (= 4.1.8)
15
+ activesupport (= 4.1.8)
16
+ arel (~> 5.0.0)
17
+ activesupport (4.1.8)
18
+ i18n (~> 0.6, >= 0.6.9)
19
+ json (~> 1.7, >= 1.7.7)
20
+ minitest (~> 5.1)
21
+ thread_safe (~> 0.1)
22
+ tzinfo (~> 1.1)
23
+ appraisal (1.0.2)
24
+ bundler
25
+ rake
26
+ thor (>= 0.14.0)
27
+ arel (5.0.1.20140414130214)
28
+ builder (3.2.2)
29
+ coderay (1.1.0)
30
+ i18n (0.6.11)
31
+ json (1.8.1)
32
+ method_source (0.8.2)
33
+ minitest (5.4.3)
34
+ minitest-focus (1.1.0)
35
+ minitest (>= 4, < 6)
36
+ pry (0.10.1)
37
+ coderay (~> 1.1.0)
38
+ method_source (~> 0.8.1)
39
+ slop (~> 3.4)
40
+ rake (10.4.1)
41
+ slop (3.6.0)
42
+ sqlite3 (1.3.10)
43
+ thor (0.19.1)
44
+ thread_safe (0.3.4)
45
+ tzinfo (1.2.2)
46
+ thread_safe (~> 0.1)
47
+
48
+ PLATFORMS
49
+ ruby
50
+
51
+ DEPENDENCIES
52
+ activerecord (~> 4.1.0)
53
+ appraisal
54
+ minitest
55
+ minitest-focus
56
+ pry
57
+ rake
58
+ sqlite3
59
+ store_configurable!
@@ -3,5 +3,5 @@ require 'store_configurable/version'
3
3
  require 'store_configurable/dirty_options'
4
4
  require 'store_configurable/object'
5
5
  require 'store_configurable/serialization'
6
- require 'store_configurable/read'
6
+ require 'store_configurable/behavior'
7
7
  require 'store_configurable/base'
@@ -2,14 +2,14 @@ require 'active_support/concern'
2
2
 
3
3
  module StoreConfigurable
4
4
  module Base
5
-
5
+
6
6
  extend ActiveSupport::Concern
7
-
7
+
8
8
  module ClassMethods
9
-
9
+
10
10
  # To use StoreConfigurable, you must create create a +_config+ colun in the mdoel's table.
11
11
  # Make sure that you declare this column as a text type, so there's plenty of room.
12
- #
12
+ #
13
13
  # class AddStoreConfigurableField < ActiveRecord::Migration
14
14
  # def up
15
15
  # add_column :users, :_config, :text
@@ -18,20 +18,21 @@ module StoreConfigurable
18
18
  # remove_column :users, :_config
19
19
  # end
20
20
  # end
21
- #
21
+ #
22
22
  # Next declare that your model uses StoreConfigurable with the +store_configurable+ method.
23
23
  # Please read the +config+ documentation for usage examples.
24
- #
24
+ #
25
25
  # class User < ActiveRecord::Base
26
26
  # store_configurable
27
27
  # end
28
+ #
28
29
  def store_configurable
29
30
  serialize '_config', StoreConfigurable::Object
30
- include Read
31
+ include StoreConfigurable::Behavior
31
32
  end
32
-
33
+
33
34
  end
34
-
35
+
35
36
  end
36
37
  end
37
38
 
@@ -1,66 +1,65 @@
1
1
  module StoreConfigurable
2
- module Read
2
+ module Behavior
3
3
 
4
- # Our main syntatic interface to the underlying +_config+ store. This method ensures that
5
- # +self+, the store's owner, will allways be set in the config object. Hence allowing all
4
+ # Our main syntatic interface to the underlying +_config+ store. This method ensures that
5
+ # +self+, the store's owner, will allways be set in the config object. Hence allowing all
6
6
  # other recursive options to get a handle back to the owner.
7
- #
8
- # The config object can be treated as a Hash but in actuality is an enhanced subclass of
9
- # +ActiveSupport::OrderedOptions+ that does two important things. First, it allows you to
7
+ #
8
+ # The config object can be treated as a Hash but in actuality is an enhanced subclass of
9
+ # +ActiveSupport::OrderedOptions+ that does two important things. First, it allows you to
10
10
  # dynamically define any nested namespace property. Second, it hooks back into your parent
11
11
  # object to notify it of change via ActiveRecord's dirty support.
12
- #
12
+ #
13
13
  # Example:
14
- #
14
+ #
15
15
  # class User < ActiveRecord::Base
16
16
  # store_configurable
17
17
  # end
18
- #
18
+ #
19
19
  # user = User.find(42)
20
20
  # user.config.remember_me = true
21
21
  # user.config.sortable_tables.products.sort_on = 'created_at'
22
22
  # user.config.sortable_tables.products.direction = 'asc'
23
23
  # user.changed? # => true
24
24
  # user.config_changed? # => true
25
+ #
25
26
  def config
26
27
  _config.__store_configurable_owner__ = self
27
28
  _config
28
29
  end
29
-
30
+
30
31
  # Simple delegation to the underlying data attribute's changed query method.
32
+ #
31
33
  def config_changed?
32
34
  _config_changed?
33
35
  end
34
-
36
+
35
37
  # Simple delegation to the underlying data attribute's change array.
38
+ #
36
39
  def config_change
37
40
  _config_change
38
41
  end
39
-
42
+
40
43
  # An override to ActiveRecord's accessor for the sole purpoes of injecting +Serialization+
41
- # behavior so that we can set the context of this owner and ensure we pass that down to
42
- # the YAML coder. Doing this on a per instance basis keeps us from trumping all other
44
+ # behavior so that we can set the context of this owner and ensure we pass that down to
45
+ # the YAML coder. Doing this on a per instance basis keeps us from trumping all other
43
46
  # +ActiveRecord::AttributeMethods::Serialization::Attribute+ objects.
47
+ #
44
48
  def _config
45
49
  attrib = @attributes['_config']
46
50
  unless attrib.respond_to?(:__store_configurable_owner__)
47
- attrib.extend Serialization
51
+ attrib.extend Serialization
48
52
  attrib.__store_configurable_owner__ = self
49
53
  end
50
54
  super
51
55
  end
52
-
53
- # An override to ActiveRecord's low level read_attribute so we can setup the config object.
54
- def read_attribute(attr_name)
55
- config
56
- super
57
- end
58
56
 
59
- # We never want the `_config` key in the list of attributes. This keeps practically keeps
60
- # ActiveRecord from always saving this serialized column too
57
+ # We never want the `_config` key in the list of attributes. This keeps ActiveRecord
58
+ # from always saving this serialized column too.
59
+ #
61
60
  def attributes
62
61
  super.tap { |x| x.delete('_config') }
63
62
  end
64
-
63
+
65
64
  end
66
65
  end
@@ -1,79 +1,81 @@
1
1
  require 'active_support/ordered_options'
2
2
 
3
3
  module StoreConfigurable
4
-
4
+
5
5
  # The heart of StoreConfigurable's data store is this subclass of ActiveSupport's OrderedOptions.
6
6
  # They are the heart of Rails' configurations and allow you to dynamically set and get hash keys
7
- # and values using dot property notation vs the +[]+ hash accessors.
8
- #
9
- # However, instances of DirtyTrackingOrderedOptions use a recursive lambda via Hash's block
7
+ # and values using dot property notation vs the +[]+ hash accessors.
8
+ #
9
+ # However, instances of DirtyTrackingOrderedOptions use a recursive lambda via Hash's block
10
10
  # initialization syntax so that you get a dynamic and endless scope on config data. Instances of
11
11
  # DirtyTrackingOrderedOptions also make sure that every sub instance of it self also has a handle
12
- # back to your store's owner. In this way when config attributes are added or values change,
12
+ # back to your store's owner. In this way when config attributes are added or values change,
13
13
  # we can mark your ActiveRecord object as dirty/changed.
14
+ #
14
15
  class DirtyTrackingOrderedOptions < ::ActiveSupport::OrderedOptions
15
-
16
+
16
17
  Recursive = lambda { |h,k| h[k] = h.class.new(h.__store_configurable_owner__) }
17
-
18
+
18
19
  attr_accessor :__store_configurable_owner__
19
-
20
+
20
21
  def initialize(owner)
21
22
  @__store_configurable_owner__ = owner
22
23
  super(&Recursive)
23
24
  end
24
-
25
+
25
26
  def []=(key, value)
26
27
  _config_may_change!(key, value)
27
28
  super
28
29
  end
29
-
30
+
30
31
  def delete(key)
31
32
  name = key.to_sym
32
33
  _config_will_change! if has_key?(name)
33
34
  super
34
35
  end
35
-
36
+
36
37
  def delete_if
37
38
  _with_config_keys_may_change! { super }
38
39
  end
39
-
40
+
40
41
  def dup
41
42
  raise NotImplementedError, 'the StoreConfigurable::Object does not support making a copy'
42
43
  end
43
44
  alias_method :reject, :dup
44
-
45
+
45
46
  def merge(other)
46
47
  dup
47
48
  end
48
-
49
+
49
50
  def reject!
50
51
  _with_config_keys_may_change! { super }
51
52
  end
52
-
53
+
53
54
  def clear
54
55
  _config_will_change!
55
56
  super
56
57
  end
57
-
58
-
58
+
59
+
59
60
  protected
60
-
61
+
61
62
  def _with_config_keys_may_change!
62
63
  starting_keys = keys.dup
63
64
  yield
64
65
  _config_will_change! if starting_keys != keys
65
66
  self
66
67
  end
67
-
68
+
68
69
  def _config_may_change!(key, value)
69
70
  name = key.to_sym
70
71
  _config_will_change! unless has_key?(name) && self[name] == value
71
72
  end
72
-
73
+
73
74
  def _config_will_change!
75
+ return unless __store_configurable_owner__
74
76
  __store_configurable_owner__._config_will_change!
75
77
  end
76
-
78
+
77
79
  end
78
-
79
- end
80
+
81
+ end
@@ -1,75 +1,86 @@
1
- require 'active_support/basic_object'
1
+ require 'active_support/proxy_object'
2
2
 
3
3
  module StoreConfigurable
4
-
4
+
5
5
  # The is the object returned by the +config+ method. It does nothing more than delegate
6
6
  # all calls to a tree of +DirtyTrackingOrderedOptions+ objects which are basically hashes.
7
- class Object < ::ActiveSupport::BasicObject
8
-
9
- # Class methods so the +StoreConfigurable::Object+ responds to +dump+ and +load+ which
10
- # allows it to conform to ActiveRecord's coder requirement via its serialize method
11
- # that we use.
12
- #
13
- # The +dump+ method serializes the raw data behind the +StoreConfigurable::Object+ proxy
14
- # object. This means that we only store pure ruby primitives in the datbase, not our
7
+ #
8
+ class Object < ::ActiveSupport::ProxyObject
9
+
10
+ OMAP_CONVERTER = lambda do |config|
11
+ ::ActiveSupport::OrderedHash[config.to_a].tap do |omap|
12
+ omap.each { |k,v| omap[k] = OMAP_CONVERTER.call(v) if v.is_a?(::Hash) }
13
+ end
14
+ end
15
+
16
+ YAML_LOADER = lambda do |options, key, value|
17
+ value.is_a?(::Hash) ? value.each { |k,v| YAML_LOADER.call(options.send(:[],key), k, v) } :
18
+ options.send(:[]=, key, value)
19
+ end
20
+
21
+ # Class methods so the +StoreConfigurable::Object+ responds to +dump+ and +load+ which
22
+ # allows it to conform to ActiveRecord's coder requirement via its serialize method
23
+ # that we use.
24
+ #
25
+ # The +dump+ method serializes the raw data behind the +StoreConfigurable::Object+ proxy
26
+ # object. This means that we only store pure ruby primitives in the datbase, not our
15
27
  # proxy object's YAML type.
16
- #
17
- # The +load+ method mimics +ActiveRecord::Coders::YAMLColumn+ internals by retuning a
18
- # new object when needed as well as making sure that the YAML we are process if of the
19
- # same type. When reconstituting a +StoreConfigurable::Object+ we must set the store's
20
- # owner as this does. That way as our recursive lambda loader regenerates the tree of
21
- # config data, we always have a handle for each +DirtyTrackingOrderedOptions+ object to
22
- # report state changes back to the owner. Finally, after each load we make sure to clear
28
+ #
29
+ # The +load+ method mimics +ActiveRecord::Coders::YAMLColumn+ internals by retuning a
30
+ # new object when needed as well as making sure that the YAML we are process if of the
31
+ # same type. When reconstituting a +StoreConfigurable::Object+ we must set the store's
32
+ # owner as this does. That way as our recursive lambda loader regenerates the tree of
33
+ # config data, we always have a handle for each +DirtyTrackingOrderedOptions+ object to
34
+ # report state changes back to the owner. Finally, after each load we make sure to clear
23
35
  # out changes so reloaded objects are not marked as dirty.
36
+ #
24
37
  module Coding
25
-
38
+
26
39
  def dump(value)
27
- YAML.dump value.__config__
40
+ config = (value.nil? ? StoreConfigurable::Object.new : value).__config__
41
+ YAML.dump OMAP_CONVERTER.call(config)
28
42
  end
29
-
30
- def load(yaml, owner)
43
+
44
+ def load(yaml, owner=nil)
31
45
  return StoreConfigurable::Object.new if yaml.blank?
32
46
  return yaml unless yaml.is_a?(String) && yaml =~ /^---/
33
47
  stored_data = YAML.load(yaml)
34
48
  unless stored_data.is_a?(Hash)
35
- raise ActiveRecord::SerializationTypeMismatch,
49
+ raise ActiveRecord::SerializationTypeMismatch,
36
50
  "Attribute was supposed to be a Hash, but was a #{stored_data.class}"
37
51
  end
38
52
  config = StoreConfigurable::Object.new
39
53
  config.__store_configurable_owner__ = owner
40
- loader = lambda do |options, key, value|
41
- value.is_a?(Hash) ? value.each { |k,v| loader.call(options.send(:[],key), k, v) } :
42
- options.send(:[]=, key, value)
43
- end
44
- stored_data.each { |k,v| loader.call(config, k, v) }
45
- owner.changed_attributes.delete('_config')
54
+ stored_data.each { |k,v| YAML_LOADER.call(config, k, v) }
55
+ owner.changed_attributes.delete('_config') if owner
46
56
  config
47
57
  end
48
-
58
+
49
59
  end
50
-
51
- # Instance methods for +StoreConfigurable::Object+ defined and included in a module so
60
+
61
+ # Instance methods for +StoreConfigurable::Object+ defined and included in a module so
52
62
  # that if you ever wanted to, you could redefine these methods and +super+ up.
63
+ #
53
64
  module Behavior
54
-
65
+
55
66
  attr_accessor :__store_configurable_owner__
56
-
67
+
57
68
  def __config__
58
69
  @__config__ ||= DirtyTrackingOrderedOptions.new(@__store_configurable_owner__)
59
70
  end
60
-
71
+
61
72
  private
62
73
 
63
74
  def method_missing(method, *args, &block)
64
75
  __config__.__send__ method, *args, &block
65
76
  end
66
-
77
+
67
78
  end
68
-
79
+
69
80
  extend Coding
70
81
  include Behavior
71
-
82
+
72
83
  end
73
-
84
+
74
85
  end
75
86
 
@@ -1,18 +1,19 @@
1
1
  module StoreConfigurable
2
-
2
+
3
3
  # This module's behavior is injected into +ActiveRecord::AttributeMethods::Serialization::Attribute+
4
4
  # class which is a mini state machine for serialized objects. It allows us to both set the store's
5
- # owner as well as overwrite the +unserialize+ method to give the coder both the YAML and owner
5
+ # owner as well as overwrite the +unserialize+ method to give the coder both the YAML and owner
6
6
  # context. This is done via the +_config+ attribute reader override.
7
+ #
7
8
  module Serialization
8
-
9
+
9
10
  attr_accessor :__store_configurable_owner__
10
-
11
- def unserialize
11
+
12
+ def unserialize(v)
12
13
  self.state = :unserialized
13
- self.value = coder.load(value, __store_configurable_owner__)
14
+ self.value = coder.load(v, __store_configurable_owner__)
14
15
  end
15
-
16
+
16
17
  end
17
-
18
+
18
19
  end
@@ -1,6 +1,7 @@
1
1
  module StoreConfigurable
2
-
2
+
3
3
  # We track ActiveRecord's major and minor version and follow semantic versioning.
4
- VERSION = '3.2.5'
5
-
4
+ #
5
+ VERSION = '4.0.0'
6
+
6
7
  end
@@ -1,37 +1,39 @@
1
1
  require 'helper'
2
2
 
3
3
  class StoreConfigurable::BaseTest < StoreConfigurable::TestCase
4
-
4
+
5
5
  it 'is never blank' do
6
6
  new_user.config.wont_be_nil
7
7
  end
8
-
8
+
9
9
  it 'can inspect a new user' do
10
10
  new_user.inspect
11
11
  end
12
-
12
+
13
13
  it 'can set and get root attributes' do
14
14
  new_user.config.foo = 'foo'
15
15
  new_user.config.foo.must_equal 'foo'
16
16
  end
17
-
17
+
18
18
  it 'can set and get adhoc nested options' do
19
19
  options = {:this => 'that'}
20
20
  new_user.config.foo.bar.options = options
21
21
  new_user.config.foo.bar.options.must_equal options
22
22
  end
23
-
23
+
24
24
  it 'can serialize to yaml' do
25
25
  user_ken.config.foo = 'bar'
26
- user_ken.config.to_yaml.must_include '--- !omap'
27
- user_ken.config.to_yaml.must_include ':foo: bar'
26
+ user_ken.save!
27
+ raw_config = User.connection.select_value "SELECT _config FROM users WHERE id = #{user_ken.id}"
28
+ raw_config.must_include '--- !omap'
29
+ raw_config.must_include ':foo: bar'
28
30
  end
29
-
31
+
30
32
  it 'wont mark owner as dirty after initial read from database with no existing config' do
31
33
  user_ken.config
32
34
  user_ken.wont_be :config_changed?
33
35
  end
34
-
36
+
35
37
  it 'can use uncool hash syntax if you want with varying techniques of strings, symbols and calls' do
36
38
  user_ken.config.color = 'black'
37
39
  user_ken.config['remember_me'] = true
@@ -45,16 +47,16 @@ class StoreConfigurable::BaseTest < StoreConfigurable::TestCase
45
47
  user_ken.config.sortable_tables[:direction].must_equal 'asc'
46
48
  user_ken.config[:sortable_tables][:column].must_equal 'updated_at'
47
49
  end
48
-
50
+
49
51
  it 'must mark owner as dirty after missing getter since that inits a new namespace' do
50
52
  user_ken.config.bar
51
53
  user_ken.must_be :config_changed?
52
54
  end
53
-
55
+
54
56
  it 'does not support dup, reject, merge' do
55
- lambda{ user_ken.config.dup }.must_raise(NotImplementedError)
57
+ lambda{ user_ken.config.dup }.must_raise(NotImplementedError)
56
58
  lambda{ user_ken.config.reject{} }.must_raise(NotImplementedError)
57
- lambda{ user_ken.config.merge({}) }.must_raise(NotImplementedError)
59
+ lambda{ user_ken.config.merge({}) }.must_raise(NotImplementedError)
58
60
  end
59
61
 
60
62
  it 'can save unsafe keys' do
@@ -62,14 +64,14 @@ class StoreConfigurable::BaseTest < StoreConfigurable::TestCase
62
64
  user_ken.config.sortable_tables[:posts][:sort].dir = 'asc'
63
65
  user_ken.save!
64
66
  end
65
-
67
+
66
68
  describe 'existing data' do
67
-
69
+
68
70
  let(:color) { '#c1c1c1' }
69
71
  let(:remember) { true }
70
72
  let(:deep_value) { StorableObject.new('test') }
71
73
  let(:plugin_opts) { Hash[:sort,'asc',:on,true] }
72
-
74
+
73
75
  before do
74
76
  user_ken.config.color = color
75
77
  user_ken.config.remember_me = remember
@@ -95,14 +97,14 @@ class StoreConfigurable::BaseTest < StoreConfigurable::TestCase
95
97
  assert_queries(1) { @user.save! }
96
98
  User.find(@user.id).config.color.must_equal new_color
97
99
  end
98
-
100
+
99
101
  it 'can reconsitute saved values' do
100
102
  @user.config.color.must_equal color
101
103
  @user.config.remember_me.must_equal remember
102
104
  @user.config.plugin.options.must_equal plugin_opts
103
105
  @user.config.you.should.never.need.to.do.this.must_equal deep_value
104
106
  end
105
-
107
+
106
108
  it 'wont be dirty after reading saved configs' do
107
109
  @user.config.color
108
110
  @user.config.remember_me
@@ -110,7 +112,7 @@ class StoreConfigurable::BaseTest < StoreConfigurable::TestCase
110
112
  @user.config.you.should.never.need.to.do.this
111
113
  @user.wont_be :config_changed?
112
114
  end
113
-
115
+
114
116
  it 'wont be dirty when setting same config values' do
115
117
  @user.config.color = color
116
118
  @user.config.remember_me = remember
@@ -118,65 +120,98 @@ class StoreConfigurable::BaseTest < StoreConfigurable::TestCase
118
120
  @user.config.you.should.never.need.to.do.this = deep_value
119
121
  @user.wont_be :config_changed?
120
122
  end
121
-
123
+
122
124
  it 'must be marked dirty when values change' do
123
125
  @user.config.color = 'black'
124
126
  @user.must_be :config_changed?
125
127
  @user.save!
126
128
  @user.config.color.must_equal 'black'
127
129
  end
128
-
130
+
129
131
  it 'must be marked dirty when clearing' do
130
132
  @user.config.clear
131
133
  @user.must_be :config_changed?
132
134
  @user.save!
133
135
  @user.config.must_be :blank?
134
136
  end
135
-
137
+
136
138
  it 'must be marked dirty when deleting a key' do
137
139
  @user.config.delete :color
138
140
  @user.must_be :config_changed?
139
141
  @user.save!
140
142
  @user.config.has_key?(:color).must_equal false
141
143
  end
142
-
144
+
143
145
  it 'wont be marked dirty when deleting a non-existent key' do
144
146
  @user.config.delete :doesnotexist
145
147
  @user.wont_be :config_changed?
146
148
  end
147
-
149
+
148
150
  it 'must be marked dirty when using delete_if' do
149
151
  @user.config.delete_if { |k,v| true }
150
152
  @user.must_be :config_changed?
151
153
  @user.config.must_be :blank?
152
154
  end
153
-
155
+
154
156
  it 'wont be marked dirty when using delete_if and nothing happens' do
155
157
  @user.config.delete_if { |k,v| false }
156
158
  @user.wont_be :config_changed?
157
159
  @user.config.you.should.never.need.to.do.this = deep_value
158
160
  end
159
-
161
+
160
162
  it 'must be marked dirty when using reject! on true' do
161
163
  @user.config.reject! { |k,v| true }
162
164
  @user.must_be :config_changed?
163
165
  @user.config.must_be :blank?
164
166
  end
165
-
167
+
166
168
  it 'can pass off nodes and still work properly' do
167
169
  need = @user.config.you.should.never.need
168
170
  need.moar = 'winning'
169
171
  @user.must_be :config_changed?
170
172
  end
171
-
173
+
172
174
  it 'can resave unsafe keys' do
173
175
  @user.config.sortable_tables[:comments][:sort].on = 'title'
174
176
  @user.config.sortable_tables[:comments][:sort].dir = 'asc'
175
177
  @user.save!
176
178
  end
177
-
179
+
180
+ end
181
+
182
+ describe 'legacy data' do
183
+
184
+ let(:omap_yaml) { omap_yaml_string.strip }
185
+
186
+ it 'can use omap yaml' do
187
+ conn = User.connection
188
+ conn.execute "UPDATE users SET _config=#{conn.quote(omap_yaml)} WHERE id=#{user_ken.id}"
189
+ user = User.find(user_ken.id)
190
+ user.config.sortable_tables.column.must_equal 'created_at'
191
+ end
192
+
193
+ end
194
+
195
+
196
+
197
+ private
198
+
199
+ def omap_yaml_string
200
+ <<YAML
201
+ --- !omap
202
+ - :remember_me: true
203
+ - :sortable_tables: !omap
204
+ - :column: created_at
205
+ - :direction: asc
206
+ - :you: !omap
207
+ - :should: !omap
208
+ - :never: !omap
209
+ - :need: !omap
210
+ - :to: !omap
211
+ - :do: !omap
212
+ - :this: deep_value
213
+ YAML
178
214
  end
179
215
 
180
-
181
216
  end
182
217
 
@@ -1,55 +1,46 @@
1
- require 'rubygems'
2
- require 'bundler'
3
- require 'bundler/setup'
4
- Bundler.require
1
+ require 'bundler' ; Bundler.require :development, :test
5
2
  require 'store_configurable'
6
- require 'active_record/base'
7
3
  require 'support/activerecord'
8
4
  require 'minitest/autorun'
9
5
  require 'logger'
10
6
 
11
-
12
- ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__),'debug.log'))
7
+ ActiveRecord::Base.logger = Logger.new('/dev/null')
13
8
  ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
14
9
 
15
-
16
10
  module StoreConfigurable
17
11
  class TestCase < MiniTest::Spec
18
12
 
19
13
  include ActiveRecordTestHelper
20
-
14
+
21
15
  before { setup_environment }
22
-
16
+
23
17
  let(:new_user) { User.new }
24
- let(:user_ken) { User.find_by_email('ken@metaskills.net') }
25
- let(:post_sc) { Post.find_by_title('StoreConfigurable') }
26
-
18
+ let(:user_ken) { User.where(:email => 'ken@metaskills.net').first }
19
+
27
20
  def setup_environment
28
21
  setup_database
29
22
  setup_data
30
23
  end
31
-
24
+
32
25
  protected
33
26
 
34
27
  def setup_database
35
28
  ActiveRecord::Base.class_eval do
36
- silence do
37
- connection.create_table :users, :force => true do |t|
38
- t.string :name, :email
39
- t.text :_config
40
- end
41
- connection.create_table :posts, :force => true do |t|
42
- t.string :title, :body
43
- end
29
+ connection.create_table :users, :force => true do |t|
30
+ t.string :name, :email
31
+ t.text :_config
32
+ end
33
+ connection.create_table :posts, :force => true do |t|
34
+ t.string :title, :body
44
35
  end
45
36
  end
46
37
  end
47
-
38
+
48
39
  def setup_data
49
40
  User.create :name => 'Ken Collins', :email => 'ken@metaskills.net'
50
41
  Post.create :title => 'StoreConfigurable', :body => 'test'
51
42
  end
52
-
43
+
53
44
  end
54
45
  end
55
46
 
@@ -63,5 +54,5 @@ class User < ActiveRecord::Base
63
54
  store_configurable
64
55
  end
65
56
 
66
- class Post < ActiveRecord::Base
57
+ class Post < ActiveRecord::Base
67
58
  end
@@ -4,7 +4,7 @@ module StoreConfigurable
4
4
  protected
5
5
 
6
6
  class SQLCounter
7
-
7
+
8
8
  class << self
9
9
  attr_accessor :ignored_sql, :log
10
10
  end
@@ -23,7 +23,7 @@ module StoreConfigurable
23
23
  return if 'CACHE' == values[:name] || ignore =~ sql
24
24
  self.class.log << sql
25
25
  end
26
-
26
+
27
27
  end
28
28
 
29
29
  def assert_sql(*patterns_to_match)
@@ -48,4 +48,4 @@ module StoreConfigurable
48
48
  ActiveSupport::Notifications.subscribe 'sql.active_record', SQLCounter.new
49
49
 
50
50
  end
51
- end
51
+ end
metadata CHANGED
@@ -1,80 +1,119 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: store_configurable
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.5
5
- prerelease:
4
+ version: 4.0.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Ken Collins
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-09-25 00:00:00.000000000 Z
11
+ date: 2014-12-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: activerecord
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ~>
17
+ - - ">="
20
18
  - !ruby/object:Gem::Version
21
- version: 3.2.0
19
+ version: '4.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '4.2'
22
23
  type: :runtime
23
24
  prerelease: false
24
25
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
26
  requirements:
27
- - - ~>
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '4.0'
30
+ - - "<"
28
31
  - !ruby/object:Gem::Version
29
- version: 3.2.0
32
+ version: '4.2'
33
+ - !ruby/object:Gem::Dependency
34
+ name: appraisal
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
30
47
  - !ruby/object:Gem::Dependency
31
48
  name: sqlite3
32
49
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
50
  requirements:
35
- - - ~>
51
+ - - ">="
36
52
  - !ruby/object:Gem::Version
37
- version: '1.3'
53
+ version: '0'
38
54
  type: :development
39
55
  prerelease: false
40
56
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
57
  requirements:
43
- - - ~>
58
+ - - ">="
44
59
  - !ruby/object:Gem::Version
45
- version: '1.3'
60
+ version: '0'
46
61
  - !ruby/object:Gem::Dependency
47
62
  name: rake
48
63
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
64
  requirements:
51
- - - ~>
65
+ - - ">="
52
66
  - !ruby/object:Gem::Version
53
- version: 0.9.2
67
+ version: '0'
54
68
  type: :development
55
69
  prerelease: false
56
70
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
71
  requirements:
59
- - - ~>
72
+ - - ">="
60
73
  - !ruby/object:Gem::Version
61
- version: 0.9.2
74
+ version: '0'
62
75
  - !ruby/object:Gem::Dependency
63
76
  name: minitest
64
77
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
78
  requirements:
67
- - - ~>
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: minitest-focus
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: pry
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
68
108
  - !ruby/object:Gem::Version
69
- version: 2.8.1
109
+ version: '0'
70
110
  type: :development
71
111
  prerelease: false
72
112
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
113
  requirements:
75
- - - ~>
114
+ - - ">="
76
115
  - !ruby/object:Gem::Version
77
- version: 2.8.1
116
+ version: '0'
78
117
  description: Grown up ActiveRecord::Store config options!
79
118
  email:
80
119
  - ken@metaskills.net
@@ -82,18 +121,25 @@ executables: []
82
121
  extensions: []
83
122
  extra_rdoc_files: []
84
123
  files:
85
- - .gitignore
86
- - .travis.yml
124
+ - ".gitignore"
125
+ - ".travis.yml"
126
+ - Appraisals
87
127
  - CHANGELOG
88
128
  - Gemfile
89
129
  - MIT-LICENSE
90
130
  - README.md
91
131
  - Rakefile
132
+ - gemfiles/activerecord32.gemfile
133
+ - gemfiles/activerecord32.gemfile.lock
134
+ - gemfiles/activerecord40.gemfile
135
+ - gemfiles/activerecord40.gemfile.lock
136
+ - gemfiles/activerecord41.gemfile
137
+ - gemfiles/activerecord41.gemfile.lock
92
138
  - lib/store_configurable.rb
93
139
  - lib/store_configurable/base.rb
140
+ - lib/store_configurable/behavior.rb
94
141
  - lib/store_configurable/dirty_options.rb
95
142
  - lib/store_configurable/object.rb
96
- - lib/store_configurable/read.rb
97
143
  - lib/store_configurable/serialization.rb
98
144
  - lib/store_configurable/version.rb
99
145
  - test/cases/base_test.rb
@@ -101,32 +147,30 @@ files:
101
147
  - test/support/activerecord.rb
102
148
  homepage: http://github.com/metaskills/store_configurable/
103
149
  licenses: []
150
+ metadata: {}
104
151
  post_install_message:
105
152
  rdoc_options:
106
- - --charset=UTF-8
153
+ - "--charset=UTF-8"
107
154
  require_paths:
108
155
  - lib
109
156
  required_ruby_version: !ruby/object:Gem::Requirement
110
- none: false
111
157
  requirements:
112
- - - ! '>='
158
+ - - ">="
113
159
  - !ruby/object:Gem::Version
114
160
  version: '0'
115
161
  required_rubygems_version: !ruby/object:Gem::Requirement
116
- none: false
117
162
  requirements:
118
- - - ! '>='
163
+ - - ">="
119
164
  - !ruby/object:Gem::Version
120
165
  version: '0'
121
166
  requirements: []
122
167
  rubyforge_project:
123
- rubygems_version: 1.8.23
168
+ rubygems_version: 2.4.2
124
169
  signing_key:
125
- specification_version: 3
170
+ specification_version: 4
126
171
  summary: A zero-configuration recursive Hash for storing a tree of options in a serialized
127
172
  ActiveRecord column.
128
173
  test_files:
129
174
  - test/cases/base_test.rb
130
175
  - test/helper.rb
131
176
  - test/support/activerecord.rb
132
- has_rdoc: