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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 71bd06fa8f2c3ff667aef12cf116a714c17c76fa
4
- data.tar.gz: 3ab68cf2cdf25df7fe6ba341a459b3c0047fc1e1
3
+ metadata.gz: c0f5ae2201ca9a681d3f65c4ea8a51d3523e1620
4
+ data.tar.gz: 5fa2fc72ff6c38fac99fa60a4fd42d3bcadc7aa6
5
5
  SHA512:
6
- metadata.gz: 19cb0ef80ba71a29fc8674ed6278cdc3c9e3336504698117818e3c856a3fb1e0cdfa5b5f4c4850e84c95cda2ed57a204b99709fc7113b9f3e0dfbe68a57e191a
7
- data.tar.gz: 66b34d0b5c243c2ac1f092f041bc68995bc5a200b30d3e0ad3ed269213108548b8e5ce7947eaeb46f327be443fd5c0b76045467e70c58da1d95e9ecce10aa5ec
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
- ## next
3
+ ## 3.2.0
4
4
 
5
- * Next feature or fix here.
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
 
@@ -18,16 +18,13 @@ module Praxis::Mapper
18
18
 
19
19
  klass.instance_eval do
20
20
  @config = {
21
- :belongs_to => {},
22
- :excluded_scopes => [],
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
- # It is expected that each versioned set of resources will have a common Base class.
17
- # self is Praxis::Mapper::Resource only for Base resource classes which are versioned.
18
- if self == Praxis::Mapper::Resource
19
- klass.instance_variable_set(:@model_map, Hash.new)
20
- elsif defined?(@model_map)
21
- klass.instance_variable_set(:@model_map, @model_map)
22
- end
23
- end
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
- def self.model_map
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
- def self.wrap(records)
87
- case records
88
- when Model
89
- return self.for_record(records)
90
- when nil
91
- # Return an empty set if `records` is nil
92
- return []
93
- else
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 wrapers for model associations that return Resources
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
- def #{name}
137
- records = record.#{name}
183
+ def #{name}
184
+ records = record.#{name}
138
185
  return nil if records.nil?
139
- @__#{name} ||= #{association_resource_class}.wrap(records)
140
- end
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
- rec.#{resource_attribute}
163
- end
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
- if (related = rec.#{resource_attribute})
176
- #{related_resource_class.name}.wrap(related)
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
- def #{name}
192
- return @__#{ivar_name} if defined? @__#{ivar_name}
193
- @__#{ivar_name} = record.#{name}
194
- end
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)
@@ -1,5 +1,5 @@
1
1
  module Praxis
2
2
  module Mapper
3
- VERSION = "3.1.2"
3
+ VERSION = "3.2"
4
4
  end
5
5
  end
@@ -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" }
@@ -129,7 +129,7 @@ class PersonModel < Praxis::Mapper::Model
129
129
  model AddressModel
130
130
  key :address_id # people.address_id
131
131
  end
132
-
132
+
133
133
  one_to_many :properties do
134
134
  model AddressModel
135
135
  primary_key :id #people.id
@@ -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
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: praxis-mapper
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.2
4
+ version: '3.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josep M. Blanquer