compositor 0.1.1 → 0.1.3
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/.pairs +1 -0
- data/README.md +49 -30
- data/compositor.gemspec +2 -0
- data/lib/compositor.rb +15 -9
- data/lib/compositor/base.rb +22 -10
- data/lib/compositor/composite.rb +19 -30
- data/lib/compositor/dsl.rb +11 -14
- data/lib/compositor/leaf.rb +6 -22
- data/lib/compositor/literal.rb +14 -0
- data/lib/compositor/{hash.rb → map.rb} +1 -1
- data/lib/compositor/version.rb +1 -1
- data/spec/compositor/base_spec.rb +13 -0
- data/spec/compositor/dsl_spec.rb +27 -0
- data/spec/compositor/hash_spec.rb +61 -0
- data/spec/compositor/leaf_spec.rb +34 -0
- data/spec/compositor/list_spec.rb +61 -0
- data/spec/compositor/literal_spec.rb +12 -0
- data/spec/compositor/performance_spec.rb +10 -11
- data/spec/support/sample_dsl.rb +25 -23
- metadata +33 -7
- data/spec/compositor/compositor_spec.rb +0 -168
data/.pairs
CHANGED
data/README.md
CHANGED
@@ -24,30 +24,40 @@ Or install it yourself as:
|
|
24
24
|
|
25
25
|
## Usage
|
26
26
|
|
27
|
-
For each model that needs a hash/json representation you need to create a ruby class that subclasses Composite::Leaf
|
28
|
-
adds some custom state that's important for rendering that object
|
27
|
+
For each model that needs a hash/json representation you need to create a ruby class that subclasses ```Composite::Leaf```,
|
28
|
+
adds some custom state that's important for rendering that object in addition to ```view_context```, and implement the ```#to_hash```
|
29
|
+
method.
|
30
|
+
|
31
|
+
The ```view_context``` variable is a reference to an object holding necessary helpers for generating JSON, for example
|
32
|
+
view_context is automatically available inside Rails controllers, and contains helper methods necessary to generate application URLs.
|
33
|
+
Outside of Rails application, ```view_context``` can be any other object holding application helpers or state. All
|
34
|
+
subclasses of ```Compositor::Leaf``` inherit view_context reference, and can use it to construct Hash representations.
|
35
|
+
|
36
|
+
We recommend you place your Compositor classes in eg ```app/compositors/*``` directory, that has one compositor
|
37
|
+
class per model class you will be rendering. Example below would be ```app/compositors/user.rb```, a compositor class
|
38
|
+
wrapping ```User``` model.
|
29
39
|
|
30
40
|
```ruby
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
41
|
+
# The actual class name "User" is converted into a DSL method named "user", shown later.
|
42
|
+
|
43
|
+
class UserCompositor < Compositor::Leaf
|
44
|
+
attr_accessor :user
|
45
|
+
|
46
|
+
def initialize(context, user, attrs = {})
|
47
|
+
super(context, attrs)
|
48
|
+
self.user = user
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_hash
|
52
|
+
{
|
53
|
+
id: user.id,
|
54
|
+
username: user.username,
|
55
|
+
location: user.location,
|
56
|
+
bio: user.bio,
|
57
|
+
url: user.url,
|
58
|
+
image_url: context.image_path(user.avatar),
|
59
|
+
...
|
60
|
+
}
|
51
61
|
end
|
52
62
|
end
|
53
63
|
```
|
@@ -56,30 +66,34 @@ This small class automatically registers "user" DSL method, which receives a use
|
|
56
66
|
important attributes.
|
57
67
|
|
58
68
|
Then this class can be merged with other similar "leaf" classes, or another "composite" class, such as
|
59
|
-
Composite::
|
69
|
+
Composite::Map or Composite::List to create a Hash or an Array as the top-level JSON data structure.
|
60
70
|
|
61
71
|
Once the tree of composite objects has been setup, calling #to_hash on the top level object quickly
|
62
72
|
generates hash by walking the tree and merging everything together.
|
63
73
|
|
74
|
+
In the example below, application defines also ```StoreCompositor```, ```ProductCompositor``` classes
|
75
|
+
that similar to ```UserCompositor``` return hash representations of each model object.
|
76
|
+
|
64
77
|
```ruby
|
65
78
|
|
66
|
-
|
67
|
-
|
68
|
-
store store, root: :store
|
69
|
-
user
|
70
|
-
list collection: products, root: :products do |p|
|
79
|
+
compositor = Compositor::DSL.create(context) do
|
80
|
+
map do
|
81
|
+
store @store, root: :store
|
82
|
+
user @user, root: :user
|
83
|
+
list collection: @products, root: :products do |p|
|
71
84
|
product p
|
72
85
|
end
|
73
86
|
end
|
74
87
|
end
|
75
88
|
|
76
|
-
puts
|
89
|
+
puts compositor.to_hash # =>
|
77
90
|
|
78
91
|
{
|
79
92
|
:store => {
|
80
93
|
id: 12354,
|
81
94
|
name: "amazon.com",
|
82
95
|
url: "http://www.amazon.com",
|
96
|
+
|
83
97
|
..
|
84
98
|
},
|
85
99
|
:user => {
|
@@ -87,7 +101,8 @@ generates hash by walking the tree and merging everything together.
|
|
87
101
|
username: "kigster",
|
88
102
|
location: "San Francisco",
|
89
103
|
bio: "",
|
90
|
-
url: ""
|
104
|
+
url: "",
|
105
|
+
image_url: "http://cdn-app.domain.com/kigster/avatar/200.jpg"
|
91
106
|
},
|
92
107
|
:products => {
|
93
108
|
[ id: 1234, :name => "Awesome Product", ... ],
|
@@ -96,6 +111,10 @@ generates hash by walking the tree and merging everything together.
|
|
96
111
|
}
|
97
112
|
```
|
98
113
|
|
114
|
+
The context is an object that can contain helpers, instance variables, or anything that can be used
|
115
|
+
within leaves. For example, you can pass in the view_context from the controller to get access to any
|
116
|
+
Rails routes or helpers.
|
117
|
+
|
99
118
|
## Contributing
|
100
119
|
|
101
120
|
1. Fork it
|
data/compositor.gemspec
CHANGED
data/lib/compositor.rb
CHANGED
@@ -3,22 +3,28 @@ require 'compositor/version'
|
|
3
3
|
module Compositor
|
4
4
|
end
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
unless "".respond_to?(:constantize)
|
7
|
+
class String
|
8
|
+
def constantize
|
9
|
+
camel_cased_word = self
|
10
|
+
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
|
11
|
+
raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
+
Object.module_eval("::#{$1}", __FILE__, __LINE__)
|
15
|
+
end
|
14
16
|
end
|
17
|
+
end
|
15
18
|
|
16
|
-
|
17
|
-
|
19
|
+
unless "".respond_to?(:underscore)
|
20
|
+
class String
|
21
|
+
def underscore
|
22
|
+
self.gsub(/::/, '/').
|
18
23
|
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
|
19
24
|
gsub(/([a-z\d])([A-Z])/, '\1_\2').
|
20
25
|
tr("-", "_").
|
21
26
|
downcase
|
27
|
+
end
|
22
28
|
end
|
23
29
|
end
|
24
30
|
|
data/lib/compositor/base.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
module Compositor
|
2
2
|
class Base
|
3
3
|
attr_reader :attrs
|
4
|
-
attr_accessor :root, :
|
4
|
+
attr_accessor :root, :context
|
5
5
|
|
6
6
|
def initialize(view_context, attrs = {})
|
7
7
|
@attrs = attrs
|
8
|
-
self.
|
8
|
+
self.context = view_context
|
9
9
|
attrs.each_pair do |key, value|
|
10
10
|
self.send("#{key}=", value)
|
11
11
|
end
|
@@ -23,10 +23,6 @@ module Compositor
|
|
23
23
|
to_hash
|
24
24
|
end
|
25
25
|
|
26
|
-
def collection_to_generator(klazz, collection)
|
27
|
-
collection.map { |o| klazz.new(view_context) }
|
28
|
-
end
|
29
|
-
|
30
26
|
def to_json(options = {})
|
31
27
|
Oj.dump(to_hash)
|
32
28
|
end
|
@@ -40,13 +36,29 @@ module Compositor
|
|
40
36
|
end
|
41
37
|
|
42
38
|
def self.root_class_name(klazz)
|
43
|
-
klazz.name.gsub(
|
39
|
+
klazz.name.gsub(/(.*::)|(Compositor$)/, '').underscore
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.inherited(subclass)
|
43
|
+
method_name = root_class_name(subclass)
|
44
|
+
unless method_name.eql?("base") # check if it's already defined
|
45
|
+
Compositor::DSL.send(:define_method, method_name) do |*args, &block|
|
46
|
+
subclass.
|
47
|
+
new(@context, *args).
|
48
|
+
dsl(self, &block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def dsl
|
54
|
+
raise "Implement in subclasses"
|
44
55
|
end
|
45
56
|
end
|
46
57
|
end
|
47
58
|
|
48
|
-
require_relative 'leaf'
|
49
|
-
require_relative 'composite'
|
50
59
|
require_relative 'dsl'
|
60
|
+
require_relative 'composite'
|
61
|
+
require_relative 'leaf'
|
62
|
+
require_relative 'literal'
|
51
63
|
require_relative 'list'
|
52
|
-
require_relative '
|
64
|
+
require_relative 'map'
|
data/lib/compositor/composite.rb
CHANGED
@@ -2,8 +2,9 @@ module Compositor
|
|
2
2
|
class Composite < Compositor::Base
|
3
3
|
attr_accessor :collection, :renderer
|
4
4
|
|
5
|
-
def initialize(view_context,
|
5
|
+
def initialize(view_context, args = {})
|
6
6
|
super
|
7
|
+
@collection_set = true if args.has_key?(:collection)
|
7
8
|
self.collection ||= []
|
8
9
|
end
|
9
10
|
|
@@ -13,41 +14,29 @@ module Compositor
|
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
|
-
def map_collection
|
17
|
-
self.collection = self.collection.map do |item|
|
18
|
-
yield item
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
17
|
def composite?
|
23
18
|
true
|
24
19
|
end
|
25
20
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
composite.collection = []
|
38
|
-
args[:collection].each do |object|
|
39
|
-
self.instance_exec(object, &block)
|
40
|
-
end
|
41
|
-
elsif block
|
42
|
-
self.instance_eval &block
|
43
|
-
end
|
44
|
-
|
45
|
-
if original_generator
|
46
|
-
self.generator = original_generator
|
47
|
-
self.generator.collection << composite
|
48
|
-
end
|
21
|
+
def dsl(dsl, &block)
|
22
|
+
original_generator = dsl.generator
|
23
|
+
|
24
|
+
dsl.generator = self
|
25
|
+
|
26
|
+
if self.collection && @collection_set && block
|
27
|
+
# reset collection, we'll be mapping it via a block
|
28
|
+
unmapped_collection = collection
|
29
|
+
self.collection = []
|
30
|
+
unmapped_collection.each do |object|
|
31
|
+
dsl.instance_exec(object, &block)
|
49
32
|
end
|
33
|
+
elsif block
|
34
|
+
dsl.instance_eval &block
|
50
35
|
end
|
36
|
+
|
37
|
+
dsl.generator = original_generator if original_generator
|
38
|
+
|
39
|
+
dsl.generator.collection << self if dsl.generator != self
|
51
40
|
end
|
52
41
|
end
|
53
42
|
end
|
data/lib/compositor/dsl.rb
CHANGED
@@ -1,26 +1,23 @@
|
|
1
1
|
module Compositor
|
2
2
|
class DSL
|
3
|
-
attr_reader :
|
4
|
-
attr_accessor :
|
3
|
+
attr_reader :context
|
4
|
+
attr_accessor :generator
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
@
|
6
|
+
def initialize(context)
|
7
|
+
@context = context
|
8
8
|
end
|
9
9
|
|
10
|
-
def self.create(
|
11
|
-
dsl = new(
|
10
|
+
def self.create(context, &block)
|
11
|
+
dsl = new(context)
|
12
|
+
context.instance_variables.each do |variable|
|
13
|
+
dsl.instance_variable_set(variable, context.instance_variable_get(variable))
|
14
|
+
end
|
12
15
|
dsl.instance_eval &block if block
|
13
16
|
dsl
|
14
17
|
end
|
15
18
|
|
16
|
-
def
|
17
|
-
|
18
|
-
pagination_url: params[:pagination_url],
|
19
|
-
params: params[:api_params]
|
20
|
-
end
|
21
|
-
|
22
|
-
def to_json
|
23
|
-
generator.to_json
|
19
|
+
def to_json(options = {})
|
20
|
+
generator.to_json(options)
|
24
21
|
end
|
25
22
|
|
26
23
|
def to_hash
|
data/lib/compositor/leaf.rb
CHANGED
@@ -1,14 +1,5 @@
|
|
1
1
|
module Compositor
|
2
2
|
class Leaf < Compositor::Base
|
3
|
-
attr_accessor :object
|
4
|
-
|
5
|
-
def initialize(view_context, object = {}, args = {})
|
6
|
-
if object.is_a?(::Hash)
|
7
|
-
super(view_context, object)
|
8
|
-
else
|
9
|
-
super(view_context, {object: object}.merge!(args))
|
10
|
-
end
|
11
|
-
end
|
12
3
|
|
13
4
|
def root
|
14
5
|
if @root.is_a?(Symbol)
|
@@ -24,19 +15,12 @@ module Compositor
|
|
24
15
|
false
|
25
16
|
end
|
26
17
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
raise "Leaves should be called within composite" unless self.generator.composite?
|
34
|
-
self.generator.collection << leaf
|
35
|
-
else
|
36
|
-
self.generator = leaf
|
37
|
-
end
|
38
|
-
leaf
|
39
|
-
end
|
18
|
+
def dsl(dsl)
|
19
|
+
if dsl.generator
|
20
|
+
raise "Leaves should be called within composite" unless dsl.generator.composite?
|
21
|
+
dsl.generator.collection << self
|
22
|
+
else
|
23
|
+
dsl.generator = self
|
40
24
|
end
|
41
25
|
end
|
42
26
|
end
|
data/lib/compositor/version.rb
CHANGED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Compositor::Base do
|
4
|
+
describe "#root_class_name" do
|
5
|
+
it "returns the underscored class name" do
|
6
|
+
Compositor::Base.root_class_name(Object).should == "object"
|
7
|
+
end
|
8
|
+
|
9
|
+
it "returns the underscored class name with compositor stripped out" do
|
10
|
+
Compositor::Base.root_class_name(DslStringCompositor).should == "dsl_string"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Compositor::DSL do
|
4
|
+
|
5
|
+
let(:context) { Object.new }
|
6
|
+
|
7
|
+
describe '#empty' do
|
8
|
+
it 'returns the default type' do
|
9
|
+
dsl = Compositor::DSL.create(context) do
|
10
|
+
end
|
11
|
+
|
12
|
+
nil.should == dsl.to_hash
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'instance variables' do
|
17
|
+
it 'allows instance variables from the view context to be accessed in the dsl evaluation' do
|
18
|
+
context.instance_eval do
|
19
|
+
@blah = 1
|
20
|
+
end
|
21
|
+
|
22
|
+
Compositor::DSL.create(context) do
|
23
|
+
raise "Instance variable should be available in DSL" unless @blah == 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Compositor::Map do
|
4
|
+
let(:context) { Object.new }
|
5
|
+
|
6
|
+
it 'returns the generated map' do
|
7
|
+
expected = {
|
8
|
+
tests: {
|
9
|
+
num1: {number: 1},
|
10
|
+
num2: {number: 2},
|
11
|
+
num3: {number: 3}
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
dsl = Compositor::DSL.create(context)
|
16
|
+
dsl.map root: :tests do
|
17
|
+
dsl.dsl_int 1, root: :num1
|
18
|
+
dsl.dsl_int 2, root: :num2
|
19
|
+
dsl.dsl_int 3, root: :num3
|
20
|
+
end
|
21
|
+
|
22
|
+
expected.should == dsl.to_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns the generated deeply nested map without explicit receiver' do
|
26
|
+
expected = {
|
27
|
+
tests: {
|
28
|
+
num1: {number: 1},
|
29
|
+
num2: {number: 2},
|
30
|
+
num3: {number: 3},
|
31
|
+
stuff: [
|
32
|
+
{number: 10},
|
33
|
+
{number: 11}
|
34
|
+
]
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
dsl = Compositor::DSL.create(context)
|
39
|
+
dsl.map root: :tests do
|
40
|
+
dsl_int 1, root: :num1
|
41
|
+
dsl_int 2, root: :num2
|
42
|
+
dsl_int 3, root: :num3
|
43
|
+
list :root => :stuff do
|
44
|
+
dsl_int 10
|
45
|
+
dsl_int 11
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
expected.should == dsl.to_hash
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#empty' do
|
53
|
+
it 'returns the default type' do
|
54
|
+
dsl = Compositor::DSL.create(context) do
|
55
|
+
map collection: [], root: false
|
56
|
+
end
|
57
|
+
|
58
|
+
{}.should == dsl.to_hash
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Compositor::Leaf do
|
4
|
+
let(:context) { Object.new }
|
5
|
+
|
6
|
+
describe '#create' do
|
7
|
+
it "defines #dsl_string method on the DSL class" do
|
8
|
+
dsl = Compositor::DSL.create(context)
|
9
|
+
dsl.should respond_to(:dsl_string)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "allows calling defined methods via a block" do
|
13
|
+
block_ran = false
|
14
|
+
Compositor::DSL.create(context) do |dsl|
|
15
|
+
dsl.dsl_string
|
16
|
+
block_ran = true
|
17
|
+
end
|
18
|
+
block_ran.should be_true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns last generator called via block" do
|
22
|
+
dsl = Compositor::DSL.create(context) do |dsl|
|
23
|
+
dsl.dsl_string
|
24
|
+
end
|
25
|
+
|
26
|
+
{a: "b"}.should == dsl.to_hash
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns an instance of subclass" do
|
30
|
+
dsl = Compositor::DSL.create(context).dsl_string
|
31
|
+
dsl.should be_kind_of(DslStringCompositor)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Compositor::List do
|
4
|
+
let(:context) { Object.new }
|
5
|
+
|
6
|
+
it 'returns the generated array with the explicit receiver' do
|
7
|
+
integers = [1, 2, 3]
|
8
|
+
expected = {
|
9
|
+
tests: [
|
10
|
+
{number: 1},
|
11
|
+
{number: 2},
|
12
|
+
{number: 3}
|
13
|
+
]
|
14
|
+
}
|
15
|
+
|
16
|
+
dsl = Compositor::DSL.create(context)
|
17
|
+
dsl.list root: :tests, collection: integers do |item|
|
18
|
+
dsl.dsl_int item
|
19
|
+
end
|
20
|
+
|
21
|
+
dsl.to_hash.should == expected
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns the generated array with explicit receiver' do
|
25
|
+
integers = [1, 2, 3]
|
26
|
+
expected = {
|
27
|
+
tests: [
|
28
|
+
{number: 1},
|
29
|
+
{number: 2},
|
30
|
+
{number: 3}
|
31
|
+
]
|
32
|
+
}
|
33
|
+
|
34
|
+
dsl = Compositor::DSL.create(context)
|
35
|
+
dsl.list root: :tests, collection: integers do |item|
|
36
|
+
dsl_int item
|
37
|
+
end
|
38
|
+
|
39
|
+
expected.should == dsl.to_hash
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#empty' do
|
43
|
+
it 'returns the default type' do
|
44
|
+
dsl = Compositor::DSL.create(context) do
|
45
|
+
list collection: [], root: false
|
46
|
+
end
|
47
|
+
|
48
|
+
[].should == dsl.to_hash
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'doesnt execute the block if an empty collection is passed' do
|
52
|
+
dsl = Compositor::DSL.create(context) do
|
53
|
+
list collection: [], root: false do |p|
|
54
|
+
raise "Shouldnt be a DSL" if p.instance_of?(Compositor::DSL)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
[].should == dsl.to_hash
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe 'Performance' do
|
4
4
|
|
5
|
-
let(:
|
5
|
+
let(:context) { Object.new }
|
6
6
|
let(:permitted_dsl_performance_penalty) { 60 } # 60% slower is allowed, any slower is not.)
|
7
7
|
|
8
8
|
describe 'generating DSL' do
|
@@ -15,9 +15,9 @@ describe 'Performance' do
|
|
15
15
|
dsl = nil
|
16
16
|
output = Benchmark.measure do
|
17
17
|
10000.times do
|
18
|
-
dsl = Compositor::DSL.create(
|
19
|
-
|
20
|
-
dsl_string "hello"
|
18
|
+
dsl = Compositor::DSL.create(context) do |dsl|
|
19
|
+
map do
|
20
|
+
dsl_string string: "hello"
|
21
21
|
dsl_int 3
|
22
22
|
list collection: [1, 2, 3], root: :numbers do |number|
|
23
23
|
dsl_int number
|
@@ -30,15 +30,14 @@ describe 'Performance' do
|
|
30
30
|
|
31
31
|
@timing[:dsl] = output.to_s.to_f
|
32
32
|
|
33
|
-
cmp = nil
|
34
33
|
output = Benchmark.measure do
|
35
34
|
10000.times do
|
36
|
-
string =
|
37
|
-
int =
|
38
|
-
list = Compositor::List.new(
|
35
|
+
string = DslStringCompositor.new(context)
|
36
|
+
int = DslIntCompositor.new(context, 3)
|
37
|
+
list = Compositor::List.new(context,
|
39
38
|
root: :numbers,
|
40
|
-
collection: [1, 2, 3].map! { |n|
|
41
|
-
cmp = Compositor::
|
39
|
+
collection: [1, 2, 3].map! { |n| DslIntCompositor.new(context, n) })
|
40
|
+
cmp = Compositor::Map.new(context, collection: [string, int, list])
|
42
41
|
cmp.to_hash.should == {:a => "b", :number => 3, :numbers => [{:number => 1}, {:number => 2}, {:number => 3}]}
|
43
42
|
end
|
44
43
|
end
|
data/spec/support/sample_dsl.rb
CHANGED
@@ -1,33 +1,35 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
def to_hash
|
4
|
-
{
|
5
|
-
a: "b"
|
6
|
-
}
|
7
|
-
end
|
8
|
-
end
|
1
|
+
class DslStringCompositor < Compositor::Leaf
|
2
|
+
attr_accessor :string
|
9
3
|
|
10
|
-
|
11
|
-
|
4
|
+
def to_hash
|
5
|
+
{
|
6
|
+
a: "b"
|
7
|
+
}
|
8
|
+
end
|
9
|
+
end
|
12
10
|
|
13
|
-
|
14
|
-
|
15
|
-
end
|
11
|
+
class DslIntCompositor < Compositor::Leaf
|
12
|
+
attr_accessor :number
|
16
13
|
|
17
|
-
|
18
|
-
|
19
|
-
{
|
20
|
-
number: @number
|
21
|
-
}
|
22
|
-
end
|
23
|
-
end
|
14
|
+
def initialize(view_context, number, attrs = {})
|
15
|
+
super(view_context, {number: number}.merge!(attrs))
|
24
16
|
end
|
25
17
|
|
26
|
-
|
27
|
-
|
18
|
+
def to_hash
|
19
|
+
with_root_element do
|
28
20
|
{
|
29
|
-
|
21
|
+
number: @number
|
30
22
|
}
|
31
23
|
end
|
32
24
|
end
|
33
25
|
end
|
26
|
+
|
27
|
+
class DslObjectCompositor < Compositor::Leaf
|
28
|
+
attr_accessor :object
|
29
|
+
|
30
|
+
def to_hash
|
31
|
+
{
|
32
|
+
a: object
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: compositor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,8 +10,24 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-05-
|
13
|
+
date: 2013-05-31 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: oj
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
15
31
|
- !ruby/object:Gem::Dependency
|
16
32
|
name: rspec
|
17
33
|
requirement: !ruby/object:Gem::Requirement
|
@@ -83,14 +99,20 @@ files:
|
|
83
99
|
- lib/compositor/base.rb
|
84
100
|
- lib/compositor/composite.rb
|
85
101
|
- lib/compositor/dsl.rb
|
86
|
-
- lib/compositor/hash.rb
|
87
102
|
- lib/compositor/leaf.rb
|
88
103
|
- lib/compositor/list.rb
|
104
|
+
- lib/compositor/literal.rb
|
105
|
+
- lib/compositor/map.rb
|
89
106
|
- lib/compositor/renderer/base.rb
|
90
107
|
- lib/compositor/renderer/iterator.rb
|
91
108
|
- lib/compositor/renderer/merged.rb
|
92
109
|
- lib/compositor/version.rb
|
93
|
-
- spec/compositor/
|
110
|
+
- spec/compositor/base_spec.rb
|
111
|
+
- spec/compositor/dsl_spec.rb
|
112
|
+
- spec/compositor/hash_spec.rb
|
113
|
+
- spec/compositor/leaf_spec.rb
|
114
|
+
- spec/compositor/list_spec.rb
|
115
|
+
- spec/compositor/literal_spec.rb
|
94
116
|
- spec/compositor/performance_spec.rb
|
95
117
|
- spec/spec_helper.rb
|
96
118
|
- spec/support/sample_dsl.rb
|
@@ -114,14 +136,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
136
|
version: '0'
|
115
137
|
requirements: []
|
116
138
|
rubyforge_project:
|
117
|
-
rubygems_version: 1.8.
|
139
|
+
rubygems_version: 1.8.25
|
118
140
|
signing_key:
|
119
141
|
specification_version: 3
|
120
142
|
summary: Composite design pattern with a convenient DSL for building JSON/Hashes of
|
121
143
|
complex objects
|
122
144
|
test_files:
|
123
|
-
- spec/compositor/
|
145
|
+
- spec/compositor/base_spec.rb
|
146
|
+
- spec/compositor/dsl_spec.rb
|
147
|
+
- spec/compositor/hash_spec.rb
|
148
|
+
- spec/compositor/leaf_spec.rb
|
149
|
+
- spec/compositor/list_spec.rb
|
150
|
+
- spec/compositor/literal_spec.rb
|
124
151
|
- spec/compositor/performance_spec.rb
|
125
152
|
- spec/spec_helper.rb
|
126
153
|
- spec/support/sample_dsl.rb
|
127
|
-
has_rdoc:
|
@@ -1,168 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe Compositor::DSL do
|
4
|
-
|
5
|
-
let(:view_context) { Object.new }
|
6
|
-
|
7
|
-
describe '#create' do
|
8
|
-
it "defines #dsl_string method on the DSL class" do
|
9
|
-
dsl = Compositor::DSL.create(view_context)
|
10
|
-
dsl.should respond_to(:dsl_string)
|
11
|
-
end
|
12
|
-
|
13
|
-
it "allows calling defined methods via a block" do
|
14
|
-
block_ran = false
|
15
|
-
Compositor::DSL.create(view_context) do |dsl|
|
16
|
-
dsl.dsl_string
|
17
|
-
block_ran = true
|
18
|
-
end
|
19
|
-
block_ran.should be_true
|
20
|
-
end
|
21
|
-
|
22
|
-
it "returns last generator called via block" do
|
23
|
-
dsl = Compositor::DSL.create(view_context) do |dsl|
|
24
|
-
dsl.dsl_string
|
25
|
-
end
|
26
|
-
|
27
|
-
{a: "b"}.should == dsl.to_hash
|
28
|
-
end
|
29
|
-
|
30
|
-
it "returns an instance of subclass" do
|
31
|
-
dsl = Compositor::DSL.create(view_context).dsl_string
|
32
|
-
dsl.should be_kind_of(Compositor::Leaf::DslString)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
describe '#composite' do
|
37
|
-
it 'returns the generated array with the explicit receiver' do
|
38
|
-
integers = [1, 2, 3]
|
39
|
-
expected = {
|
40
|
-
tests: [
|
41
|
-
{number: 1},
|
42
|
-
{number: 2},
|
43
|
-
{number: 3}
|
44
|
-
]
|
45
|
-
}
|
46
|
-
|
47
|
-
dsl = Compositor::DSL.create(view_context)
|
48
|
-
dsl.list root: :tests, collection: integers do |item|
|
49
|
-
dsl.dsl_int item
|
50
|
-
end
|
51
|
-
|
52
|
-
dsl.to_hash.should == expected
|
53
|
-
end
|
54
|
-
|
55
|
-
it 'returns the generated hash' do
|
56
|
-
expected = {
|
57
|
-
tests: {
|
58
|
-
num1: {number: 1},
|
59
|
-
num2: {number: 2},
|
60
|
-
num3: {number: 3}
|
61
|
-
}
|
62
|
-
}
|
63
|
-
|
64
|
-
dsl = Compositor::DSL.create(view_context)
|
65
|
-
dsl.hash root: :tests do
|
66
|
-
dsl.dsl_int 1, root: :num1
|
67
|
-
dsl.dsl_int 2, root: :num2
|
68
|
-
dsl.dsl_int 3, root: :num3
|
69
|
-
end
|
70
|
-
|
71
|
-
expected.should == dsl.to_hash
|
72
|
-
end
|
73
|
-
|
74
|
-
it 'returns the generated deeply nested hash' do
|
75
|
-
expected = {
|
76
|
-
tests: {
|
77
|
-
num1: {number: 1},
|
78
|
-
num2: {number: 2},
|
79
|
-
num3: {number: 3},
|
80
|
-
stuff: [
|
81
|
-
{number: 10},
|
82
|
-
{number: 11}
|
83
|
-
]
|
84
|
-
}
|
85
|
-
}
|
86
|
-
|
87
|
-
dsl = Compositor::DSL.create(view_context)
|
88
|
-
dsl.hash root: :tests do
|
89
|
-
dsl.dsl_int 1, root: :num1
|
90
|
-
dsl.dsl_int 2, root: :num2
|
91
|
-
dsl.dsl_int 3, root: :num3
|
92
|
-
dsl.list :root => :stuff do
|
93
|
-
dsl.dsl_int 10
|
94
|
-
dsl.dsl_int 11
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
expected.should == dsl.to_hash
|
99
|
-
end
|
100
|
-
|
101
|
-
it 'returns the generated deeply nested hash without explicit receiver' do
|
102
|
-
expected = {
|
103
|
-
tests: {
|
104
|
-
num1: {number: 1},
|
105
|
-
num2: {number: 2},
|
106
|
-
num3: {number: 3},
|
107
|
-
stuff: [
|
108
|
-
{number: 10},
|
109
|
-
{number: 11}
|
110
|
-
]
|
111
|
-
}
|
112
|
-
}
|
113
|
-
|
114
|
-
dsl = Compositor::DSL.create(view_context)
|
115
|
-
dsl.hash root: :tests do
|
116
|
-
dsl_int 1, root: :num1
|
117
|
-
dsl_int 2, root: :num2
|
118
|
-
dsl_int 3, root: :num3
|
119
|
-
list :root => :stuff do
|
120
|
-
dsl_int 10
|
121
|
-
dsl_int 11
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
expected.should == dsl.to_hash
|
126
|
-
end
|
127
|
-
|
128
|
-
it 'returns the generated array with explicit receiver' do
|
129
|
-
integers = [1, 2, 3]
|
130
|
-
expected = {
|
131
|
-
tests: [
|
132
|
-
{number: 1},
|
133
|
-
{number: 2},
|
134
|
-
{number: 3}
|
135
|
-
]
|
136
|
-
}
|
137
|
-
|
138
|
-
dsl = Compositor::DSL.create(view_context)
|
139
|
-
dsl.list root: :tests, collection: integers do |item|
|
140
|
-
dsl_int item
|
141
|
-
end
|
142
|
-
|
143
|
-
expected.should == dsl.to_hash
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
describe '#empty' do
|
148
|
-
it 'returns the default type' do
|
149
|
-
dsl = Compositor::DSL.create(view_context) do
|
150
|
-
end
|
151
|
-
|
152
|
-
nil.should == dsl.to_hash
|
153
|
-
|
154
|
-
dsl = Compositor::DSL.create(view_context) do
|
155
|
-
hash collection: [], root: false
|
156
|
-
end
|
157
|
-
|
158
|
-
{}.should == dsl.to_hash
|
159
|
-
|
160
|
-
dsl = Compositor::DSL.create(view_context) do
|
161
|
-
list collection: [], root: false
|
162
|
-
end
|
163
|
-
|
164
|
-
[].should == dsl.to_hash
|
165
|
-
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|