shape 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7721ae7dec761e6fc7df7c801c2fa4e2176d07d4
4
+ data.tar.gz: ccbbc447f0038018e40a1f1e75d8e2c4444a5562
5
+ SHA512:
6
+ metadata.gz: 878acd9eab21ba6c2732285de0b2810f71038b801764060ccf29c5eee1222b8e4c7cf5f43cfa80423d90b5d9d40a74ec4d3be0ab64697576fb2e1e4d24698c72
7
+ data.tar.gz: aa47f8d4ac3e7bcd5e08332f111d7b706e63810d70ea8bc86775b9279da5da02e13c22bdcb6f23802b4e525c074085ac1b9b76b3b7feca0d4df0392a646b2288
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'awesome_print'
7
+ gem 'rspec'
8
+ gem 'rspec-rails'
9
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # Shape
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'shape'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install shape
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,12 @@
1
+ require 'active_support/all'
2
+ require 'shape/version'
3
+ require 'shape/base'
4
+ require 'shape/property_shaper'
5
+ require 'shape/data_visitor'
6
+ require 'shape/renderers'
7
+
8
+ module Shape
9
+ extend ActiveSupport::Concern
10
+ include Shape::Base
11
+ include Shape::Renderers
12
+ end
@@ -0,0 +1,130 @@
1
+ require 'active_support/concern'
2
+
3
+ module Shape
4
+ module Base
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+ attr_accessor :_source
9
+ attr_accessor :_parent
10
+
11
+ protected :_parent
12
+
13
+ def initialize(source = nil, options = {})
14
+ self._source = source
15
+ self._parent = options.delete(:parent)
16
+ self.delegate_properties_from
17
+ end
18
+
19
+ #def source_name
20
+ #_source.class.name
21
+ #end
22
+ #protected :source_name
23
+ #
24
+
25
+ protected
26
+
27
+ def delegate_properties_from
28
+ self.class._properties_from.each do |from, except|
29
+ Array(_source.send(from)).each do |name|
30
+ unless except.include?(name.to_sym)
31
+ property(name) do
32
+ from do
33
+ _source.send(:[], name)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+
42
+ module ClassMethods
43
+
44
+ def shape(source, options={})
45
+ self.new(source, options)
46
+ end
47
+
48
+ def shaper_context
49
+ @shaper_context || self
50
+ end
51
+
52
+ # Expose a property as {<property_name>: "..."}
53
+ # To expose a property using a different attribute on the resource:
54
+ #
55
+ # property :display, from: :display_name
56
+ #
57
+ # To expose a property with inline definition:
58
+ #
59
+ # property :display do
60
+ # from do
61
+ # #{last_name}, #{first_name}
62
+ # end
63
+ # end
64
+ #
65
+ # To expose a decorated collection:
66
+ #
67
+ # property :practices, with: PracticeDecorator
68
+ #
69
+ #
70
+ # To expose a decorated collection with view context
71
+ #
72
+ # property :practices, with: PracticeDecorator, context: {view: :summary}
73
+ #
74
+ def property(property_name, options={}, &block)
75
+ properties[property_name] = Shape::PropertyShaper.new(
76
+ shaper_context, property_name, options, &block
77
+ )
78
+ end
79
+ alias_method :association, :property
80
+
81
+ def properties_from(name, options={})
82
+ except = Array(options[:except])
83
+ _properties_from << [name, except]
84
+ end
85
+
86
+ def associations
87
+ @associations ||= {}
88
+ end
89
+
90
+ def properties
91
+ @properties ||= {}
92
+ end
93
+
94
+ def _properties_from
95
+ @properties_from ||= []
96
+ end
97
+
98
+ # @overload delegate(*methods, options = {})
99
+ # Overrides {http://api.rubyonrails.org/classes/Module.html#method-i-delegate Module.delegate}
100
+ # to make `:_source` the default delegation target.
101
+ #
102
+ # @return [void]
103
+ def delegate(*methods)
104
+ options = methods.extract_options!
105
+ super *methods, options.reverse_merge(to: :_source)
106
+ end
107
+
108
+ def shape_collection(collection, options = {})
109
+ Array(collection).map do |item|
110
+ self.shape(item, options.clone)
111
+ end
112
+ end
113
+
114
+ protected
115
+ def delegate_property(name)
116
+ if !shaper_context.method_defined?(name)
117
+ shaper_context.delegate(name)
118
+ shaper_context.send(:protected, name)
119
+ end
120
+ end
121
+
122
+
123
+ end
124
+ # Allows properties to be added in an instance..
125
+ include ClassMethods
126
+ def shaper_context
127
+ @shaper_context || self.class
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,32 @@
1
+ module Shape
2
+ module DataVisitor
3
+
4
+ def visit(visitor = lambda {|x| x})
5
+ data_visitor(properties_to_visit, visitor)
6
+ end
7
+
8
+ protected
9
+
10
+ def properties_to_visit
11
+ self.class.properties.merge(self.properties)
12
+ end
13
+
14
+ def data_visitor(properties = self.properties_to_visit, visitor = lambda {|x| x})
15
+ properties.each_with_object({}) do |(name, property), obj|
16
+ if property.options.present? && property.options[:with]
17
+ result = self.send(name)
18
+ if result.respond_to?(:visit)
19
+ obj[name] = result.visit(visitor)
20
+ elsif result.is_a?(Enumerable)
21
+ obj[name] = result.each_with_object([]) do |item, results|
22
+ results << item.visit(visitor)
23
+ end
24
+ end
25
+ else
26
+ obj[name] = visitor.call(self.send(name))
27
+ end
28
+ end
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,82 @@
1
+ module Shape
2
+ # = Property Shaper
3
+ # Keeps track of property info and context
4
+ # when shaping shaper views.
5
+ #
6
+ # We'll use the PropertyShaper objects
7
+ # later to recursively build the data.
8
+ #
9
+ # Allows anything inside a property block
10
+ # to call methods in the context of the
11
+ # Shape dsl allowing for nested properties.
12
+ #
13
+ # Example:
14
+ #
15
+ # property :address do
16
+ # property :street_address do
17
+ # property :addr_line1
18
+ # property :addr_line2
19
+ # end
20
+ # property :city
21
+ # # ...
22
+ # end
23
+ class PropertyShaper
24
+ include Shape::Base::ClassMethods
25
+
26
+ attr_accessor :name
27
+ attr_accessor :shaper_context
28
+ attr_accessor :options
29
+
30
+ def initialize(shaper_context, name, options={}, &block)
31
+ self.shaper_context = shaper_context
32
+ self.name = name
33
+ self.options = options
34
+
35
+ if block
36
+ instance_eval(&block)
37
+ else
38
+ from = options[:from] || name
39
+ define_accessor(name, from)
40
+ delegate_property(from)
41
+ end
42
+ end
43
+
44
+ def from(&block)
45
+ unless shaper_context.method_defined?(name.to_sym)
46
+ shaper_context.send(:define_method, name, &block)
47
+ end
48
+ end
49
+
50
+ protected
51
+
52
+ def define_accessor(name, source_name)
53
+ if !shaper_context.method_defined?(name.to_sym)
54
+ _options = self.options
55
+ self.from do
56
+ return nil unless _source
57
+ result = begin
58
+ _source.send(source_name)
59
+ rescue NoMethodError
60
+ # If source doesn't have a corresponding method, try accessing it
61
+ # via element accessor.
62
+ if _source.respond_to?(:[])
63
+ _source.send(:[], source_name)
64
+ else
65
+ raise
66
+ end
67
+ end
68
+ if with = _options[:with]
69
+ if result.respond_to?(:join)
70
+ with.shape_collection(result, parent: self)
71
+ else
72
+ with.shape(result, parent: self)
73
+ end
74
+ else
75
+ result
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,14 @@
1
+ module Shape
2
+ module Renderers
3
+ include Shape::DataVisitor
4
+
5
+ def as_json(*args)
6
+ visit(lambda {|x| x.as_json})
7
+ end
8
+
9
+ def to_hash(*args)
10
+ visit
11
+ end
12
+ alias_method :to_h, :to_hash
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Shape
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,55 @@
1
+ module Shape
2
+ # = View Decorator
3
+ # Allows for creating different, composable
4
+ # decorator views
5
+ #
6
+ # Example:
7
+ #
8
+ # property :href
9
+ # property :addr_line1
10
+ # property :addr_line2
11
+ # property :city
12
+ # # ...
13
+ #
14
+ # view :with_lat_long do
15
+ # property :latitude
16
+ # property :longitude
17
+ # end
18
+ #
19
+ # view :with_facilities do
20
+ # link :facilities
21
+ # end
22
+ #
23
+ # view :full do
24
+ # view :with_lat_long # nests with_lat_long view
25
+ # view :with_facilities # nests with_facilities view
26
+ # end
27
+ #
28
+ #
29
+ # To use a decorator view, pass the view in to
30
+ # the decorator context. For example:
31
+ #
32
+ # @address = AddressDecorator.new(
33
+ # Address.find(params[:id]),
34
+ # context: {view: :full})
35
+ #
36
+ #
37
+ class ViewDecorator
38
+ include Shape::Base::ClassMethods
39
+ attr_accessor :name
40
+ attr_accessor :decorator_context
41
+ attr_accessor :options
42
+
43
+ def initialize(decorator_context, name, options={}, &block)
44
+ self.decorator_context = decorator_context
45
+ self.name = name
46
+ self.options = options
47
+
48
+ if block
49
+ instance_eval(&block)
50
+ else
51
+ views[name] = decorator_context.views[name]
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'shape/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'shape'
7
+ s.version = Shape::VERSION
8
+ s.summary = 'Shape your api'
9
+ s.description = 'Shape your api. Extracted from Vitals Platform.'
10
+ s.authors = ['Robin Curry', 'Brandon Westcott', 'Tim Morgan']
11
+ s.email = ['robin.curry@vitals.com', 'brandon.westcott@vitals.com', 'tim.morgan@vitals.com']
12
+ s.homepage = 'https://github.com/robincurry/shape'
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = ["lib"]
17
+
18
+ s.add_dependency 'activesupport', '>= 3.0'
19
+ end
@@ -0,0 +1,186 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Property child associations" do
4
+
5
+ context 'Given an object with method attributes' do
6
+
7
+ let(:source) {
8
+ OpenStruct.new.tap do |person|
9
+ person.name = 'John Smith'
10
+ person.age = 34
11
+ person.ssn = 123456789
12
+ person.children = [
13
+ OpenStruct.new.tap do |child|
14
+ child.name = 'Jimmy Smith'
15
+ end,
16
+ OpenStruct.new.tap do |child|
17
+ child.name = 'Jane Smith'
18
+ end,
19
+ ]
20
+ end
21
+ }
22
+
23
+ context 'and Parent and Child Shape decorators' do
24
+
25
+ before do
26
+ stub_const('ChildDecorator', Class.new do
27
+ include Shape::Base
28
+ property :name
29
+ end)
30
+
31
+ stub_const('ParentDecorator', Class.new do
32
+ include Shape::Base
33
+ property :name
34
+ property :years_of_age, from: :age
35
+
36
+ association :children, with: ChildDecorator
37
+ end)
38
+ end
39
+
40
+ context 'when shaped by the decorator' do
41
+
42
+ subject {
43
+ ParentDecorator.new(source)
44
+ }
45
+
46
+ it 'exposes and shapes children associations' do
47
+ expect(subject.children.map(&:name)).to eq(['Jimmy Smith', 'Jane Smith'])
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+
56
+ context 'Given a hash with attributes' do
57
+
58
+ let(:source) {
59
+ {
60
+ name: 'John Smith',
61
+ age: 34,
62
+ ssn: 123456789,
63
+ children: [
64
+ {
65
+ name: 'Jimmy Smith'
66
+ },
67
+ {
68
+ name: 'Jane Smith'
69
+ }
70
+ ]
71
+ }
72
+ }
73
+
74
+
75
+ context 'and Parent and Child Shape decorators' do
76
+
77
+ before do
78
+ stub_const('ChildDecorator', Class.new do
79
+ include Shape::Base
80
+ property :legal_name, from: :name
81
+ end)
82
+
83
+ stub_const('ParentDecorator', Class.new do
84
+ include Shape::Base
85
+ property :name
86
+ property :years_of_age, from: :age
87
+
88
+ association :children, with: ChildDecorator
89
+ end)
90
+ end
91
+
92
+ context 'when shaped by the decorator' do
93
+
94
+ subject {
95
+ ParentDecorator.new(source)
96
+ }
97
+
98
+ it 'exposes and shapes children associations' do
99
+ expect(subject.children.map(&:legal_name)).to eq(['Jimmy Smith', 'Jane Smith'])
100
+ end
101
+
102
+ end
103
+
104
+ end
105
+
106
+ end
107
+
108
+ context 'Given a hash with deeply nested attributes' do
109
+
110
+ let(:source) {
111
+ {
112
+ name: 'John Smith',
113
+ age: 34,
114
+ ssn: 123456789,
115
+ children: [
116
+ {
117
+ name: 'Jimmy Smith',
118
+ children: [
119
+ {
120
+ name: 'Suzy Smith'
121
+ },
122
+ {
123
+ name: 'Sally Smith'
124
+ }
125
+ ]
126
+ },
127
+ {
128
+ name: 'Jane Smith',
129
+ children: [
130
+ {
131
+ name: 'Sam Smith'
132
+ },
133
+ {
134
+ name: 'Tim Smith'
135
+ },
136
+ ]
137
+ }
138
+ ]
139
+ }
140
+ }
141
+
142
+
143
+ context 'and Parent and Child Shape decorators' do
144
+
145
+ before do
146
+ stub_const('NestedChildDecorator', Class.new do
147
+ include Shape::Base
148
+ property :name
149
+ end)
150
+
151
+ stub_const('ChildDecorator', Class.new do
152
+ include Shape::Base
153
+ property :legal_name, from: :name
154
+ association :children, with: NestedChildDecorator
155
+ end)
156
+
157
+ stub_const('ParentDecorator', Class.new do
158
+ include Shape::Base
159
+ property :name
160
+ property :years_of_age, from: :age
161
+
162
+ association :children, with: ChildDecorator
163
+ end)
164
+ end
165
+
166
+ context 'when shaped by the decorator' do
167
+
168
+ subject {
169
+ ParentDecorator.new(source)
170
+ }
171
+
172
+ it 'exposes and shapes children associations' do
173
+ expect(subject.children.map(&:legal_name)).to eq(['Jimmy Smith', 'Jane Smith'])
174
+ end
175
+
176
+ it 'exposes and shapes nested children associations' do
177
+ expect(subject.children.first.children.map(&:name)).to eq(['Suzy Smith', 'Sally Smith'])
178
+ end
179
+
180
+ end
181
+
182
+ end
183
+
184
+ end
185
+
186
+ end
@@ -0,0 +1,217 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Shape::Base do
4
+
5
+ context 'Given an object with method attributes' do
6
+
7
+ let(:source) {
8
+ OpenStruct.new.tap do |person|
9
+ person.name = 'John Smith'
10
+ person.age = 34
11
+ person.ssn = 123456789
12
+ person.children = [
13
+ OpenStruct.new.tap do |child|
14
+ child.name = 'Jimmy Smith'
15
+ end,
16
+ OpenStruct.new.tap do |child|
17
+ child.name = 'Jane Smith'
18
+ end,
19
+ ]
20
+ end
21
+ }
22
+
23
+ context 'and a Shape decorator' do
24
+
25
+ before do
26
+ stub_const('MockDecorator', Class.new do
27
+ include Shape::Base
28
+ property :name
29
+ property :years_of_age, from: :age
30
+ end)
31
+
32
+ end
33
+
34
+ context 'when shaped by the decorator' do
35
+
36
+ subject {
37
+ MockDecorator.new(source)
38
+ }
39
+
40
+ it 'exposes defined properties from source' do
41
+ expect(subject.name).to eq('John Smith')
42
+ end
43
+
44
+ it 'exposes defined properties renamed from source' do
45
+ expect(subject.years_of_age).to eq(34)
46
+ end
47
+
48
+ it 'does not expose unspecified attributes' do
49
+ expect(subject).to_not respond_to(:ssn)
50
+ expect(subject).to_not respond_to(:age)
51
+ end
52
+ end
53
+ end
54
+
55
+ context 'and Parent and Child Shape decorators' do
56
+
57
+ before do
58
+ stub_const('ChildDecorator', Class.new do
59
+ include Shape::Base
60
+ property :name
61
+ end)
62
+
63
+ stub_const('ParentDecorator', Class.new do
64
+ include Shape::Base
65
+ property :name
66
+ property :years_of_age, from: :age
67
+
68
+ association :children, with: ChildDecorator
69
+ end)
70
+ end
71
+
72
+ context 'when shaped by the decorator' do
73
+
74
+ subject {
75
+ ParentDecorator.new(source)
76
+ }
77
+
78
+ it 'exposes and shapes children associations' do
79
+ expect(subject.children.map(&:name)).to eq(['Jimmy Smith', 'Jane Smith'])
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+
88
+ context 'Given a hash with method attributes' do
89
+
90
+ let(:source) {
91
+ {
92
+ name: 'John Smith',
93
+ age: 34,
94
+ ssn: 123456789,
95
+ children: [
96
+ {
97
+ name: 'Jimmy Smith'
98
+ },
99
+ {
100
+ name: 'Jane Smith'
101
+ }
102
+ ]
103
+ }
104
+ }
105
+
106
+ context 'and a Shape decorator' do
107
+
108
+ before do
109
+ stub_const('MockDecorator', Class.new do
110
+ include Shape::Base
111
+ property :name
112
+ property :years_of_age, from: :age
113
+ end)
114
+
115
+ end
116
+
117
+ context 'when shaped by the decorator' do
118
+
119
+ subject {
120
+ MockDecorator.new(source)
121
+ }
122
+
123
+ it 'exposes defined properties from source' do
124
+ expect(subject.name).to eq('John Smith')
125
+ end
126
+
127
+ it 'exposes defined properties renamed from source' do
128
+ expect(subject.years_of_age).to eq(34)
129
+ end
130
+
131
+ it 'does not expose unspecified attributes' do
132
+ expect(subject).to_not respond_to(:ssn)
133
+ expect(subject).to_not respond_to(:age)
134
+ end
135
+ end
136
+
137
+ end
138
+
139
+ context 'and Parent and Child Shape decorators' do
140
+
141
+ before do
142
+ stub_const('ChildDecorator', Class.new do
143
+ include Shape::Base
144
+ property :legal_name, from: :name
145
+ end)
146
+
147
+ stub_const('ParentDecorator', Class.new do
148
+ include Shape::Base
149
+ property :name
150
+ property :years_of_age, from: :age
151
+
152
+ association :children, with: ChildDecorator
153
+ end)
154
+ end
155
+
156
+ context 'when shaped by the decorator' do
157
+
158
+ subject {
159
+ ParentDecorator.new(source)
160
+ }
161
+
162
+ it 'exposes and shapes children associations' do
163
+ expect(subject.children.map(&:legal_name)).to eq(['Jimmy Smith', 'Jane Smith'])
164
+ end
165
+
166
+ end
167
+
168
+ end
169
+
170
+ context 'and a Shape decorator with properties_from' do
171
+
172
+ before do
173
+ stub_const('MockDecorator', Class.new do
174
+ include Shape::Base
175
+ properties_from(:keys)
176
+ end)
177
+
178
+ end
179
+
180
+ context 'when shaped by the decorator' do
181
+
182
+ subject {
183
+ MockDecorator.new(source)
184
+ }
185
+
186
+ it 'exposes defined properties from source for each key' do
187
+ expect(subject).to respond_to(:name, :age, :ssn, :children)
188
+ expect(subject.name).to eq('John Smith')
189
+ end
190
+ end
191
+
192
+ end
193
+
194
+ context 'and a Shape decorator with properties_from with an except list' do
195
+
196
+ before do
197
+ stub_const('MockDecorator', Class.new do
198
+ include Shape::Base
199
+ properties_from(:keys, except: :ssn)
200
+ end)
201
+ end
202
+
203
+ context 'when shaped by the decorator' do
204
+
205
+ subject {
206
+ MockDecorator.new(source)
207
+ }
208
+
209
+ it 'should not expose defined properties for the exceptions' do
210
+ expect(subject).to_not respond_to(:ssn)
211
+ end
212
+ end
213
+
214
+ end
215
+ end
216
+
217
+ end
@@ -0,0 +1,57 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Shape::DataVisitor do
4
+
5
+ context 'Given a decorated class that implements Shape::DataVisitor' do
6
+
7
+ before do
8
+ stub_const('PersonDecorator', Class.new do
9
+ include Shape::Base
10
+ include Shape::DataVisitor
11
+ property :name
12
+ property :age
13
+ association :spouse, with: self
14
+ end)
15
+ end
16
+
17
+ let(:source) {
18
+ OpenStruct.new.tap do |person|
19
+ person.name = 'John Smith'
20
+ person.age = 34
21
+ person.spouse = OpenStruct.new.tap do |spouse|
22
+ spouse.name = 'Jane Smith'
23
+ spouse.age = 32
24
+ end
25
+ end
26
+ }
27
+
28
+ context 'when I visit the object without providing a visitor' do
29
+ subject {
30
+ PersonDecorator.new(source).visit
31
+ }
32
+
33
+ it 'returns the raw visited data' do
34
+ expect(subject[:name]).to eq('John Smith')
35
+ expect(subject[:age]).to eq(34)
36
+ expect(subject[:spouse][:name]).to eq('Jane Smith')
37
+ expect(subject[:spouse][:age]).to eq(32)
38
+ end
39
+ end
40
+
41
+ context 'when I visit the object with a string visitor' do
42
+ subject {
43
+ PersonDecorator.new(source).visit(lambda do |data|
44
+ data.to_s
45
+ end)
46
+ }
47
+
48
+ it 'returns the visited data as strings' do
49
+ expect(subject[:name]).to eq('John Smith')
50
+ expect(subject[:age]).to eq('34')
51
+ expect(subject[:spouse][:age]).to eq('32')
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -0,0 +1,139 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Shape::PropertyShaper do
4
+
5
+ context 'Given an object with method attributes' do
6
+
7
+ let(:source) {
8
+ OpenStruct.new.tap do |person|
9
+ person.name = 'John Smith'
10
+ person.age = 34
11
+ person.ssn = 123456789
12
+ person.children = [
13
+ OpenStruct.new.tap do |child|
14
+ child.name = 'Jimmy Smith'
15
+ end,
16
+ OpenStruct.new.tap do |child|
17
+ child.name = 'Jane Smith'
18
+ end,
19
+ ]
20
+ end
21
+ }
22
+
23
+ context 'and a Shape decorator' do
24
+
25
+ before do
26
+ stub_const('MockDecorator', Class.new do
27
+ include Shape::Base
28
+ property :name
29
+ property :years_of_age, from: :age
30
+ end)
31
+
32
+ end
33
+
34
+ context 'when shaped by the decorator' do
35
+
36
+ subject {
37
+ MockDecorator.new(source)
38
+ }
39
+
40
+ it 'exposes defined properties from source' do
41
+ expect(subject.name).to eq('John Smith')
42
+ end
43
+
44
+ it 'exposes defined properties renamed from source' do
45
+ expect(subject.years_of_age).to eq(34)
46
+ end
47
+
48
+ it 'does not expose unspecified attributes' do
49
+ expect(subject).to_not respond_to(:ssn)
50
+ expect(subject).to_not respond_to(:age)
51
+ end
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ context 'Given a hash with attributes' do
58
+
59
+ let(:source) do
60
+ {
61
+ name: 'John Smith',
62
+ age: 34,
63
+ ssn: 123456789,
64
+ children: [
65
+ {
66
+ name: 'Jimmy Smith'
67
+ },
68
+ {
69
+ name: 'Jane Smith'
70
+ }
71
+ ]
72
+ }
73
+ end
74
+
75
+ context 'and a Shape decorator' do
76
+
77
+ before do
78
+ stub_const('MockDecorator', Class.new do
79
+ include Shape::Base
80
+ property :name
81
+ property :years_of_age, from: :age
82
+ end)
83
+
84
+ end
85
+
86
+ context 'when shaped by the decorator' do
87
+
88
+ subject {
89
+ MockDecorator.new(source)
90
+ }
91
+
92
+ it 'exposes defined properties from source' do
93
+ expect(subject.name).to eq('John Smith')
94
+ end
95
+
96
+ it 'exposes defined properties renamed from source' do
97
+ expect(subject.years_of_age).to eq(34)
98
+ end
99
+
100
+ it 'does not expose unspecified attributes' do
101
+ expect(subject).to_not respond_to(:ssn)
102
+ expect(subject).to_not respond_to(:age)
103
+ end
104
+ end
105
+
106
+ end
107
+
108
+ context 'and a Shape decorator with property with:' do
109
+
110
+ before do
111
+ stub_const('ChildDecorator', Class.new do
112
+ include Shape::Base
113
+ property :name
114
+ end)
115
+
116
+ stub_const('MockDecorator', Class.new do
117
+ include Shape::Base
118
+ property :children, with: ChildDecorator
119
+ end)
120
+
121
+ end
122
+
123
+ context 'when shaped by the decorator' do
124
+
125
+ subject {
126
+ MockDecorator.new(source)
127
+ }
128
+
129
+ it 'exposes and shapes each child element of the property with the provided decorator' do
130
+ expect(subject.children.map(&:name)).to eq(['Jimmy Smith', 'Jane Smith'])
131
+ end
132
+
133
+ end
134
+
135
+ end
136
+
137
+ end
138
+
139
+ end
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+ require 'rspec'
5
+ require 'shape'
6
+
7
+ # Requires supporting files with custom matchers and macros, etc,
8
+ # in ./support/ and its subdirectories.
9
+ # Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
10
+
11
+ RSpec.configure do |config|
12
+ # Use color in STDOUT
13
+ config.color_enabled = true
14
+
15
+ # Use color not only in STDOUT but also in pagers and files
16
+ config.tty = true
17
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shape
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Robin Curry
8
+ - Brandon Westcott
9
+ - Tim Morgan
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-09-04 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - '>='
27
+ - !ruby/object:Gem::Version
28
+ version: '3.0'
29
+ description: Shape your api. Extracted from Vitals Platform.
30
+ email:
31
+ - robin.curry@vitals.com
32
+ - brandon.westcott@vitals.com
33
+ - tim.morgan@vitals.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - Gemfile
40
+ - Gemfile.lock
41
+ - LICENSE.txt
42
+ - README.md
43
+ - Rakefile
44
+ - lib/shape.rb
45
+ - lib/shape/base.rb
46
+ - lib/shape/data_visitor.rb
47
+ - lib/shape/property_shaper.rb
48
+ - lib/shape/renderers.rb
49
+ - lib/shape/version.rb
50
+ - lib/shape/view_decorator.rb
51
+ - shape.gemspec
52
+ - spec/association_shaper_spec.rb
53
+ - spec/base_spec.rb
54
+ - spec/data_visitor_spec.rb
55
+ - spec/property_shaper_spec.rb
56
+ - spec/spec_helper.rb
57
+ homepage: https://github.com/robincurry/shape
58
+ licenses: []
59
+ metadata: {}
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 2.0.6
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: Shape your api
80
+ test_files:
81
+ - spec/association_shaper_spec.rb
82
+ - spec/base_spec.rb
83
+ - spec/data_visitor_spec.rb
84
+ - spec/property_shaper_spec.rb
85
+ - spec/spec_helper.rb