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.
- checksums.yaml +7 -0
- data/.travis.yml +2 -2
- data/Appraisals +8 -0
- data/Gemfile +1 -1
- data/README.md +21 -3
- data/Rakefile +3 -7
- data/gemfiles/activerecord32.gemfile +7 -0
- data/gemfiles/activerecord32.gemfile.lock +44 -0
- data/gemfiles/activerecord40.gemfile +7 -0
- data/gemfiles/activerecord40.gemfile.lock +60 -0
- data/gemfiles/activerecord41.gemfile +7 -0
- data/gemfiles/activerecord41.gemfile.lock +59 -0
- data/lib/store_configurable.rb +1 -1
- data/lib/store_configurable/base.rb +10 -9
- data/lib/store_configurable/{read.rb → behavior.rb} +23 -24
- data/lib/store_configurable/dirty_options.rb +25 -23
- data/lib/store_configurable/object.rb +49 -38
- data/lib/store_configurable/serialization.rb +9 -8
- data/lib/store_configurable/version.rb +4 -3
- data/test/cases/base_test.rb +65 -30
- data/test/helper.rb +16 -25
- data/test/support/activerecord.rb +3 -3
- metadata +82 -38
checksums.yaml
ADDED
@@ -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
|
data/.travis.yml
CHANGED
data/Appraisals
ADDED
data/Gemfile
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
source
|
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', '~>
|
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)
|
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
|
-
|
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(
|
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,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,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,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!
|
data/lib/store_configurable.rb
CHANGED
@@ -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/
|
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
|
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
|
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
|
60
|
-
#
|
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/
|
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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
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
|
-
|
41
|
-
|
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(
|
14
|
+
self.value = coder.load(v, __store_configurable_owner__)
|
14
15
|
end
|
15
|
-
|
16
|
+
|
16
17
|
end
|
17
|
-
|
18
|
+
|
18
19
|
end
|
data/test/cases/base_test.rb
CHANGED
@@ -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.
|
27
|
-
|
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
|
|
data/test/helper.rb
CHANGED
@@ -1,55 +1,46 @@
|
|
1
|
-
require '
|
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.
|
25
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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:
|
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:
|
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:
|
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:
|
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: '
|
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: '
|
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
|
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
|
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:
|
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:
|
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:
|
168
|
+
rubygems_version: 2.4.2
|
124
169
|
signing_key:
|
125
|
-
specification_version:
|
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:
|