praxis-mapper 3.1.2 → 3.2
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.
- 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
|
+
|