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 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