data_bindings 0.0.1
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.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/README.md +167 -0
- data/Rakefile +13 -0
- data/data_bindings.gemspec +35 -0
- data/lib/data_bindings/adapters/bson.rb +32 -0
- data/lib/data_bindings/adapters/json.rb +32 -0
- data/lib/data_bindings/adapters/native.rb +50 -0
- data/lib/data_bindings/adapters/params.rb +91 -0
- data/lib/data_bindings/adapters/ruby.rb +44 -0
- data/lib/data_bindings/adapters/tnetstring.rb +32 -0
- data/lib/data_bindings/adapters/xml.rb +74 -0
- data/lib/data_bindings/adapters/yaml.rb +26 -0
- data/lib/data_bindings/adapters.rb +12 -0
- data/lib/data_bindings/bound.rb +331 -0
- data/lib/data_bindings/converters.rb +83 -0
- data/lib/data_bindings/generator.rb +140 -0
- data/lib/data_bindings/unbound.rb +76 -0
- data/lib/data_bindings/util.rb +78 -0
- data/lib/data_bindings/version.rb +3 -0
- data/lib/data_bindings.rb +52 -0
- data/test/array_test.rb +55 -0
- data/test/bson_test.rb +23 -0
- data/test/converter_test.rb +36 -0
- data/test/data_bindings_test.rb +67 -0
- data/test/fixtures/1.json +1 -0
- data/test/json_test.rb +40 -0
- data/test/native_test.rb +64 -0
- data/test/params_test.rb +60 -0
- data/test/test_helper.rb +17 -0
- data/test/tnetstring_test.rb +23 -0
- data/test/validation_test.rb +217 -0
- data/test/xml_test.rb +20 -0
- data/test/yaml_test.rb +19 -0
- metadata +282 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Data bindings
|
|
2
|
+
|
|
3
|
+
There are many ways to represent data. For instance, XML, JSON and YAML are all very similar while having different representations.
|
|
4
|
+
Data bindings attempts to unify these various representations by allowing the creation of representation-free schemas which can be used to valiate a document. As well,
|
|
5
|
+
it provides adapters to normalize access across these various types.
|
|
6
|
+
|
|
7
|
+
Data bindings has four central concepts. *Adapters* provide normal access independent of representation. *Readers* allow you to define adapter-independent ways of reading data. *Writers* allows you define adapter-independent ways of writing data.*Validations* allow you to define a schema for your document.
|
|
8
|
+
|
|
9
|
+
## 5 minute demo
|
|
10
|
+
|
|
11
|
+
Start by loading from a JSON object
|
|
12
|
+
|
|
13
|
+
a = DataBindings.from_json('{"name":"Proust","books":[{"published":1913,"title":"Swan\'s Way"},{"published":1923,"title":"The Prisoner"}]}')
|
|
14
|
+
|
|
15
|
+
(You could also load from YAML, XML BSON, etc by using `#from_yaml`, `#from_xml`, `#from_bson` and so forth)
|
|
16
|
+
|
|
17
|
+
We can go ahead and access that like we nomrally would
|
|
18
|
+
|
|
19
|
+
a[:name]
|
|
20
|
+
# "proust"
|
|
21
|
+
a[:name][0][:title]
|
|
22
|
+
# "Swan's Way"
|
|
23
|
+
|
|
24
|
+
Great, now let's get a validated copy of that object
|
|
25
|
+
|
|
26
|
+
b = a.bind {
|
|
27
|
+
property :name, String
|
|
28
|
+
property :books, [] {
|
|
29
|
+
property :published, Integer
|
|
30
|
+
property :title, String
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Is it okay?
|
|
35
|
+
|
|
36
|
+
b.valid?
|
|
37
|
+
# => true
|
|
38
|
+
|
|
39
|
+
How about we represent it in YAML!
|
|
40
|
+
|
|
41
|
+
b.convert_to_yaml
|
|
42
|
+
# => "---\nname: Proust\nbooks:\n- published: 1913\n title: Swan's Way\n- published: 1923\n title: The Prisoner\n"
|
|
43
|
+
|
|
44
|
+
Or, right out to a YAML file
|
|
45
|
+
|
|
46
|
+
b.convert_to_yaml_file("/tmp/proust.yaml")
|
|
47
|
+
|
|
48
|
+
And load it back
|
|
49
|
+
|
|
50
|
+
from_file = DataBindings.from_yaml_file("/tmp/proust.yaml")
|
|
51
|
+
from_file.bind(a) # Use the binding from above
|
|
52
|
+
|
|
53
|
+
We can also define the types independently so that we can associate them with Ruby constructors later.
|
|
54
|
+
|
|
55
|
+
DataBindings.type(:book) {
|
|
56
|
+
property :published, Integer
|
|
57
|
+
property :title, String
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
DataBindings.type(:person) {
|
|
61
|
+
property :name
|
|
62
|
+
property :books, [:book]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
proust = DataBindings.from_yaml_file('/tmp/proust.yaml').bind(:person)
|
|
66
|
+
p proust[:name]
|
|
67
|
+
# => "Proust"
|
|
68
|
+
p proust[:books][1]
|
|
69
|
+
# => {"published"=>1923, "title"=>"The Prisoner"}
|
|
70
|
+
|
|
71
|
+
Maybe we also want to create a Ruby object out of person, let's do that.
|
|
72
|
+
|
|
73
|
+
class Person
|
|
74
|
+
attr_reader :name, :books
|
|
75
|
+
|
|
76
|
+
def initialize(name, books)
|
|
77
|
+
@name = name
|
|
78
|
+
@books = books
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def proust?
|
|
82
|
+
name.downcase == 'proust'
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
class Book
|
|
87
|
+
attr_reader :published, :title
|
|
88
|
+
|
|
89
|
+
def initialize(published, title)
|
|
90
|
+
@published, @title = published, title
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def published_before?(year)
|
|
94
|
+
published < year
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
DataBindings.for_native(:person) { |attrs| Person.new(attrs[:name], attrs[:books]) }
|
|
99
|
+
DataBindings.for_native(:book) { |attrs| Book.new(attrs[:published], attrs[:title]) }
|
|
100
|
+
|
|
101
|
+
proust = DataBindings.from_yaml_file('/tmp/proust.yaml').bind!(:person).to_native
|
|
102
|
+
proust.proust?
|
|
103
|
+
# => true
|
|
104
|
+
proust.books[0].published_before?(2011)
|
|
105
|
+
# => true
|
|
106
|
+
proust.books[0].published_before?(1800)
|
|
107
|
+
# => false
|
|
108
|
+
|
|
109
|
+
## Adapters
|
|
110
|
+
|
|
111
|
+
Adapters have a simple contract. They must be a module. They must define a method #from_* where * is a type. For example, the JSONAdapter provides `#from_json`. They must also provide a singleton method #construct that can serialize an object into it's target representation. They may provide other methods to your base generator; they are included into it and thus can access any of it's internals. They are typically expected to return a ruby hash or array. For instance:
|
|
112
|
+
|
|
113
|
+
a = DataBindings.from_json('{"Hello":"World"}')
|
|
114
|
+
# => {"Hello"=>"World"}
|
|
115
|
+
a.class
|
|
116
|
+
# => DataBindings::Adapters::Ruby::RubyObjectAdapter
|
|
117
|
+
|
|
118
|
+
## Binding
|
|
119
|
+
|
|
120
|
+
Bindings provide a mechanism to validate certain properties of a Hash.
|
|
121
|
+
|
|
122
|
+
To create a type, define it from your generator. For example:
|
|
123
|
+
|
|
124
|
+
DataBindings.type(:person) do
|
|
125
|
+
property :name, String
|
|
126
|
+
property :age, Integer
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
Would define a type for `:person`. This object would have two properties `name` and `age`. The types available are String, Integer, Float, DataBindings::Boolean. As well, you can refer to any of the types you've defined previously. You can refer to an implicit array of values by putting the type in `[]`. For example, you could have
|
|
130
|
+
|
|
131
|
+
DataBindings.type(:person) do
|
|
132
|
+
property :name, String
|
|
133
|
+
property :age, Integer
|
|
134
|
+
property :lottery_numbers, [Integer]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
## Readers
|
|
138
|
+
|
|
139
|
+
Readers provide an adapter-indepedent way of reading data from other sources. By default, we are also dealing with a String representation of the data. For instance:
|
|
140
|
+
|
|
141
|
+
DataBindings.from_json('{"Hello":"World"}')
|
|
142
|
+
|
|
143
|
+
would create a JSON representation. You could provide file access by adding a `file` reader.
|
|
144
|
+
|
|
145
|
+
DataBindings.reader(:file) { |f| File.read(f) }
|
|
146
|
+
|
|
147
|
+
Now, we could load the above JSON from disk by using
|
|
148
|
+
|
|
149
|
+
DataBindings.from_json_file('/tmp/file.json')
|
|
150
|
+
|
|
151
|
+
The `#from_json_file` method is synthesized into your generator by adding a `:file` reader. By default, there are readers for files, io, and http.
|
|
152
|
+
|
|
153
|
+
## Writers
|
|
154
|
+
|
|
155
|
+
Writers provide an adapter-indepedent way of writing data to other sources. By default, we emit our representation of the data as a String. For instance:
|
|
156
|
+
|
|
157
|
+
DataBindings.from_ruby({"Hello" => "World"}).convert_to_yaml
|
|
158
|
+
|
|
159
|
+
would create a YAML representation. You could provide file writing by adding a `file` writer.
|
|
160
|
+
|
|
161
|
+
DataBindings.reader(:file) { |obj, f| File.open(f, 'w') { |h| h << obj } }
|
|
162
|
+
|
|
163
|
+
Now, if you wanted to write the above JSON to disk as YAML, you could do the following:
|
|
164
|
+
|
|
165
|
+
DataBindings.from_ruby({"Hello" => "World"}).convert_to_file(:yaml, "/tmp/out.yaml")
|
|
166
|
+
|
|
167
|
+
The `#convert_to_file` method that would be synthesized into your generator. By default, there are writers for files, io, and http.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
2
|
+
require 'rake/testtask'
|
|
3
|
+
require 'yard'
|
|
4
|
+
|
|
5
|
+
task :test do
|
|
6
|
+
Rake::TestTask.new do |t|
|
|
7
|
+
Dir['test/**/*_test.rb'].each{|f| require File.expand_path(f)}
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
YARD::Rake::YardocTask.new do |t|
|
|
12
|
+
t.files = ['lib/**/*.rb'] # optional
|
|
13
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "data_bindings/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "data_bindings"
|
|
7
|
+
s.version = DataBindings::VERSION
|
|
8
|
+
s.authors = ["Joshual Hull"]
|
|
9
|
+
s.email = ["joshbuddy@gmail.com"]
|
|
10
|
+
s.homepage = "http://github.com/joshbuddy/data_bindings"
|
|
11
|
+
s.summary = %q{Bind data to and from things}
|
|
12
|
+
s.description = %q{Bind data to and from things.}
|
|
13
|
+
|
|
14
|
+
s.rubyforge_project = "data_bindings"
|
|
15
|
+
|
|
16
|
+
s.files = `git ls-files`.split("\n")
|
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
19
|
+
s.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
s.add_runtime_dependency 'hashie', '= 2.0.0.beta'
|
|
22
|
+
|
|
23
|
+
# specify any dependencies here; for example:
|
|
24
|
+
s.add_development_dependency 'bson'
|
|
25
|
+
s.add_development_dependency 'multi_json'
|
|
26
|
+
s.add_development_dependency 'nokogiri'
|
|
27
|
+
s.add_development_dependency 'builder'
|
|
28
|
+
s.add_development_dependency 'httparty'
|
|
29
|
+
s.add_development_dependency "minitest"
|
|
30
|
+
s.add_development_dependency "rake"
|
|
31
|
+
s.add_development_dependency "fakeweb"
|
|
32
|
+
s.add_development_dependency "yard"
|
|
33
|
+
s.add_development_dependency "redcarpet"
|
|
34
|
+
s.add_development_dependency "tnetstring"
|
|
35
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module DataBindings
|
|
2
|
+
module Adapters
|
|
3
|
+
module BSON
|
|
4
|
+
include Ruby
|
|
5
|
+
include DataBindings::GemRequirement
|
|
6
|
+
|
|
7
|
+
# Constructs a wrapped object from a JSON string
|
|
8
|
+
# @param [String] str The JSON object
|
|
9
|
+
# @return [RubyObjectAdapter, RubyArrayAdapter] The wrapped object
|
|
10
|
+
def from_bson(str)
|
|
11
|
+
from_ruby(::BSON.deserialize(str.unpack("C*")))
|
|
12
|
+
end
|
|
13
|
+
gentle_require_gem :from_bson, 'bson'
|
|
14
|
+
|
|
15
|
+
module Convert
|
|
16
|
+
include ConverterHelper
|
|
17
|
+
include DataBindings::GemRequirement
|
|
18
|
+
|
|
19
|
+
# Creates a String repsentation of a Ruby Hash or Array.
|
|
20
|
+
# @param [Generator] generator The generator that invokes this constructor
|
|
21
|
+
# @param [Symbol] name The name of the binding used on this object
|
|
22
|
+
# @param [Array, Hash] obj The object to be represented in JSON
|
|
23
|
+
# @return [String] The JSON representation of this object
|
|
24
|
+
def force_convert_to_bson
|
|
25
|
+
::BSON.serialize(self).to_s
|
|
26
|
+
end
|
|
27
|
+
gentle_require_gem :force_convert_to_bson, 'bson'
|
|
28
|
+
standard_converter :convert_to_bson
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module DataBindings
|
|
2
|
+
module Adapters
|
|
3
|
+
module JSON
|
|
4
|
+
include Ruby
|
|
5
|
+
include DataBindings::GemRequirement
|
|
6
|
+
|
|
7
|
+
# Constructs a wrapped object from a JSON string
|
|
8
|
+
# @param [String] str The JSON object
|
|
9
|
+
# @return [RubyObjectAdapter, RubyArrayAdapter] The wrapped object
|
|
10
|
+
def from_json(str)
|
|
11
|
+
from_ruby(MultiJson.decode(str))
|
|
12
|
+
end
|
|
13
|
+
gentle_require_gem :from_json, 'multi_json'
|
|
14
|
+
|
|
15
|
+
module Convert
|
|
16
|
+
include ConverterHelper
|
|
17
|
+
include DataBindings::GemRequirement
|
|
18
|
+
|
|
19
|
+
# Creates a String repsentation of a Ruby Hash or Array.
|
|
20
|
+
# @param [Generator] generator The generator that invokes this constructor
|
|
21
|
+
# @param [Symbol] name The name of the binding used on this object
|
|
22
|
+
# @param [Array, Hash] obj The object to be represented in JSON
|
|
23
|
+
# @return [String] The JSON representation of this object
|
|
24
|
+
def force_convert_to_json
|
|
25
|
+
MultiJson.encode(self)
|
|
26
|
+
end
|
|
27
|
+
gentle_require_gem :force_convert_to_json, 'multi_json'
|
|
28
|
+
standard_converter :convert_to_json
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module DataBindings
|
|
2
|
+
module Adapters
|
|
3
|
+
module Native
|
|
4
|
+
# Constructs a wrapped object from a native Ruby object. This object is expected
|
|
5
|
+
# to respond to calls similar to those defined by #attr_accessor
|
|
6
|
+
# @param [Object] obj The object to be wrapped
|
|
7
|
+
# @return [NativeArrayAdapter, NativeObjectAdapter] The wrapped object
|
|
8
|
+
def from_native(obj)
|
|
9
|
+
binding_class(NativeAdapter).new(self, obj)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class NativeAdapter
|
|
13
|
+
include Unbound
|
|
14
|
+
|
|
15
|
+
def initialize(generator, object)
|
|
16
|
+
@generator, @object = generator, object
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def pre_convert
|
|
20
|
+
raise DataBindings::UnboundError unless @name
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def type
|
|
24
|
+
@object.is_a?(Array) ? :array : :hash
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def [](idx)
|
|
28
|
+
val = @object.respond_to?(:[]) ? @object[idx] : @object.send(idx)
|
|
29
|
+
if DataBindings.primitive_value?(val)
|
|
30
|
+
val
|
|
31
|
+
else
|
|
32
|
+
binding_class(NativeAdapter).new(@generator, val)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def []=(idx, value)
|
|
37
|
+
@object.respond_to?(:[]=) ? @object[idx] = value : @object.send("#{idx}=", value)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def key?(name)
|
|
41
|
+
@object.respond_to?(name)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def to_hash
|
|
45
|
+
raise UnboundError
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require 'cgi'
|
|
2
|
+
|
|
3
|
+
module DataBindings
|
|
4
|
+
module Adapters
|
|
5
|
+
module Params
|
|
6
|
+
include Ruby
|
|
7
|
+
|
|
8
|
+
def from_params(str)
|
|
9
|
+
from_ruby( parse_nested_query(str) )
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse_nested_query(qs, d = nil)
|
|
14
|
+
params = {}
|
|
15
|
+
|
|
16
|
+
(qs || '').split(d ? /[#{d}] */n : /[&;] */n).each do |p|
|
|
17
|
+
k, v = p.split('=', 2).map { |s| CGI::unescape(s) }
|
|
18
|
+
normalize_params(params, k, v)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
return params
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
def normalize_params(params, name, v = nil)
|
|
26
|
+
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
|
|
27
|
+
k = $1 || ''
|
|
28
|
+
after = $' || ''
|
|
29
|
+
|
|
30
|
+
return if k.empty?
|
|
31
|
+
|
|
32
|
+
if after == ""
|
|
33
|
+
params[k] = v
|
|
34
|
+
elsif after == "[]"
|
|
35
|
+
params[k] ||= []
|
|
36
|
+
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
|
37
|
+
params[k] << v
|
|
38
|
+
elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
|
|
39
|
+
child_key = $1
|
|
40
|
+
params[k] ||= []
|
|
41
|
+
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
|
42
|
+
if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
|
|
43
|
+
normalize_params(params[k].last, child_key, v)
|
|
44
|
+
else
|
|
45
|
+
params[k] << normalize_params({}, child_key, v)
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
params[k] ||= {}
|
|
49
|
+
raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
|
|
50
|
+
params[k] = normalize_params(params[k], after, v)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
return params
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
module Convert
|
|
57
|
+
include ConverterHelper
|
|
58
|
+
|
|
59
|
+
# Creates a String repsentation of a Ruby Hash or Array.
|
|
60
|
+
# @param [Generator] generator The generator that invokes this constructor
|
|
61
|
+
# @param [Symbol] name The name of the binding used on this object
|
|
62
|
+
# @param [Array, Hash] obj The object to be represented in JSON
|
|
63
|
+
# @return [String] The JSON representation of this object
|
|
64
|
+
def force_convert_to_params
|
|
65
|
+
build_nested_query(to_hash)
|
|
66
|
+
end
|
|
67
|
+
standard_converter :convert_to_params
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def build_nested_query(value, prefix = nil)
|
|
72
|
+
case value
|
|
73
|
+
when Array
|
|
74
|
+
index = 0
|
|
75
|
+
value.map { |v|
|
|
76
|
+
query_string = build_nested_query(v, prefix ? "#{prefix}[#{index}]" : index)
|
|
77
|
+
index += 1
|
|
78
|
+
query_string
|
|
79
|
+
}.join("&")
|
|
80
|
+
when Hash
|
|
81
|
+
value.map { |k, v|
|
|
82
|
+
build_nested_query(v, prefix ? "#{prefix}[#{CGI::escape(k)}]" : CGI::escape(k))
|
|
83
|
+
}.join("&")
|
|
84
|
+
else
|
|
85
|
+
"#{prefix}=#{CGI::escape(value.to_s)}"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module DataBindings
|
|
2
|
+
module Adapters
|
|
3
|
+
module Ruby
|
|
4
|
+
|
|
5
|
+
# Constructs a wrapped object from an Array or Hash
|
|
6
|
+
# @param [Array, Hash] obj The Ruby array or hash
|
|
7
|
+
# @return [RubyObjectAdapter, RubyArrayAdapter] The wrapped object
|
|
8
|
+
def from_ruby(obj)
|
|
9
|
+
case obj
|
|
10
|
+
when Array then from_ruby_array(obj)
|
|
11
|
+
when Hash then from_ruby_hash(obj)
|
|
12
|
+
else obj
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def from_ruby_hash(h)
|
|
17
|
+
binding_class(RubyObjectAdapter).new(self, h)
|
|
18
|
+
end
|
|
19
|
+
alias_method :from_ruby_object, :from_ruby_hash
|
|
20
|
+
|
|
21
|
+
def from_ruby_array(a)
|
|
22
|
+
binding_class(RubyArrayAdapter).new(self, a)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class RubyArrayAdapter < Array
|
|
26
|
+
include Unbound
|
|
27
|
+
|
|
28
|
+
def initialize(generator, o)
|
|
29
|
+
@generator = generator
|
|
30
|
+
super o
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class RubyObjectAdapter < IndifferentHash
|
|
35
|
+
include Unbound
|
|
36
|
+
|
|
37
|
+
def initialize(generator, o)
|
|
38
|
+
@generator = generator
|
|
39
|
+
replace o
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|