praxis-blueprints 3.2 → 3.3

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: e3679f890d11a1474437b9018c7030a909e050ca
4
- data.tar.gz: 61094297c27f94f4add6227d637654fae03ed662
3
+ metadata.gz: 2baa109dab4f8585d61ee9d5317bc11039cbaa98
4
+ data.tar.gz: f2bd553cd4f075311155154b0c7fef9aad415b3f
5
5
  SHA512:
6
- metadata.gz: aa633b4772c07f859fd4024044a618c2243b5b6d95678dd7c0757486811dc1a315eafde12319c4c4215b72571d0d38f7ccdae7d91903183aa14c1baaaa86ae58
7
- data.tar.gz: 47e3c62fd5bdfa05c142083e7053687d57d25fd2b08b3a5b2dd913050cbaf94a9ccfb699707e1c1e43f6384ce25e4688beaed9cb81a39038080eff2155e9c979
6
+ metadata.gz: e52a91a7f9c78b9964650972b09c61a883035903f04e41374e82115d04b6643776f9ff05aebb281a007b7074913c9bfb086328ab6043418bd0df0ec8232ccfc8
7
+ data.tar.gz: af137419cbb73cceac9e4283a2558d758b0f4425ac009fe9243780328eb6df1091cdcee776147327fb646630a1832573bdbbd7e14b5a9cac8cb4e30d2f31fa5a
@@ -0,0 +1,35 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+ Style/Documentation:
4
+ Enabled: false
5
+ Metrics/MethodLength:
6
+ Enabled: false
7
+ Metrics/ClassLength:
8
+ Enabled: false
9
+ Metrics/LineLength:
10
+ Max: 200
11
+ Style/RedundantSelf:
12
+ Enabled: false
13
+ Style/ClassAndModuleChildren:
14
+ Enabled: false
15
+ Lint/Debugger:
16
+ Enabled: false
17
+ Metrics/AbcSize:
18
+ Enabled: false
19
+ Style/CaseEquality:
20
+ Enabled: false
21
+ Lint/UnusedMethodArgument:
22
+ AllowUnusedKeywordArguments: true
23
+ Style/FileName:
24
+ Exclude:
25
+ - 'lib/praxis-blueprints.rb'
26
+ Style/ClassVars:
27
+ Exclude:
28
+ - 'lib/praxis-blueprints/blueprint.rb'
29
+
30
+ # Offense count: 5
31
+ Metrics/CyclomaticComplexity:
32
+ Max: 8
33
+ # Offense count: 5
34
+ Metrics/PerceivedComplexity:
35
+ Max: 12
@@ -2,6 +2,8 @@ language: ruby
2
2
  cache: bundler
3
3
  sudo: false
4
4
  rvm:
5
- - 2.1.2
6
- - 2.2.2
7
- script: bundle exec rspec spec
5
+ - 2.2.5
6
+ - 2.3.1
7
+ script:
8
+ - bundle exec rspec spec
9
+ - bundle exec rubocop --format=clang
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## next
4
4
 
5
+ ## 3.3
6
+
7
+ * Include Attributor::Dumpable in Blueprint, so it renders (semi) correctly if
8
+ rendered with just `true` specified for fields.
9
+ * Fix bug rendering subobjects with nil values (manifested when `include_nil: true` there’s an explicit subsection of fields)
10
+
5
11
  ## 3.2
6
12
 
7
13
  * Ensure we call `object.dump` in Renderer when fully dumping an instance (or array of instances) that have the Attributor::Dumpable module (i.e., when no subfields were selected)
data/Gemfile CHANGED
@@ -1,2 +1,3 @@
1
+ # frozen_string_literal: true
1
2
  source 'https://rubygems.org'
2
3
  gemspec
data/Guardfile CHANGED
@@ -1,11 +1,17 @@
1
+ # frozen_string_literal: true
1
2
  # Config file for Guard
2
3
  # More info at https://github.com/guard/guard#readme
