arms 0.0.1.pre.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.simplecov +1 -0
- data/.yardopts +1 -0
- data/LICENSE.txt +21 -0
- data/README.md +82 -0
- data/Rakefile.rb +9 -0
- data/arms.gemspec +37 -0
- data/lib/arms.rb +96 -0
- data/lib/arms/indifferent_hashes_coder.rb +26 -0
- data/lib/arms/multi_coder.rb +84 -0
- data/lib/arms/struct_coder.rb +93 -0
- data/lib/arms/version.rb +3 -0
- data/test/arms_test.rb +140 -0
- data/test/blog_models.rb +56 -0
- data/test/struct_coder_test.rb +69 -0
- data/test/test_helper.rb +25 -0
- metadata +168 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6b55e292c1c3dd14a37093716198e3b6f1c727f0ec5b7936cff67737abfbdab5
|
4
|
+
data.tar.gz: 34431ad4cb56af7559816402c5c9a27b68f4218728a36e5d0c077ffb99691fc4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cc79ea29be6ced5ad4ed5e1a8a915f2993d898c44d4fa40510719ae2eeb9ab362b67194ea0ea595afa9a1cc52102ba2e098d4592d4718ba7e538d1af3d007cdd
|
7
|
+
data.tar.gz: 0e300ee399e111f75a0d1cbb144b0fe246402acfa6f64aee1c66b341c62532f28277225e890b94ca3f90c7c43fc0bc1af9010eb393f6d6385e272683f4171469
|
data/.simplecov
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
SimpleCov.start
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--main README.md --markup=markdown {lib}/**/*.rb
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 Ethan
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# ARMS
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/notEthan/arms.svg?branch=master)](https://travis-ci.org/notEthan/arms)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/github/notEthan/arms/badge.svg)](https://coveralls.io/github/notEthan/arms)
|
5
|
+
|
6
|
+
ARMS: Active Record Multiple Serialization. This is a library which extends the capabilities of ActiveRecord serialization, allowing you to chain together coders.
|
7
|
+
|
8
|
+
For a very simple example, we'll do a thing you can't easily do in ActiveRecord: get a hash with indifferent string/symbol access on the model (in this case for a column named `preferences`), storing serialized JSON in the database:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
require 'arms'
|
12
|
+
class Foo < ActiveRecord::Base
|
13
|
+
arms_serialize :preferences, :indifferent_hashes, JSON
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
assuming you have a database set up with table `foos` and string column `preferences` (see [this script](https://gist.github.com/notEthan/84a4e583ea6e96f0f92dab43286ba301) for a full example), the database will contain JSON (seen with `#preferences_before_type_cast`) and the model attribute offers indifferent access.
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
Foo.create!(preferences: {favorite_animal: 'ocelot'})
|
21
|
+
|
22
|
+
foo = foo.last
|
23
|
+
|
24
|
+
# JSON in the DB:
|
25
|
+
foo.preferences_before_type_cast
|
26
|
+
# => "{\"favorite_animal\":\"ocelot\"}"
|
27
|
+
|
28
|
+
# indifferent access on the model:
|
29
|
+
foo.preferences[:favorite_animal]
|
30
|
+
# => 'ocelot'
|
31
|
+
foo.preferences['favorite_animal']
|
32
|
+
# => 'ocelot'
|
33
|
+
```
|
34
|
+
|
35
|
+
## Coder Shortcuts
|
36
|
+
|
37
|
+
With stock ActiveRecord, you can call `serialize :foo, JSON` which is a sort of shortcut to the coder `ActiveRecord::Coders::JSON`. ARMS extends this a bit and offers a registry of shortcuts. In the above example, `:indifferent_hashes` is a shortcut invoking the coder `ARMS::IndifferentHashesCoder`. Most shortcut keys are symbols, but sometimes classes such as JSON or YAML are shortcut keys to coders for those serializations.
|
38
|
+
|
39
|
+
Some coders take arguments when they are instantiated. Shortcuts can be expressed as an array, where the first element is the shortcut key and the remainder of the array is passed as arguments to instantiate the coder - for example, the YAML coder can take an argument hinting what class it expects to be serializing. Modifying the above, this would look like:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
class Foo < ActiveRecord::Base
|
43
|
+
arms_serialize :preferences, :indifferent_hashes, [YAML, Hash]
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
A full example with this serialization is [at this link](https://gist.github.com/notEthan/297243912fcbd07354fc3d48093df12f).
|
48
|
+
|
49
|
+
### Built-in Shortcuts
|
50
|
+
|
51
|
+
The following shortcuts are built into ARMS:
|
52
|
+
|
53
|
+
| Shortcut Key | Loads | Dumps | Arguments | Coder Class |
|
54
|
+
| --- | --- | --- | --- | --- |
|
55
|
+
| JSON | A string of JSON | Ruby Arrays, Hashes, and basic types | none | ActiveRecord::Coders::JSON |
|
56
|
+
| :json | ^ | ^ | ^ | ^ |
|
57
|
+
| YAML | A string of YAML | Any | Expected loaded class | ActiveRecord::Coders::YAMLColumn |
|
58
|
+
| :yaml | ^ | ^ | ^ | ^ |
|
59
|
+
| :indifferent_hashes | Indifferentiated structure of Arrays and Hashes | Plain structure of Arrays and Hashes | none | ARMS::IndifferentHashesCoder |
|
60
|
+
| :struct | An instance or array of instances of a Struct class | A Hash or Array of Hashes | The Struct class to instantiate | ARMS::StructCoder |
|
61
|
+
|
62
|
+
## Provided Coders
|
63
|
+
|
64
|
+
ARMS offers a few useful coders which may be used with arms_serialize, or with vanilla ActiveRecord::Base.serialize. For the most part these aim to have JSONifiable data on the #dump side, which may be stored in a JSON column or serialized to text with yaml or json.
|
65
|
+
|
66
|
+
### ARMS::IndifferentHashesCoder
|
67
|
+
|
68
|
+
When loading, this coder takes a JSONifiable structure of arrays and hashes, and will change Hash instances to ActiveSupport::HashWithIndifferentAccess.
|
69
|
+
|
70
|
+
When dumping, it converts indifferent hashes to plain hashes.
|
71
|
+
|
72
|
+
### ARMS::StructCoder
|
73
|
+
|
74
|
+
Instantiated with a Struct class, this converts an instance or instances of that struct to JSONifiable types.
|
75
|
+
|
76
|
+
When loading a hash (or array of hashes), the given struct class is instatiated with each member corresponding to a key of a hash.
|
77
|
+
|
78
|
+
When dumping, an instance of the specified struct class (or array of instances) is dumped to a hash in which each member of the struct and its value is a key/value pair.
|
79
|
+
|
80
|
+
## License
|
81
|
+
|
82
|
+
ARMS is open source software available under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile.rb
ADDED
data/arms.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "arms/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "arms"
|
7
|
+
spec.version = ARMS::VERSION
|
8
|
+
spec.authors = ["Ethan"]
|
9
|
+
spec.email = ["ethan@unth.net"]
|
10
|
+
|
11
|
+
spec.summary = 'Active Record Multiple Serialization'
|
12
|
+
spec.description = 'A library which offers flexible, chained serializion for Active Record'
|
13
|
+
spec.homepage = "https://github.com/notEthan/arms"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
ignore_files = %w(.gitignore .travis.yml Gemfile test)
|
17
|
+
ignore_files_re = %r{\A(#{ignore_files.map { |f| Regexp.escape(f) }.join('|')})(/|\z)}
|
18
|
+
# Specify which files should be added to the gem when it is released.
|
19
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
20
|
+
Dir.chdir(File.expand_path('..', __FILE__)) do
|
21
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(ignore_files_re) }
|
22
|
+
spec.test_files = `git ls-files -z test`.split("\x0") + [
|
23
|
+
'.simplecov',
|
24
|
+
]
|
25
|
+
end
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_dependency "activerecord"
|
31
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
32
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
33
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
34
|
+
spec.add_development_dependency "minitest-reporters"
|
35
|
+
spec.add_development_dependency "simplecov"
|
36
|
+
spec.add_development_dependency "sqlite3", "~> 1.3", ">= 1.3.6" # loosen this in accordance with active_record/connection_adapters/sqlite3_adapter.rb
|
37
|
+
end
|
data/lib/arms.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require "arms/version"
|
2
|
+
|
3
|
+
module ARMS
|
4
|
+
# base class for ARMS errors
|
5
|
+
class Error < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# a coder which is not a recognized shortcut and/or does not respond to #load and #dump
|
9
|
+
class InvalidCoder < Error
|
10
|
+
attr_accessor :coder
|
11
|
+
end
|
12
|
+
|
13
|
+
# an error loading column data to objects on the model
|
14
|
+
class LoadError < Error
|
15
|
+
end
|
16
|
+
|
17
|
+
# an error dumping objects from the model to column data
|
18
|
+
class DumpError < Error
|
19
|
+
end
|
20
|
+
|
21
|
+
# the object passed to a coder shortcut proc, which indicates the model and attribute name
|
22
|
+
# and passes optional arguments used to instantiate the coder.
|
23
|
+
class ShortcutInvocation
|
24
|
+
# the model on which an attribute is being serialized
|
25
|
+
attr_accessor :model
|
26
|
+
# the name of the attribute being serialized
|
27
|
+
attr_accessor :attr_name
|
28
|
+
# arguments passed from the shortcut invocation to the coder shortcut proc
|
29
|
+
attr_accessor :args
|
30
|
+
end
|
31
|
+
|
32
|
+
@coder_shortcuts = {}
|
33
|
+
|
34
|
+
class << self
|
35
|
+
# adds a shortcut which can be used with ActiveRecord::Base.arms_serialize. the key is usually
|
36
|
+
# a symbol, but may be anything. the given block is called by arms_serialize with an
|
37
|
+
# ARMS::ShortcutInvocation object, and must result in a coder.
|
38
|
+
#
|
39
|
+
# @yieldparam shortcut_invocation [ARMS::ShortcutInvocation]
|
40
|
+
# @yieldreturn [#load, #dump] a coder which responds to #load and #dump
|
41
|
+
def register_coder_shortcut(key, &coderproc)
|
42
|
+
raise(ArgumentError, "already registered shortcut: #{key}") if @coder_shortcuts.key?(key)
|
43
|
+
@coder_shortcuts[key] = coderproc
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
autoload :MultiCoder, 'arms/multi_coder'
|
49
|
+
autoload :IndifferentHashesCoder, 'arms/indifferent_hashes_coder'
|
50
|
+
autoload :StructCoder, 'arms/struct_coder'
|
51
|
+
end
|
52
|
+
|
53
|
+
require 'json'
|
54
|
+
ARMS.register_coder_shortcut(JSON) { ::ActiveRecord::Coders::JSON }
|
55
|
+
ARMS.register_coder_shortcut(:json) { ::ActiveRecord::Coders::JSON }
|
56
|
+
|
57
|
+
require 'yaml'
|
58
|
+
ARMS.register_coder_shortcut(YAML) { |s| ::ActiveRecord::Coders::YAMLColumn.new(s.attr_name, *s.args) }
|
59
|
+
ARMS.register_coder_shortcut(:yaml) { |s| ::ActiveRecord::Coders::YAMLColumn.new(s.attr_name, *s.args) }
|
60
|
+
|
61
|
+
ARMS.register_coder_shortcut(:indifferent_hashes) { ARMS::IndifferentHashesCoder.new }
|
62
|
+
ARMS.register_coder_shortcut(:struct) { |s| ARMS::StructCoder.new(*s.args) }
|
63
|
+
|
64
|
+
module ARMS
|
65
|
+
module ActiveRecord
|
66
|
+
module AttributeMethods
|
67
|
+
module Serialization
|
68
|
+
# ActiveRecord::Base.arms_serialize takes an attribute name and any number of coders which
|
69
|
+
# will be chained to serialize and deserialize between the database column and the model
|
70
|
+
# attribute.
|
71
|
+
#
|
72
|
+
# full documentation is at {ARMS::MultiCoder#initialize}.
|
73
|
+
#
|
74
|
+
# here are a few example invocations:
|
75
|
+
#
|
76
|
+
# # two coders: indifferent hashes, YAML with argument Hash (the object_class)
|
77
|
+
# arms_serialize('preferences', :indifferent_hashes, [YAML, Hash])
|
78
|
+
#
|
79
|
+
# # two coders: struct coder with argument Preference (the struct class), JSON coder
|
80
|
+
# MultiCoder.new([[:struct, Preference], :json], attr_name: 'preferences', model: Foo)
|
81
|
+
def arms_serialize(attr_name, *coders)
|
82
|
+
multi_coder = ARMS::MultiCoder.new(coders, attr_name: attr_name, model: self)
|
83
|
+
serialize(attr_name, multi_coder)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
require 'active_record'
|
91
|
+
|
92
|
+
module ActiveRecord
|
93
|
+
class Base
|
94
|
+
extend ARMS::ActiveRecord::AttributeMethods::Serialization
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ARMS
|
2
|
+
# ARMS::IndifferentHashesCoder will replace any Hashes in a structure of Arrays and Hashes with ActiveSupport::HashWithIndifferentAccess on load, and convert back to plain hashes on dump.
|
3
|
+
class IndifferentHashesCoder
|
4
|
+
# @param column_data [Array, Hash, Object] a structure in which Hashes will be replaced with ActiveSupport::HashWithIndifferentAccess
|
5
|
+
def load(column_data)
|
6
|
+
if column_data.respond_to?(:to_ary)
|
7
|
+
column_data.to_ary.map { |el| load(el) }
|
8
|
+
elsif column_data.respond_to?(:to_hash)
|
9
|
+
ActiveSupport::HashWithIndifferentAccess.new(column_data).transform_values { |v| load(v) }
|
10
|
+
else
|
11
|
+
column_data
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param object [#to_ary, #to_hash, Object] a structure in which ActiveSupport::HashWithIndifferentAccess instances will be replaced with plain Hashes
|
16
|
+
def dump(object)
|
17
|
+
if object.respond_to?(:to_ary)
|
18
|
+
object.to_ary.map { |el| dump(el) }
|
19
|
+
elsif object.respond_to?(:to_hash)
|
20
|
+
object.to_hash.transform_values { |v| dump(v) }
|
21
|
+
else
|
22
|
+
object
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module ARMS
|
2
|
+
class MultiCoder
|
3
|
+
# loads and dumps between database column and model attribute, using any number of coders.
|
4
|
+
#
|
5
|
+
# the first coder is closest to the loaded model attribute. the last coder is closest to
|
6
|
+
# the dumped database column.
|
7
|
+
#
|
8
|
+
# each coder must respond to #load and #dump. such a coder can be passed directly, or as a
|
9
|
+
# shortcut consisting of a key registered with ARMS.register_coder_shortcut and optional
|
10
|
+
# arguments (using an array). each of the following is a valid coder (an element of the
|
11
|
+
# coders array):
|
12
|
+
#
|
13
|
+
# # direct reference to the coder
|
14
|
+
# ::ActiveRecord::Coders::YAMLColumn.new('foo')
|
15
|
+
#
|
16
|
+
# # shortcut, equivalent to the above
|
17
|
+
# :yaml
|
18
|
+
#
|
19
|
+
# # shortcut passing optional `object_class` argument to yaml coder.
|
20
|
+
# # the first element of this array is the shortcut key, and the remainder
|
21
|
+
# # is arguments passed to instantiate the coder.
|
22
|
+
# [:yaml, Array]
|
23
|
+
#
|
24
|
+
# here are a few example invocations that instantiate a MultiCoder:
|
25
|
+
#
|
26
|
+
# # two coders: indifferent hashes, YAML with argument Hash (the object_class)
|
27
|
+
# MultiCoder.new([:indifferent_hashes, [YAML, Hash]], attr_name: 'preferences', model: Foo)
|
28
|
+
#
|
29
|
+
# # two coders: struct coder with argument Preference (the struct class), JSON coder
|
30
|
+
# MultiCoder.new([[:struct, Preference], :json], attr_name: 'preferences', model: Foo)
|
31
|
+
#
|
32
|
+
# load goes like:
|
33
|
+
#
|
34
|
+
# database column -> coderN.load -> ... -> coder1.load -> model attribute
|
35
|
+
#
|
36
|
+
# dump goes like:
|
37
|
+
#
|
38
|
+
# model attribute -> coder1.dump -> ... -> coderN.dump -> database column
|
39
|
+
#
|
40
|
+
# @param coders [Array] an array of coders (which respond to #load and #dump) or coder shortcuts
|
41
|
+
# @param model [Class] the model on which the attribute is being serialized
|
42
|
+
# @param attr_name the attribute name being serialized on the model
|
43
|
+
def initialize(coders, model: nil, attr_name: nil)
|
44
|
+
@coders = coders.each_with_index.map do |coder, i|
|
45
|
+
shortcut_invocation = ShortcutInvocation.new
|
46
|
+
shortcut_invocation.model = model
|
47
|
+
shortcut_invocation.attr_name = attr_name
|
48
|
+
|
49
|
+
if coder.respond_to?(:to_ary)
|
50
|
+
shortcut_invocation.args = coder[1..-1]
|
51
|
+
coder = coder[0]
|
52
|
+
end
|
53
|
+
|
54
|
+
if ARMS.instance_exec { @coder_shortcuts }.key?(coder)
|
55
|
+
ARMS.instance_exec { @coder_shortcuts }[coder].(shortcut_invocation)
|
56
|
+
elsif coder.respond_to?(:load) && coder.respond_to?(:dump)
|
57
|
+
if shortcut_invocation.args.nil? || shortcut_invocation.args.empty?
|
58
|
+
coder
|
59
|
+
else
|
60
|
+
raise(InvalidCoder.new("given shortcut arguments are not passed to the coder at index #{i} which responds to #load and #dump. coder: #{coder.inspect}; shortcut args: #{shortcut_invocation.args.inspect}").tap { |e| e.coder = coder })
|
61
|
+
end
|
62
|
+
else
|
63
|
+
raise(InvalidCoder.new("given coder at index #{i} is not a recognized shortcut and does not respond to #load and #dump. coder: #{coder.inspect}; shortcut args: #{shortcut_invocation.args.inspect}").tap { |e| e.coder = coder })
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param column_data [Object] data hot off the database column
|
69
|
+
# @return [Object] loaded (deserialized) data
|
70
|
+
def load(column_data)
|
71
|
+
@coders.reverse.inject(column_data) do |data, coder|
|
72
|
+
coder.load(data)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param object [Object] object on the model attribute
|
77
|
+
# @return [Object] dumped (serialized) data
|
78
|
+
def dump(object)
|
79
|
+
@coders.inject(object) do |data, coder|
|
80
|
+
coder.dump(data)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module ARMS
|
2
|
+
# this is a ActiveRecord serialization class intended to serialize from a Struct class
|
3
|
+
# on the loaded ruby side to something JSON-compatible on the dumped database side.
|
4
|
+
#
|
5
|
+
# This coder relies on `loaded_class`, the Struct class which will be used to instantiate
|
6
|
+
# the column data. properties (members) of the loaded class will correspond
|
7
|
+
# to keys of the dumped json object.
|
8
|
+
#
|
9
|
+
# the data may be either a single instance of the loaded class
|
10
|
+
# (serialized as one hash) or an array of them (serialized as an
|
11
|
+
# array of hashes), indicated by the boolean keyword argument `array`.
|
12
|
+
#
|
13
|
+
# the column behind the attribute may be an actual JSON column (postgres json
|
14
|
+
# or jsonb - hstore should work too if you only have string attributes) or may
|
15
|
+
# be a string column with a string serializer after StructCoder.
|
16
|
+
class StructCoder
|
17
|
+
# @param loaded_class [Class] the Struct class to load
|
18
|
+
# @param array [Boolean] whether the column holds an array of Struct instances instead of just one
|
19
|
+
def initialize(loaded_class, array: false)
|
20
|
+
@loaded_class = loaded_class
|
21
|
+
# this notes the order of the keys as they were in the json, used by dump_object to generate
|
22
|
+
# json that is equivalent to the json/jsonifiable that came in, so that AR's #changed_attributes
|
23
|
+
# can tell whether the attribute has been changed.
|
24
|
+
@loaded_class.send(:attr_accessor, :arms_object_json_coder_keys_order)
|
25
|
+
@array = array
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param data [Hash, Array<Hash>]
|
29
|
+
# @return [loaded_class, Array[loaded_class]]
|
30
|
+
def load(data)
|
31
|
+
return nil if data.nil?
|
32
|
+
object = if @array
|
33
|
+
unless data.respond_to?(:to_ary)
|
34
|
+
raise LoadError, "expected array-like column data; got: #{data.class}: #{data.inspect}"
|
35
|
+
end
|
36
|
+
data.map { |el| load_object(el) }
|
37
|
+
else
|
38
|
+
load_object(data)
|
39
|
+
end
|
40
|
+
object
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param object [loaded_class, Array[loaded_class]]
|
44
|
+
# @return [Hash, Array<Hash>]
|
45
|
+
def dump(object)
|
46
|
+
return nil if object.nil?
|
47
|
+
jsonifiable = begin
|
48
|
+
if @array
|
49
|
+
unless object.respond_to?(:to_ary)
|
50
|
+
raise DumpError, "expected array-like attribute; got: #{object.class}: #{object.inspect}"
|
51
|
+
end
|
52
|
+
object.map do |el|
|
53
|
+
dump_object(el)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
dump_object(object)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
jsonifiable
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# @param data [Hash]
|
65
|
+
# @return [loaded_class]
|
66
|
+
def load_object(data)
|
67
|
+
if data.respond_to?(:to_hash)
|
68
|
+
data = data.to_hash
|
69
|
+
good_keys = @loaded_class.members.map(&:to_s)
|
70
|
+
bad_keys = data.keys - good_keys
|
71
|
+
unless bad_keys.empty?
|
72
|
+
raise LoadError, "expected keys #{good_keys}; got unrecognized keys: #{bad_keys}"
|
73
|
+
end
|
74
|
+
instance = @loaded_class.new(*@loaded_class.members.map { |m| data[m.to_s] })
|
75
|
+
instance.arms_object_json_coder_keys_order = data.keys
|
76
|
+
instance
|
77
|
+
else
|
78
|
+
raise LoadError, "expected instance(s) of #{Hash}; got: #{data.class}: #{data.inspect}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# @param object [loaded_class]
|
83
|
+
# @return [Hash]
|
84
|
+
def dump_object(object)
|
85
|
+
if object.is_a?(@loaded_class)
|
86
|
+
keys = (object.arms_object_json_coder_keys_order || []) | @loaded_class.members.map(&:to_s)
|
87
|
+
keys.map { |member| {member => object[member]} }.inject({}, &:update)
|
88
|
+
else
|
89
|
+
raise TypeError, "expected instance(s) of #{@loaded_class}; got: #{object.class}: #{object.inspect}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/arms/version.rb
ADDED
data/test/arms_test.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class ARMSTest < Minitest::Test
|
4
|
+
def test_that_it_has_a_version_number
|
5
|
+
refute_nil ::ARMS::VERSION
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'ActiveRecord::Base.arms_serialize' do
|
10
|
+
describe 'serializing to JSON' do
|
11
|
+
it 'serializes json with JSON' do
|
12
|
+
Blog::Foo.create!(tags_const_json: {'#BlackLivesMatter' => {rank: 1}})
|
13
|
+
assert_equal(%q({"#BlackLivesMatter":{"rank":1}}), Blog::UnserializedFoo.last.tags_const_json)
|
14
|
+
end
|
15
|
+
it 'serializes json with :json' do
|
16
|
+
Blog::Foo.create!(tags_sym_json: {'#BlackLivesMatter' => {rank: 1}})
|
17
|
+
assert_equal(%q({"#BlackLivesMatter":{"rank":1}}), Blog::UnserializedFoo.last.tags_sym_json)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
describe 'serializing to YAML' do
|
21
|
+
it 'serializes yaml with YAML' do
|
22
|
+
Blog::Foo.create!(tags_const_yaml: {'#BlackLivesMatter' => {rank: 1}})
|
23
|
+
assert_equal(%Q(---\n"#BlackLivesMatter":\n :rank: 1\n), Blog::UnserializedFoo.last.tags_const_yaml)
|
24
|
+
end
|
25
|
+
it 'serializes yaml with :yaml' do
|
26
|
+
Blog::Foo.create!(tags_sym_yaml: {'#BlackLivesMatter' => {rank: 1}})
|
27
|
+
assert_equal(%Q(---\n"#BlackLivesMatter":\n :rank: 1\n), Blog::UnserializedFoo.last.tags_sym_yaml)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
describe 'deserializing with indifferent access' do
|
31
|
+
it 'deserializes yaml with string keys with indifferent access shortcut' do
|
32
|
+
Blog::UnserializedFoo.create!(tags_indifferent_yaml: %Q(---\n"#BlackLivesMatter":\n rank: 1\n))
|
33
|
+
assert_equal({'#BlackLivesMatter' => {'rank' => 1}}, Blog::Foo.last.tags_indifferent_yaml)
|
34
|
+
assert_instance_of(ActiveSupport::HashWithIndifferentAccess, Blog::Foo.last.tags_indifferent_yaml)
|
35
|
+
end
|
36
|
+
it 'deserializes yaml with symbol keys with indifferent access shortcut' do
|
37
|
+
Blog::UnserializedFoo.create!(tags_indifferent_yaml: %Q(---\n"#BlackLivesMatter":\n :rank: 1\n))
|
38
|
+
assert_equal({'#BlackLivesMatter' => {'rank' => 1}}, Blog::Foo.last.tags_indifferent_yaml)
|
39
|
+
assert_instance_of(ActiveSupport::HashWithIndifferentAccess, Blog::Foo.last.tags_indifferent_yaml)
|
40
|
+
end
|
41
|
+
it 'deserializes json with indifferent access shortcut' do
|
42
|
+
Blog::UnserializedFoo.create!(tags_indifferent_json: %q({"#BlackLivesMatter":{"rank":1}}))
|
43
|
+
assert_equal({'#BlackLivesMatter' => {'rank' => 1}}, Blog::Foo.last.tags_indifferent_json)
|
44
|
+
assert_instance_of(ActiveSupport::HashWithIndifferentAccess, Blog::Foo.last.tags_indifferent_json)
|
45
|
+
end
|
46
|
+
it 'deserializes json with ARMS::IndifferentHashesCoder' do
|
47
|
+
Blog::UnserializedFoo.create!(tags_const_indifferent_json: %q({"#BlackLivesMatter":{"rank":1,"x":[{"y":"z"}]}}))
|
48
|
+
assert_equal({'#BlackLivesMatter' => {'rank' => 1, 'x' => [{'y' => 'z'}]}}, Blog::Foo.last.tags_const_indifferent_json)
|
49
|
+
assert_instance_of(ActiveSupport::HashWithIndifferentAccess, Blog::Foo.last.tags_const_indifferent_json)
|
50
|
+
assert_instance_of(ActiveSupport::HashWithIndifferentAccess, Blog::Foo.last.tags_const_indifferent_json['#BlackLivesMatter']['x'][0])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
describe 'serializing with indifferent access' do
|
54
|
+
it 'serializes yaml with string keys with indifferent access shortcut' do
|
55
|
+
Blog::Foo.create!(tags_indifferent_yaml: {'#BlackLivesMatter' => {'rank' => 1}})
|
56
|
+
assert_equal(%Q(---\n"#BlackLivesMatter":\n rank: 1\n), Blog::UnserializedFoo.last.tags_indifferent_yaml)
|
57
|
+
end
|
58
|
+
it 'serializes yaml with symbol keys with indifferent access shortcut' do
|
59
|
+
Blog::Foo.create!(tags_indifferent_yaml: {'#BlackLivesMatter' => {rank: 1}})
|
60
|
+
assert_equal(%Q(---\n"#BlackLivesMatter":\n rank: 1\n), Blog::UnserializedFoo.last.tags_indifferent_yaml)
|
61
|
+
end
|
62
|
+
it 'serializes json with indifferent access shortcut' do
|
63
|
+
Blog::Foo.create!(tags_indifferent_json: {'#BlackLivesMatter' => {'rank' => 1}})
|
64
|
+
assert_equal(%q({"#BlackLivesMatter":{"rank":1}}), Blog::UnserializedFoo.last.tags_indifferent_json)
|
65
|
+
end
|
66
|
+
it 'serializes json with ARMS::IndifferentHashesCoder' do
|
67
|
+
Blog::Foo.create!(tags_const_indifferent_json: {'#BlackLivesMatter' => {rank: 1, x: [{y: 'z'}]}})
|
68
|
+
assert_equal(%q({"#BlackLivesMatter":{"rank":1,"x":[{"y":"z"}]}}), Blog::UnserializedFoo.last.tags_const_indifferent_json)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
describe 'deserializing to structs' do
|
72
|
+
it 'deserializes json array of tags to structs' do
|
73
|
+
Blog::UnserializedFoo.create!(tags_ary_struct_json: %q([{"name":"#BlackLivesMatter","rank":1}]))
|
74
|
+
tag = Blog::Foo.last.tags_ary_struct_json.last
|
75
|
+
assert_equal("#BlackLivesMatter", tag.name)
|
76
|
+
assert_equal(1, tag.rank)
|
77
|
+
assert_instance_of(Blog::Tag, tag)
|
78
|
+
end
|
79
|
+
it 'deserializes yaml array of tags to structs' do
|
80
|
+
Blog::UnserializedFoo.create!(tags_ary_struct_yaml: %Q(---\n- name: "#BlackLivesMatter"\n rank: 1\n))
|
81
|
+
tag = Blog::Foo.last.tags_ary_struct_yaml.last
|
82
|
+
assert_equal("#BlackLivesMatter", tag.name)
|
83
|
+
assert_equal(1, tag.rank)
|
84
|
+
assert_instance_of(Blog::Tag, tag)
|
85
|
+
end
|
86
|
+
it 'deserializes json array of tags to structs (tags_ary_struct_inst_json)' do
|
87
|
+
Blog::UnserializedFoo.create!(tags_ary_struct_inst_json: %q([{"name":"#BlackLivesMatter","rank":1}]))
|
88
|
+
tag = Blog::Foo.last.tags_ary_struct_inst_json.last
|
89
|
+
assert_equal("#BlackLivesMatter", tag.name)
|
90
|
+
assert_equal(1, tag.rank)
|
91
|
+
assert_instance_of(Blog::Tag, tag)
|
92
|
+
end
|
93
|
+
it 'deserializes yaml array of tags to structs (tags_ary_struct_inst_yaml)' do
|
94
|
+
Blog::UnserializedFoo.create!(tags_ary_struct_inst_yaml: %Q(---\n- name: "#BlackLivesMatter"\n rank: 1\n))
|
95
|
+
tag = Blog::Foo.last.tags_ary_struct_inst_yaml.last
|
96
|
+
assert_equal("#BlackLivesMatter", tag.name)
|
97
|
+
assert_equal(1, tag.rank)
|
98
|
+
assert_instance_of(Blog::Tag, tag)
|
99
|
+
end
|
100
|
+
it 'newest_tag' do
|
101
|
+
Blog::UnserializedFoo.create!(newest_tag: %Q({"name":"#arms","rank":5280}))
|
102
|
+
foo = Blog::Foo.last
|
103
|
+
assert_equal("#arms", foo.newest_tag.name)
|
104
|
+
assert_equal(5280, foo.newest_tag.rank)
|
105
|
+
assert_instance_of(Blog::Tag, foo.newest_tag)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
describe 'serializing to structs' do
|
109
|
+
it 'deserializes json array of tags to structs' do
|
110
|
+
Blog::Foo.create!(tags_ary_struct_json: [Blog::Tag.new('#BlackLivesMatter', 1)])
|
111
|
+
assert_equal(%q([{"name":"#BlackLivesMatter","rank":1}]), Blog::UnserializedFoo.last.tags_ary_struct_json)
|
112
|
+
end
|
113
|
+
it 'deserializes yaml array of tags to structs' do
|
114
|
+
Blog::Foo.create!(tags_ary_struct_yaml: [Blog::Tag.new('#BlackLivesMatter', 1)])
|
115
|
+
assert_equal(%Q(---\n- name: "#BlackLivesMatter"\n rank: 1\n), Blog::UnserializedFoo.last.tags_ary_struct_yaml)
|
116
|
+
end
|
117
|
+
it 'deserializes json array of tags to structs (tags_ary_struct_inst_json)' do
|
118
|
+
Blog::Foo.create!(tags_ary_struct_inst_json: [Blog::Tag.new('#BlackLivesMatter', 1)])
|
119
|
+
assert_equal(%q([{"name":"#BlackLivesMatter","rank":1}]), Blog::UnserializedFoo.last.tags_ary_struct_inst_json)
|
120
|
+
end
|
121
|
+
it 'deserializes yaml array of tags to structs (tags_ary_struct_inst_yaml)' do
|
122
|
+
Blog::Foo.create!(tags_ary_struct_inst_yaml: [Blog::Tag.new('#BlackLivesMatter', 1)])
|
123
|
+
assert_equal(%Q(---\n- name: "#BlackLivesMatter"\n rank: 1\n), Blog::UnserializedFoo.last.tags_ary_struct_inst_yaml)
|
124
|
+
end
|
125
|
+
it 'newest_tag' do
|
126
|
+
Blog::Foo.create!(newest_tag: Blog::Tag.new('#arms', 5280))
|
127
|
+
assert_equal(%Q({"name":"#arms","rank":5280}), Blog::UnserializedFoo.last.newest_tag)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
describe 'incorrect invocation' do
|
131
|
+
it 'raises when trying to pass arguments to a coder that is not a shortcut' do
|
132
|
+
err = assert_raises(ARMS::InvalidCoder) { Blog::Foo.arms_serialize('x', [ARMS::IndifferentHashesCoder.new, 3]) }
|
133
|
+
assert_match(%r(given shortcut arguments are not passed to the coder at index 0 which responds to #load and #dump\. coder: #<ARMS::IndifferentHashesCoder.*>; shortcut args: \[3\]), err.message)
|
134
|
+
end
|
135
|
+
it 'raises when given a coder that is not a coder' do
|
136
|
+
err = assert_raises(ARMS::InvalidCoder) { Blog::Foo.arms_serialize('x', "jsonify this pls") }
|
137
|
+
assert_equal("given coder at index 0 is not a recognized shortcut and does not respond to #load and #dump. coder: \"jsonify this pls\"; shortcut args: nil", err.message)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
data/test/blog_models.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'logger'
|
2
|
+
module Blog
|
3
|
+
logpath = Pathname.new('log/test.log')
|
4
|
+
FileUtils.mkdir_p(logpath.dirname)
|
5
|
+
-> (logger) { define_singleton_method(:logger) { logger } }.(::Logger.new(logpath))
|
6
|
+
logger.level = ::Logger::INFO
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'active_record'
|
10
|
+
ActiveRecord::Base.logger = Blog.logger
|
11
|
+
dbpath = Pathname.new('tmp/blog.sqlite3')
|
12
|
+
FileUtils.mkdir_p(dbpath.dirname)
|
13
|
+
dbpath.unlink if dbpath.exist?
|
14
|
+
ActiveRecord::Base.establish_connection({
|
15
|
+
:adapter => "sqlite3",
|
16
|
+
:database => dbpath,
|
17
|
+
})
|
18
|
+
|
19
|
+
ActiveRecord::Schema.define do
|
20
|
+
create_table :foos do |table|
|
21
|
+
table.column :tags_const_json, :string
|
22
|
+
table.column :tags_const_yaml, :string
|
23
|
+
table.column :tags_sym_json, :string
|
24
|
+
table.column :tags_sym_yaml, :string
|
25
|
+
table.column :tags_indifferent_json, :string
|
26
|
+
table.column :tags_indifferent_yaml, :string
|
27
|
+
table.column :tags_const_indifferent_json, :string
|
28
|
+
table.column :tags_ary_struct_json, :string
|
29
|
+
table.column :tags_ary_struct_yaml, :string
|
30
|
+
table.column :tags_ary_struct_inst_json, :string
|
31
|
+
table.column :tags_ary_struct_inst_yaml, :string
|
32
|
+
table.column :newest_tag, :string
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module Blog
|
37
|
+
Tag = Struct.new(:name, :rank)
|
38
|
+
|
39
|
+
class Foo < ActiveRecord::Base
|
40
|
+
arms_serialize :tags_const_json, JSON
|
41
|
+
arms_serialize :tags_const_yaml, YAML
|
42
|
+
arms_serialize :tags_sym_json, :json
|
43
|
+
arms_serialize :tags_sym_yaml, :yaml
|
44
|
+
arms_serialize :tags_indifferent_json, :indifferent_hashes, :json
|
45
|
+
arms_serialize :tags_indifferent_yaml, :indifferent_hashes, :yaml
|
46
|
+
arms_serialize :tags_const_indifferent_json, ARMS::IndifferentHashesCoder.new, JSON
|
47
|
+
arms_serialize :tags_ary_struct_json, [:struct, Blog::Tag, array: true], :json
|
48
|
+
arms_serialize :tags_ary_struct_yaml, [:struct, Blog::Tag, array: true], :yaml
|
49
|
+
arms_serialize :tags_ary_struct_inst_json, ARMS::StructCoder.new(Blog::Tag, array: true), :json
|
50
|
+
arms_serialize :tags_ary_struct_inst_yaml, ARMS::StructCoder.new(Blog::Tag, array: true), :yaml
|
51
|
+
arms_serialize :newest_tag, [:struct, Blog::Tag], :json
|
52
|
+
end
|
53
|
+
class UnserializedFoo < ActiveRecord::Base
|
54
|
+
self.table_name = 'foos'
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
describe ARMS::StructCoder do
|
4
|
+
let(:struct) { Struct.new(:foo, :bar) }
|
5
|
+
let(:options) { {} }
|
6
|
+
let(:struct_coder) { ARMS::StructCoder.new(struct, options) }
|
7
|
+
describe 'json' do
|
8
|
+
describe 'load' do
|
9
|
+
it 'loads nil' do
|
10
|
+
assert_nil(struct_coder.load(nil))
|
11
|
+
end
|
12
|
+
it 'loads a hash' do
|
13
|
+
assert_equal(struct.new('bar'), struct_coder.load({"foo" => "bar"}))
|
14
|
+
end
|
15
|
+
it 'loads something else' do
|
16
|
+
assert_raises(ARMS::LoadError) do
|
17
|
+
struct_coder.load([[]])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
it 'loads unrecognized keys' do
|
21
|
+
assert_raises(ARMS::LoadError) do
|
22
|
+
struct_coder.load({"uhoh" => "spaghettio"})
|
23
|
+
end
|
24
|
+
end
|
25
|
+
describe 'array' do
|
26
|
+
let(:options) { {array: true} }
|
27
|
+
it 'loads an array of hashes' do
|
28
|
+
data = [{"foo" => "bar"}, {"foo" => "baz"}]
|
29
|
+
assert_equal([struct.new('bar'), struct.new('baz')], struct_coder.load(data))
|
30
|
+
end
|
31
|
+
it 'loads an empty array' do
|
32
|
+
assert_equal([], struct_coder.load([]))
|
33
|
+
end
|
34
|
+
it 'does not load what is not an array of structs' do
|
35
|
+
assert_raises(ARMS::LoadError) { struct_coder.load({"foo" => "bar"}) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
describe 'dump' do
|
40
|
+
it 'dumps nil' do
|
41
|
+
assert_nil(struct_coder.dump(nil))
|
42
|
+
end
|
43
|
+
it 'dumps a struct' do
|
44
|
+
assert_equal({"foo" => "x", "bar" => "y"}, struct_coder.dump(struct.new('x', 'y')))
|
45
|
+
end
|
46
|
+
it 'dumps something else' do
|
47
|
+
assert_raises(TypeError) do
|
48
|
+
struct_coder.dump(Object.new)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
it 'dumps all the keys of a struct after loading in a partial one' do
|
52
|
+
struct = struct_coder.load({'foo' => 'who'})
|
53
|
+
assert_equal({'foo' => 'who', 'bar' => nil}, struct_coder.dump(struct))
|
54
|
+
struct.bar = 'whar'
|
55
|
+
assert_equal({'foo' => 'who', 'bar' => 'whar'}, struct_coder.dump(struct))
|
56
|
+
end
|
57
|
+
describe 'array' do
|
58
|
+
let(:options) { {array: true} }
|
59
|
+
it 'dumps an array of structs' do
|
60
|
+
structs = [struct.new('x', 'y'), struct.new('z', 'q')]
|
61
|
+
assert_equal([{"foo" => "x", "bar" => "y"}, {"foo" => "z", "bar" => "q"}], struct_coder.dump(structs))
|
62
|
+
end
|
63
|
+
it 'does not dump what is not an array of structs' do
|
64
|
+
assert_raises(ARMS::DumpError) { struct_coder.dump(struct.new('z', 'q')) }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
proc { |p| $:.unshift(p) unless $:.any? { |lp| File.expand_path(lp) == p } }.call(File.expand_path('../lib', File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
require 'coveralls'
|
4
|
+
if Coveralls.will_run?
|
5
|
+
Coveralls.wear!
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'simplecov'
|
9
|
+
require 'byebug'
|
10
|
+
|
11
|
+
# NO EXPECTATIONS
|
12
|
+
ENV["MT_NO_EXPECTATIONS"] = ''
|
13
|
+
|
14
|
+
require 'minitest/autorun'
|
15
|
+
require 'minitest/reporters'
|
16
|
+
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
|
17
|
+
|
18
|
+
class ARMSSpec < Minitest::Spec
|
19
|
+
end
|
20
|
+
|
21
|
+
# register this to be the base class for specs instead of Minitest::Spec
|
22
|
+
Minitest::Spec.register_spec_type(//, ARMSSpec)
|
23
|
+
|
24
|
+
require 'arms'
|
25
|
+
require_relative 'blog_models'
|
metadata
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: arms
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.pre.rc1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ethan
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-08-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '5.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest-reporters
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sqlite3
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.3'
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 1.3.6
|
107
|
+
type: :development
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - "~>"
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '1.3'
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: 1.3.6
|
117
|
+
description: A library which offers flexible, chained serializion for Active Record
|
118
|
+
email:
|
119
|
+
- ethan@unth.net
|
120
|
+
executables: []
|
121
|
+
extensions: []
|
122
|
+
extra_rdoc_files: []
|
123
|
+
files:
|
124
|
+
- ".simplecov"
|
125
|
+
- ".yardopts"
|
126
|
+
- LICENSE.txt
|
127
|
+
- README.md
|
128
|
+
- Rakefile.rb
|
129
|
+
- arms.gemspec
|
130
|
+
- lib/arms.rb
|
131
|
+
- lib/arms/indifferent_hashes_coder.rb
|
132
|
+
- lib/arms/multi_coder.rb
|
133
|
+
- lib/arms/struct_coder.rb
|
134
|
+
- lib/arms/version.rb
|
135
|
+
- test/arms_test.rb
|
136
|
+
- test/blog_models.rb
|
137
|
+
- test/struct_coder_test.rb
|
138
|
+
- test/test_helper.rb
|
139
|
+
homepage: https://github.com/notEthan/arms
|
140
|
+
licenses:
|
141
|
+
- MIT
|
142
|
+
metadata: {}
|
143
|
+
post_install_message:
|
144
|
+
rdoc_options: []
|
145
|
+
require_paths:
|
146
|
+
- lib
|
147
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
|
+
requirements:
|
154
|
+
- - ">"
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: 1.3.1
|
157
|
+
requirements: []
|
158
|
+
rubyforge_project:
|
159
|
+
rubygems_version: 2.7.8
|
160
|
+
signing_key:
|
161
|
+
specification_version: 4
|
162
|
+
summary: Active Record Multiple Serialization
|
163
|
+
test_files:
|
164
|
+
- test/arms_test.rb
|
165
|
+
- test/blog_models.rb
|
166
|
+
- test/struct_coder_test.rb
|
167
|
+
- test/test_helper.rb
|
168
|
+
- ".simplecov"
|