praxis-mapper 3.1.2 → 3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -2
- data/lib/praxis-mapper/model.rb +2 -5
- data/lib/praxis-mapper/resource.rb +104 -49
- data/lib/praxis-mapper/version.rb +1 -1
- data/spec/praxis-mapper/resource_spec.rb +37 -1
- data/spec/support/spec_models.rb +1 -1
- data/spec/support/spec_resources.rb +31 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0f5ae2201ca9a681d3f65c4ea8a51d3523e1620
|
4
|
+
data.tar.gz: 5fa2fc72ff6c38fac99fa60a4fd42d3bcadc7aa6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2add48d3bca8c0ce48ee4bf2d66ba02582f0c781bcb45e792af98b500eb0f368b7e6e93fd64c0e1a211fe2e1a02355c1b190130cd2fd750409e44c0a79197b1d
|
7
|
+
data.tar.gz: 13e3428c8129be0d9eb2a3c9c3269ab50cd10545027092e971fcdbc07e6c0f3af67fa51d277c4abfca56a7e19a88ac9b09a63c292a116a64e5a107f300be4bca
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# praxis-mapper changelog
|
2
2
|
|
3
|
-
##
|
3
|
+
## 3.2.0
|
4
4
|
|
5
|
-
*
|
5
|
+
* Add `Resource.decorate` for extending/overriding methods on Resource associations. See `PersonResource` in [spec_resources.rb](spec/support/spec_resources.rb) for usage.
|
6
6
|
|
7
7
|
## 3.1.2
|
8
8
|
|
data/lib/praxis-mapper/model.rb
CHANGED
@@ -18,16 +18,13 @@ module Praxis::Mapper
|
|
18
18
|
|
19
19
|
klass.instance_eval do
|
20
20
|
@config = {
|
21
|
-
:
|
22
|
-
:
|
23
|
-
:identities => []
|
21
|
+
excluded_scopes: [],
|
22
|
+
identities: []
|
24
23
|
}
|
25
24
|
@associations = {}
|
26
25
|
@serialized_fields = {}
|
27
26
|
@contexts = Hash.new
|
28
27
|
end
|
29
|
-
|
30
|
-
|
31
28
|
end
|
32
29
|
|
33
30
|
# Internal finalize! logic
|
@@ -3,33 +3,56 @@ require 'active_support/inflector'
|
|
3
3
|
# A resource creates a data store and instantiates a list of models that it wishes to load, building up the overall set of data that it will need.
|
4
4
|
# Once that is complete, the data set is iterated and a resultant view is generated.
|
5
5
|
module Praxis::Mapper
|
6
|
+
|
7
|
+
class ResourceDecorator < BasicObject
|
8
|
+
def initialize(parent, object)
|
9
|
+
@parent = parent
|
10
|
+
@object = object # store obj for future use
|
11
|
+
end
|
12
|
+
|
13
|
+
def respond_to_missing?(name, include_private = false)
|
14
|
+
@object.respond_to?(name, include_private) || super
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(name,*args, &block)
|
18
|
+
@object.__send__(name, *args, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def __getobj__
|
22
|
+
@object
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
6
27
|
class Resource
|
7
28
|
extend Finalizable
|
8
29
|
|
9
30
|
attr_accessor :record
|
10
|
-
|
31
|
+
|
32
|
+
class << self
|
33
|
+
attr_reader :model_map
|
34
|
+
attr_reader :decorations
|
35
|
+
end
|
36
|
+
|
11
37
|
# TODO: also support an attribute of sorts on the versioned resource module. ie, V1::Resources.api_version.
|
12
|
-
# replacing the self == Praxis::Mapper::Resource condition below.
|
38
|
+
# replacing the self.superclass == Praxis::Mapper::Resource condition below.
|
13
39
|
def self.inherited(klass)
|
14
40
|
super
|
15
41
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
42
|
+
klass.instance_eval do
|
43
|
+
# It is expected that each versioned set of resources
|
44
|
+
# will have a common Base class, and so should share
|
45
|
+
# a model_map
|
46
|
+
if self.superclass == Praxis::Mapper::Resource
|
47
|
+
@model_map = Hash.new
|
48
|
+
else
|
49
|
+
@model_map = self.superclass.model_map
|
50
|
+
end
|
24
51
|
|
25
|
-
|
26
|
-
if defined? @model_map
|
27
|
-
return @model_map
|
28
|
-
else
|
29
|
-
return {}
|
52
|
+
@decorations = {}
|
30
53
|
end
|
31
|
-
end
|
32
54
|
|
55
|
+
end
|
33
56
|
|
34
57
|
#TODO: Take symbol/string and resolve the klass (but lazily, so we don't care about load order)
|
35
58
|
def self.model(klass=nil)
|
@@ -41,9 +64,16 @@ module Praxis::Mapper
|
|
41
64
|
end
|
42
65
|
end
|
43
66
|
|
67
|
+
|
68
|
+
def self.decorate(name, &block)
|
69
|
+
self.decorations[name] = Class.new(ResourceDecorator, &block)
|
70
|
+
end
|
71
|
+
|
44
72
|
def self._finalize!
|
45
73
|
finalize_resource_delegates
|
46
74
|
define_model_accessors
|
75
|
+
define_decorators
|
76
|
+
|
47
77
|
super
|
48
78
|
end
|
49
79
|
|
@@ -60,6 +90,7 @@ module Praxis::Mapper
|
|
60
90
|
|
61
91
|
def self.define_model_accessors
|
62
92
|
return if model.nil?
|
93
|
+
|
63
94
|
model.associations.each do |k,v|
|
64
95
|
if self.instance_methods.include? k
|
65
96
|
warn "WARNING: #{self.name} already has method named #{k.inspect}. Will not define accessor for resource association."
|
@@ -69,6 +100,29 @@ module Praxis::Mapper
|
|
69
100
|
end
|
70
101
|
|
71
102
|
|
103
|
+
def self.define_decorators
|
104
|
+
self.decorations.each do |name,block|
|
105
|
+
self.define_decorator(name, block)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.define_decorator(name, block)
|
110
|
+
unless self.instance_methods.include?(name)
|
111
|
+
# assume it'll be a regular accessor and create it
|
112
|
+
self.define_accessor(name)
|
113
|
+
end
|
114
|
+
# alias original method and wrap it
|
115
|
+
raw_name = "_raw_#{name}"
|
116
|
+
alias_method(raw_name.to_sym, name)
|
117
|
+
|
118
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
119
|
+
def #{name}
|
120
|
+
object = self.#{raw_name}
|
121
|
+
self.class.decorations[#{name.inspect}].new(self, object)
|
122
|
+
end
|
123
|
+
RUBY
|
124
|
+
end
|
125
|
+
|
72
126
|
def self.for_record(record)
|
73
127
|
return record._resource if record._resource
|
74
128
|
|
@@ -83,17 +137,17 @@ module Praxis::Mapper
|
|
83
137
|
end
|
84
138
|
|
85
139
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
return records.collect { |record| self.for_record(record) }
|
95
|
-
end
|
140
|
+
def self.wrap(records)
|
141
|
+
case records
|
142
|
+
when nil
|
143
|
+
return []
|
144
|
+
when Enumerable
|
145
|
+
return records.collect { |record| self.for_record(record) }
|
146
|
+
else
|
147
|
+
return self.for_record(records)
|
96
148
|
end
|
149
|
+
end
|
150
|
+
|
97
151
|
|
98
152
|
|
99
153
|
def self.get(condition)
|
@@ -109,14 +163,6 @@ module Praxis::Mapper
|
|
109
163
|
end
|
110
164
|
|
111
165
|
|
112
|
-
def initialize(record)
|
113
|
-
@record = record
|
114
|
-
end
|
115
|
-
|
116
|
-
def respond_to_missing?(name,*)
|
117
|
-
@record.respond_to?(name) || super
|
118
|
-
end
|
119
|
-
|
120
166
|
def self.resource_delegates
|
121
167
|
@resource_delegates ||= {}
|
122
168
|
end
|
@@ -127,17 +173,18 @@ module Praxis::Mapper
|
|
127
173
|
end
|
128
174
|
end
|
129
175
|
|
130
|
-
# Defines
|
176
|
+
# Defines wrappers for model associations that return Resources
|
131
177
|
def self.define_model_association_accessor(name, association_spec)
|
132
178
|
association_model = association_spec.fetch(:model)
|
133
179
|
association_resource_class = model_map[association_model]
|
180
|
+
|
134
181
|
if association_resource_class
|
135
182
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
136
|
-
|
137
|
-
|
183
|
+
def #{name}
|
184
|
+
records = record.#{name}
|
138
185
|
return nil if records.nil?
|
139
|
-
|
140
|
-
|
186
|
+
@__#{name} ||= #{association_resource_class}.wrap(records)
|
187
|
+
end
|
141
188
|
RUBY
|
142
189
|
end
|
143
190
|
end
|
@@ -154,13 +201,12 @@ module Praxis::Mapper
|
|
154
201
|
end
|
155
202
|
|
156
203
|
|
157
|
-
|
158
204
|
def self.define_delegation_for_related_attribute(resource_name, resource_attribute)
|
159
205
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
160
206
|
def #{resource_attribute}
|
161
207
|
@__#{resource_attribute} ||= if (rec = self.#{resource_name})
|
162
|
-
|
163
|
-
|
208
|
+
rec.#{resource_attribute}
|
209
|
+
end
|
164
210
|
end
|
165
211
|
RUBY
|
166
212
|
end
|
@@ -172,11 +218,11 @@ module Praxis::Mapper
|
|
172
218
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
173
219
|
def #{resource_attribute}
|
174
220
|
@__#{resource_attribute} ||= if (rec = self.#{resource_name})
|
175
|
-
|
176
|
-
|
177
|
-
end
|
221
|
+
if (related = rec.#{resource_attribute})
|
222
|
+
#{related_resource_class.name}.wrap(related)
|
178
223
|
end
|
179
224
|
end
|
225
|
+
end
|
180
226
|
RUBY
|
181
227
|
end
|
182
228
|
|
@@ -188,13 +234,22 @@ module Praxis::Mapper
|
|
188
234
|
end
|
189
235
|
|
190
236
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
237
|
+
def #{name}
|
238
|
+
return @__#{ivar_name} if defined? @__#{ivar_name}
|
239
|
+
@__#{ivar_name} = record.#{name}
|
240
|
+
end
|
195
241
|
RUBY
|
196
242
|
end
|
197
243
|
|
244
|
+
|
245
|
+
def initialize(record)
|
246
|
+
@record = record
|
247
|
+
end
|
248
|
+
|
249
|
+
def respond_to_missing?(name,*)
|
250
|
+
@record.respond_to?(name) || super
|
251
|
+
end
|
252
|
+
|
198
253
|
def method_missing(name,*args)
|
199
254
|
if @record.respond_to?(name)
|
200
255
|
self.class.define_accessor(name)
|
@@ -21,7 +21,6 @@ describe Praxis::Mapper::Resource do
|
|
21
21
|
its(:collection_name) { should == 'simple_resources' }
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
24
|
context 'retrieving resources' do
|
26
25
|
|
27
26
|
context 'getting a single resource' do
|
@@ -114,6 +113,43 @@ describe Praxis::Mapper::Resource do
|
|
114
113
|
end
|
115
114
|
|
116
115
|
|
116
|
+
context 'decorate' do
|
117
|
+
let(:person_record) do
|
118
|
+
PersonModel.new(id: 1, email: "one@example.com", address_id: 2)
|
119
|
+
end
|
120
|
+
|
121
|
+
let(:address_record) do
|
122
|
+
AddressModel.new(id: 2, owner_id: 1, state: 'OR')
|
123
|
+
end
|
124
|
+
|
125
|
+
before do
|
126
|
+
identity_map.add_records([person_record])
|
127
|
+
identity_map.add_records([address_record])
|
128
|
+
end
|
129
|
+
|
130
|
+
subject(:person) { PersonResource.for_record(person_record) }
|
131
|
+
let(:address){ AddressResource.for_record(address_record) }
|
132
|
+
|
133
|
+
it 'wraps the decorated associations in a ResourceDecorator' do
|
134
|
+
# somewhat hard to test, as ResourceDecorator uses BasicObject
|
135
|
+
person.address.should_not be(address)
|
136
|
+
person.address.__getobj__.should be(address)
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
it 'decorates many_to_one associations properly' do
|
141
|
+
person.address.href.should eq('/people/1/address')
|
142
|
+
person.address.state.should eq('OR')
|
143
|
+
address.href.should eq('/addresses/2')
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'decorates one_to_many associations properly' do
|
148
|
+
person.properties.href.should eq('/people/1/properties')
|
149
|
+
person.properties.first.should be(address)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
117
153
|
context "memoized resource creation" do
|
118
154
|
let(:other_name) { "foo" }
|
119
155
|
let(:other_attribute) { "other value" }
|
data/spec/support/spec_models.rb
CHANGED
@@ -37,3 +37,34 @@ end
|
|
37
37
|
class YamlArrayResource < BaseResource
|
38
38
|
model YamlArrayModel
|
39
39
|
end
|
40
|
+
|
41
|
+
|
42
|
+
class PersonResource < BaseResource
|
43
|
+
model PersonModel
|
44
|
+
|
45
|
+
decorate :address do
|
46
|
+
def href
|
47
|
+
@parent.href + "/address"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
decorate :properties do
|
52
|
+
def href
|
53
|
+
@parent.href + "/properties"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def href
|
58
|
+
"/people/#{self.id}"
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
class AddressResource < BaseResource
|
64
|
+
model AddressModel
|
65
|
+
|
66
|
+
def href
|
67
|
+
"/addresses/#{self.id}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|