4
+ group :red_green_refactor, halt_on_fail: true do
5
+ guard :rspec, cmd: 'bundle exec rspec' do
6
+ watch(%r{^spec/.+_spec\.rb$})
7
+ watch(%r{^lib/praxis-blueprints/(.+)\.rb$}) { |m| "spec/praxis-blueprints/#{m[1]}_spec.rb" }
8
+ watch('spec/*.rb') { 'spec' }
9
+ watch('lib/praxis-blueprints.rb') { 'spec' }
10
+ watch(%r{^spec/support/(.+)\.rb$}) { 'spec' }
11
+ end
3
12
 
4
- guard :rspec, cmd: 'bundle exec rspec' do
5
- watch(%r{^spec/.+_spec\.rb$})
6
- watch(%r{^lib/praxis-blueprints/(.+)\.rb$}) { |m| "spec/praxis-blueprints/#{m[1]}_spec.rb" }
7
- watch('spec/*.rb') { 'spec' }
8
- watch('lib/praxis-blueprints.rb') { 'spec' }
9
- watch(%r{^spec/support/(.+)\.rb$}) { 'spec' }
13
+ guard :rubocop, cli: '--auto-correct --display-cop-names' do
14
+ watch(/.+\.rb$/)
15
+ watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
16
+ end
10
17
  end
11
-
data/Rakefile CHANGED
@@ -1,17 +1,18 @@
1
+ # frozen_string_literal: true
1
2
  require 'bundler/setup'
2
3
 
3
- require "bundler/gem_tasks"
4
+ require 'bundler/gem_tasks'
4
5
 
5
6
  require 'rspec/core'
6
7
  require 'rspec/core/rake_task'
7
8
  require 'bundler/gem_tasks'
8
9
 
9
- desc "Run RSpec code examples with simplecov"
10
+ desc 'Run RSpec code examples with simplecov'
10
11
  RSpec::Core::RakeTask.new do |spec|
11
12
  spec.pattern = FileList['spec/**/*_spec.rb']
12
13
  end
13
14
 
14
- task :default => :spec
15
+ task default: :spec
15
16
 
16
17
  require 'yard'
17
18
  YARD::Rake::YardocTask.new
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
1
2
  require 'json'
2
3
  require 'yaml'
3
4
  require 'logger'
4
5
 
5
6
  require 'attributor'
6
7
 
7
- require "praxis-blueprints/version"
8
+ require 'praxis-blueprints/version'
8
9
 
9
10
  require 'praxis-blueprints/finalizable'
10
11
  require 'praxis-blueprints/config_hash'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'ostruct'
2
3
 
3
4
  # Blueprint ==
@@ -7,6 +8,8 @@ require 'ostruct'
7
8
  module Praxis
8
9
  class Blueprint
9
10
  include Attributor::Type
11
+ include Attributor::Dumpable
12
+
10
13
  extend Finalizable
11
14
 
12
15
  if RUBY_ENGINE =~ /^jruby/
@@ -18,7 +21,6 @@ module Praxis
18
21
 
19
22
  @@caching_enabled = false
20
23
 
21
-
22
24
  attr_reader :validating
23
25
  attr_accessor :object
24
26
  attr_accessor :decorators
@@ -34,20 +36,20 @@ module Praxis
34
36
  super
35
37
 
36
38
  klass.instance_eval do
37
- @views = Hash.new
38
- @options = Hash.new
39
+ @views = {}
40
+ @options = {}
39
41
  @domain_model = Object
40
42
  end
41
43
  end
42
44
 
43
45
  # Override default new behavior to support memoized creation through an IdentityMap
44
- def self.new(object, decorators=nil)
46
+ def self.new(object, decorators = nil)
45
47
  if @@caching_enabled && decorators.nil?
46
48
  cache = if object.respond_to?(:identity_map) && object.identity_map
47
- object.identity_map.blueprint_cache[self]
48
- else
49
- self.cache
50
- end
49
+ object.identity_map.blueprint_cache[self]
50
+ else
51
+ self.cache
52
+ end
51
53
 
52
54
  return cache[object] ||= begin
53
55
  blueprint = self.allocate
@@ -65,14 +67,12 @@ module Praxis
65
67
  'hash'
66
68
  end
67
69
 
68
- def self.describe(shallow=false,example: nil, **opts)
70
+ def self.describe(shallow = false, example: nil, **opts)
69
71
  type_name = self.ancestors.find { |k| k.name && !k.name.empty? }.name
70
72
 
71
- if example
72
- example = example.object
73
- end
73
+ example = example.object if example
74
74
 
75
- description = self.attribute.type.describe(shallow,example: example, **opts).merge!(id: self.id, name: type_name)
75
+ description = self.attribute.type.describe(shallow, example: example, **opts).merge!(id: self.id, name: type_name)
76
76
  description.delete :anonymous # discard the Struct's view of anonymity, and use the Blueprint's one
77
77
  description[:anonymous] = @_anonymous unless @_anonymous.nil?
78
78
 
@@ -85,37 +85,30 @@ module Praxis
85
85
  description
86
86
  end
87
87
 
88
-
89
- def self.attributes(opts={}, &block)
88
+ def self.attributes(opts = {}, &block)
90
89
  if block_given?
91
- if self.const_defined?(:Struct, false)
92
- raise "Redefining Blueprint attributes is not currently supported"
93
- else
90
+ raise 'Redefining Blueprint attributes is not currently supported' if self.const_defined?(:Struct, false)
94
91
 
95
- if opts.has_key?(:reference) && opts[:reference] != self.reference
96
- raise "Reference mismatch in #{self.inspect}. Given :reference option #{opts[:reference].inspect}, while using #{self.reference.inspect}"
97
- elsif self.reference
98
- opts[:reference] = self.reference #pass the reference Class down
99
- else
100
- opts[:reference] = self
101
- end
102
-
103
- @options.merge!(opts)
104
- @block = block
92
+ if opts.key?(:reference) && opts[:reference] != self.reference
93
+ raise "Reference mismatch in #{self.inspect}. Given :reference option #{opts[:reference].inspect}, while using #{self.reference.inspect}"
94
+ elsif self.reference
95
+ opts[:reference] = self.reference # pass the reference Class down
96
+ else
97
+ opts[:reference] = self
105
98
  end
106
99
 
100
+ @options.merge!(opts)
101
+ @block = block
102
+
107
103
  return @attribute
108
104
  end
109
105
 
110
- unless @attribute
111
- raise "@attribute not defined yet for #{self.name}"
112
- end
106
+ raise "@attribute not defined yet for #{self.name}" unless @attribute
113
107
 
114
108
  @attribute.attributes
115
109
  end
116
110
 
117
-
118
- def self.domain_model(klass=nil)
111
+ def self.domain_model(klass = nil)
119
112
  return @domain_model if klass.nil?
120
113
  @domain_model = klass
121
114
  end
@@ -123,26 +116,25 @@ module Praxis
123
116
  def self.check_option!(name, value)
124
117
  case name
125
118
  when :identity
126
- raise Attributor::AttributorException, "Invalid identity type #{value.inspect}" unless value.kind_of?(::Symbol)
119
+ raise Attributor::AttributorException, "Invalid identity type #{value.inspect}" unless value.is_a?(::Symbol)
127
120
  return :ok
128
121
  else
129
122
  return Attributor::Struct.check_option!(name, value)
130
123
  end
131
124
  end
132
125
 
133
-
134
- def self.load(value,context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
126
+ def self.load(value, context = Attributor::DEFAULT_ROOT_CONTEXT, **options)
135
127
  case value
136
128
  when self
137
129
  value
138
130
  when nil, Hash, String
139
131
  # Need to parse/deserialize first
140
132
  # or apply default/recursive loading options if necessary
141
- if (value = self.attribute.load(value,context, **options))
133
+ if (value = self.attribute.load(value, context, **options))
142
134
  self.new(value)
143
135
  end
144
136
  else
145
- if value.kind_of?(self.domain_model) || value.kind_of?(self::Struct)
137
+ if value.is_a?(self.domain_model) || value.is_a?(self::Struct)
146
138
  # Wrap the value directly
147
139
  self.new(value)
148
140
  else
@@ -153,7 +145,7 @@ module Praxis
153
145
  end
154
146
 
155
147
  class << self
156
- alias_method :from, :load
148
+ alias from load
157
149
  end
158
150
 
159
151
  def self.caching_enabled?
@@ -175,35 +167,33 @@ module Praxis
175
167
 
176
168
  def self.valid_type?(value)
177
169
  # FIXME: this should be more... ducklike
178
- value.kind_of?(self) || value.kind_of?(self.attribute.type)
170
+ value.is_a?(self) || value.is_a?(self.attribute.type)
179
171
  end
180
172
 
181
- def self.example(context=nil, **values)
173
+ def self.example(context = nil, **values)
182
174
  context = case context
183
- when nil
184
- ["#{self.name}-#{values.object_id.to_s}"]
185
- when ::String
186
- [context]
187
- else
188
- context
189
- end
175
+ when nil
176
+ ["#{self.name}-#{values.object_id}"]
177
+ when ::String
178
+ [context]
179
+ else
180
+ context
181
+ end
190
182
 
191
183
  self.new(self.attribute.example(context, values: values))
192
184
  end
193
185
 
194
-
195
- def self.validate(value, context=Attributor::DEFAULT_ROOT_CONTEXT, _attribute=nil)
196
- raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context == nil
186
+ def self.validate(value, context = Attributor::DEFAULT_ROOT_CONTEXT, _attribute = nil)
187
+ raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context.nil?
197
188
  context = [context] if context.is_a? ::String
198
189
 
199
- unless value.kind_of?(self)
190
+ unless value.is_a?(self)
200
191
  raise ArgumentError, "Error validating #{Attributor.humanize_context(context)} as #{self.name} for an object of type #{value.class.name}."
201
192
  end
202
193
 
203
194
  value.validate(context)
204
195
  end
205
196
 
206
-
207
197
  def self.view(name, **options, &block)
208
198
  if block_given?
209
199
  return self.views[name] = View.new(name, self, **options, &block)
@@ -219,7 +209,7 @@ module Praxis
219
209
  object.render(view: view, context: context, **opts)
220
210
  end
221
211
  class << self
222
- alias_method :render, :dump
212
+ alias render dump
223
213
  end
224
214
 
225
215
  # Internal finalize! logic
@@ -235,7 +225,7 @@ module Praxis
235
225
  end
236
226
 
237
227
  def self.resolve_domain_model!
238
- return unless self.domain_model.kind_of?(String)
228
+ return unless self.domain_model.is_a?(String)
239
229
 
240
230
  @domain_model = self.domain_model.constantize
241
231
  end
@@ -248,7 +238,7 @@ module Praxis
248
238
  end
249
239
 
250
240
  def self.define_readers!
251
- self.attributes.each do |name, attribute|
241
+ self.attributes.each do |name, _attribute|
252
242
  name = name.to_sym
253
243
 
254
244
  # Don't redefine existing methods
@@ -258,7 +248,6 @@ module Praxis
258
248
  end
259
249
  end
260
250
 
261
-
262
251
  def self.define_reader!(name)
263
252
  attribute = self.attributes[name]
264
253
  # TODO: profile and optimize
@@ -270,18 +259,16 @@ module Praxis
270
259
  @decorators.send(name)
271
260
  else
272
261
  value = @object.__send__(name)
273
- return value if value.nil? || value.kind_of?(attribute.type)
262
+ return value if value.nil? || value.is_a?(attribute.type)
274
263
  attribute.load(value)
275
264
  end
276
265
  end
277
266
  end
278
267
 
279
-
280
-
281
268
  def self.generate_master_view!
282
269
  attributes = self.attributes
283
270
  view :master do
284
- attributes.each do | name, attr |
271
+ attributes.each do |name, _attr|
285
272
  # Note: we can freely pass master view for attributes that aren't blueprint/containers because
286
273
  # their dump methods will ignore it (they always dump everything regardless)
287
274
  attribute name, view: :default
@@ -289,33 +276,29 @@ module Praxis
289
276
  end
290
277
  end
291
278
 
292
-
293
- def initialize(object, decorators=nil)
279
+ def initialize(object, decorators = nil)
294
280
  # TODO: decide what sort of type checking (if any) we want to perform here.
295
281
  @object = object
296
282
 
297
- @decorators = if decorators.kind_of?(Hash) && decorators.any?
298
- OpenStruct.new(decorators)
299
- else
300
- decorators
301
- end
283
+ @decorators = if decorators.is_a?(Hash) && decorators.any?
284
+ OpenStruct.new(decorators)
285
+ else
286
+ decorators
287
+ end
302
288
 
303
289
  @validating = false
304
290
  end
305
291
 
306
-
307
292
  # Render the wrapped data with the given view
308
- def render(view_name=nil, context: Attributor::DEFAULT_ROOT_CONTEXT,renderer: Renderer.new, **opts)
309
- if view_name != nil
310
- warn "DEPRECATED: please do not pass the view name as the first parameter in Blueprint.render, pass through the view: named param instead."
293
+ def render(view_name = nil, context: Attributor::DEFAULT_ROOT_CONTEXT, renderer: Renderer.new, **opts)
294
+ if !view_name.nil?
295
+ warn 'DEPRECATED: please do not pass the view name as the first parameter in Blueprint.render, pass through the view: named param instead.'
311
296
  elsif opts.key?(:view)
312
297
  view_name = opts[:view]
313
298
  end
314
299
 
315
300
  fields = opts[:fields]
316
- if view_name.nil? && fields.nil?
317
- view_name = :default
318
- end
301
+ view_name = :default if view_name.nil? && fields.nil?
319
302
 
320
303
  if view_name
321
304
  unless (view = self.class.views[view_name])
@@ -326,7 +309,7 @@ module Praxis
326
309
 
327
310
  # Accept a simple array of fields, and transform it to a 1-level hash with true values
328
311
  if fields.is_a? Array
329
- fields = fields.each_with_object({}) {|field, hash| hash[field] = true }
312
+ fields = fields.each_with_object({}) { |field, hash| hash[field] = true }
330
313
  end
331
314
 
332
315
  # expand fields
@@ -334,33 +317,31 @@ module Praxis
334
317
 
335
318
  renderer.render(self, expanded_fields, context: context)
336
319
  end
337
- alias_method :to_hash, :render
338
- alias_method :dump, :render
320
+ alias to_hash render
321
+ alias dump render
339
322
 
340
- def validate(context=Attributor::DEFAULT_ROOT_CONTEXT)
341
- raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context == nil
323
+ def validate(context = Attributor::DEFAULT_ROOT_CONTEXT)
324
+ raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context.nil?
342
325
  context = [context] if context.is_a? ::String
343
326
  keys_with_values = []
344
327
 
345
- raise "validation conflict" if @validating
328
+ raise 'validation conflict' if @validating
346
329
  @validating = true
347
330
 
348
331
  errors = []
349
332
  self.class.attributes.each do |sub_attribute_name, sub_attribute|
350
- sub_context = self.class.generate_subcontext(context,sub_attribute_name)
333
+ sub_context = self.class.generate_subcontext(context, sub_attribute_name)
351
334
  value = self.send(sub_attribute_name)
352
- unless value.nil?
353
- keys_with_values << sub_attribute_name
354
- end
335
+ keys_with_values << sub_attribute_name unless value.nil?
355
336
 
356
337
  if value.respond_to?(:validating) # really, it's a thing with sub-attributes
357
338
  next if value.validating
358
339
  end
359
- errors.push(*sub_attribute.validate(value, sub_context))
340
+ errors.concat(sub_attribute.validate(value, sub_context))
360
341
  end
361
342
  self.class.attribute.type.requirements.each do |req|
362
343
  validation_errors = req.validate(keys_with_values, context)
363
- errors.push(*validation_errors) unless validation_errors.empty?
344
+ errors.concat(validation_errors) unless validation_errors.empty?
364
345
  end
365
346
  errors
366
347
  ensure
@@ -371,7 +352,5 @@ module Praxis
371
352
  def _get_attr(name)
372
353
  self.send(name)
373
354
  end
374
-
375
355
  end
376
-
377
356
  end