store_configurable 3.2.5 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: