delineate 0.6.1 → 0.6.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: 7fcb6ebbc0458a87120f829033d853357317bfa1
4
- data.tar.gz: a7ee9aef7c42dd4c1e794935ac6e71c6ada2c7af
3
+ metadata.gz: a6242128797a0959401231046cd088cf6c4d3cd5
4
+ data.tar.gz: 3c04565b00c65ee61c96c1f87b9154722060ef9f
5
5
  SHA512:
6
- metadata.gz: 80fd619678b2808735873628329e4b8d6b7103e586ce3f70c12b9b44e0d2b65fec6f593cac7e625ae9101d65c39f9fe15f2ba24843b5ce42dfaeaa8e97b3e3ef
7
- data.tar.gz: e2c886ce454cf4782be0bf957d38a64dd677ee2133960a9c8b0ca8dbbcb6c00a55bad653070b315bb5ff498c01d363ae9b6f9485c512ffd3593b7e5f2d2fa90c
6
+ metadata.gz: f5412696a28e4d9c40b97496ab6d5182943adbfb3b3b01e59bd4203d4ee47b4feef056f3348300cba91ba3990424ec83e932742e24b81b7635c2a848b21e07b5
7
+ data.tar.gz: 87587a174725ecd3d79a68abd1a38163ae1d3fc69a9b6e11d2dfa17e348340a5cbf9a713f0dc8000bddfd65f43a3f6e62cecc0584cd2adc359fdd7457836cc69
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.1
1
+ 0.6.2
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: delineate 0.6.1 ruby lib
5
+ # stub: delineate 0.6.2 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "delineate"
9
- s.version = "0.6.1"
9
+ s.version = "0.6.2"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Tom Smith"]
14
- s.date = "2014-02-23"
14
+ s.date = "2014-02-28"
15
15
  s.description = "ActiveRecord serializer DSL for mapping model attributes and associations. Similar to ActiveModel Serializers with many enhancements including bi-directional support, i.e. deserialization."
16
16
  s.email = "tsmith@landfall.com"
17
17
  s.extra_rdoc_files = [
@@ -34,6 +34,7 @@ Gem::Specification.new do |s|
34
34
  "lib/delineate/attribute_map.rb",
35
35
  "lib/delineate/map_attributes.rb",
36
36
  "lib/delineate/map_serializer.rb",
37
+ "lib/delineate/resolve.rb",
37
38
  "lib/delineate/schema.rb",
38
39
  "lib/delineate/serialization.rb",
39
40
  "lib/delineate/serializers/csv_serializer.rb",
@@ -1,5 +1,6 @@
1
1
  require 'active_support/core_ext/hash/deep_dup.rb'
2
2
 
3
+ require 'delineate/resolve'
3
4
  require 'delineate/serialization'
4
5
  require 'delineate/schema'
5
6
 
@@ -222,12 +223,10 @@ module Delineate
222
223
  @attributes[name] = options
223
224
 
224
225
  model_attr = (options[:model_attr] || name).to_sym
225
- model_attr = define_attr_methods(name, model_attr, options) unless is_model_attr?(model_attr)
226
+ model_attr = define_attr_methods(name, model_attr, options)
226
227
 
227
228
  if options[:access] != :ro
228
- if model_attr.to_s != klass.primary_key && !klass.accessible_attributes.detect { |a| a == model_attr.to_s }
229
- raise "Expected 'attr_accessible :#{model_attr}' in #{@klass_name}"
230
- end
229
+ validate_attr_writable(model_attr)
231
230
  @write_attributes[name] = model_attr
232
231
  end
233
232
  end
@@ -246,12 +245,7 @@ module Delineate
246
245
 
247
246
  attr_map.instance_eval(&blk) if block_given?
248
247
 
249
- if !merge_option?(options) && attr_map.empty?
250
- raise ArgumentError, "Map association '#{name}' in class #{@klass_name} specifies :replace but has empty block"
251
- end
252
- if options[:access] != :ro and !klass.accessible_attributes.include?(model_attr.to_s+'_attributes')
253
- raise "Expected attr_accessible and/or accepts_nested_attributes_for :#{model_attr} in #{@klass_name} model"
254
- end
248
+ validate_assoc_map(name, model_attr, attr_map, options)
255
249
 
256
250
  @associations[name] = {:klass_name => reflection.class_name, :options => options,
257
251
  :attr_map => attr_map.empty? ? nil : attr_map,
@@ -263,7 +257,7 @@ module Delineate
263
257
  return if other_attr_map.nil?
264
258
 
265
259
  @attributes = @attributes.deep_merge(other_attr_map.attributes)
266
- @associations.deep_merge!(other_attr_map.associations)
260
+ @associations = @associations.deep_merge(other_attr_map.associations)
267
261
 
268
262
  @write_attributes = {:_destroy => :_destroy}
269
263
  @attributes.each {|k, v| @write_attributes[k] = (v[:model_attr] || k) unless v[:access] == :ro}
@@ -297,31 +291,6 @@ module Delineate
297
291
  self
298
292
  end
299
293
 
300
- def resolved?
301
- @resolved
302
- end
303
-
304
- # Will raise an exception of the map cannot be fully resolved
305
- def resolve!
306
- resolve(:must_resolve)
307
- self
308
- end
309
-
310
- # Attempts to resolve the map and the maps it depends on. If must_resolve is truthy, will
311
- # raise an exception if map cannot be resolved.
312
- def resolve(must_resolve = false, resolving = [])
313
- return true if @resolved
314
- return true if resolving.include?(@klass_name) # prevent infinite recursion
315
-
316
- resolving.push(@klass_name)
317
-
318
- result = resolve_associations(must_resolve, resolving)
319
- result = false unless resolve_sti_baseclass(must_resolve, resolving)
320
-
321
- resolving.pop
322
- @resolved = result
323
- end
324
-
325
294
 
326
295
  protected
327
296
 
@@ -354,8 +323,10 @@ module Delineate
354
323
  raise ArgumentError, "Association '#{association}' in model #{@klass_name} is not defined" if reflection.nil?
355
324
  begin
356
325
  reflection.klass
357
- rescue
358
- raise NameError, "Cannot resolve association class '#{reflection.class_name}' from model '#{@klass_name}'"
326
+ rescue => e
327
+ msg = "Cannot resolve association class '#{reflection.class_name}' from model '#{@klass_name}'"
328
+ msg += "\n#{e.message}"
329
+ raise NameError, msg
359
330
  end
360
331
  end
361
332
  end
@@ -384,7 +355,7 @@ module Delineate
384
355
  validate_access_option(options[:access])
385
356
  options[:model_attr] = options.delete(:using) if options.key?(:using)
386
357
 
387
- raise ArgumentError, 'Cannot specify :override or provide block with :polymorphic' if options[:polymorphic] and (blk or options[:override])
358
+ raise ArgumentError, 'Cannot specify :override or provide block with :polymorphic' if options[:polymorphic] && (blk or options[:override])
388
359
  raise ArgumentError, 'Option :override must = :replace or :merge' unless !options.key?(:override) || [:merge, :replace].include?(options[:override])
389
360
  end
390
361
 
@@ -411,70 +382,37 @@ module Delineate
411
382
  raise ArgumentError, 'Invalid value for :access option' if opt and !VALID_ACCESS_OPTIONS.include?(opt)
412
383
  end
413
384
 
414
- def validate(map, class_name)
415
- raise(NameError, "Expected attribute map :#{@name} to be defined for class '#{class_name}'") if map.nil?
416
- map.resolve! unless map.resolved?
417
- map
418
- end
419
-
420
- def resolve_associations(must_resolve, resolving)
421
- result = true
422
-
423
- @associations.each do |assoc_name, assoc|
424
- if detect_circular_merge(assoc)
425
- raise "Detected attribute map circular merge references: class=#{@klass_name}, association=#{assoc_name}"
426
- end
427
-
428
- assoc_map = assoc[:attr_map] || assoc[:klass_name].constantize.attribute_maps.try(:fetch, @name, nil)
429
- if assoc_map && !assoc_map.resolved?
430
- if assoc_map.resolve(must_resolve, resolving) && merge_option?(assoc[:options]) && assoc[:attr_map]
431
- merge_map = assoc[:klass_name].constantize.attribute_maps[@name]
432
- assoc_map = merge_map.dup.merge!(assoc_map, :with_options => true, :with_state => true)
433
- end
434
- end
435
- assoc[:attr_map] = assoc_map
436
-
437
- if assoc_map.nil? or !assoc_map.resolve(false, resolving)
438
- result = false
439
- raise "Cannot resolve map for association :#{assoc_name} in #{@klass_name}:#{@name} map" if must_resolve
440
- end
385
+ def validate_attr_writable(attr_name)
386
+ if attr_name.to_s != klass.primary_key && !klass.accessible_attributes.detect { |a| a == attr_name.to_s }
387
+ raise "Expected 'attr_accessible :#{attr_name}' in #{@klass_name}"
441
388
  end
442
-
443
- result
444
389
  end
445
390
 
446
- # If this is the map of an STI subclass, inherit/merge the map from the base class
447
- def resolve_sti_baseclass(must_resolve, resolving)
448
- result = true
449
-
450
- if klass_sti_subclass? && !@sti_baseclass_merged && merge_option?(@options)
451
- if klass.superclass.attribute_maps.try(:fetch, @name, nil).try(:resolve, must_resolve, resolving)
452
- @resolved = @sti_baseclass_merged = true
453
- self.copy(klass.superclass.attribute_maps[@name].dup.merge!(self))
454
- else
455
- result = false
456
- raise "Can't resolve base class map for #{@klass_name}:#{@name} map" if must_resolve
457
- end
391
+ def validate_assoc_map(name, model_attr, attr_map, options)
392
+ if !merge_option?(options) && attr_map.empty?
393
+ raise ArgumentError, "Map association '#{name}' in class #{@klass_name} specifies :replace but has empty block"
394
+ end
395
+ if options[:access] != :ro and !klass.accessible_attributes.include?(model_attr.to_s+'_attributes')
396
+ raise "Expected attr_accessible and/or accepts_nested_attributes_for :#{model_attr} in #{@klass_name} model"
458
397
  end
459
-
460
- result
461
398
  end
462
399
 
463
- # Checks to see if an association specifies a merge, and the association class's
464
- # attribute map attempts to merge the association parent attribute map.
465
- def detect_circular_merge(assoc)
466
- return if assoc.nil? || assoc[:attr_map].nil? || !merge_option?(assoc[:options])
467
- return unless (map = assoc[:klass_name].constantize.attribute_maps.try(:fetch, @name, nil))
468
-
469
- map.associations.each_value do |a|
470
- return true if a[:klass_name] == @klass_name && merge_option?(a[:options]) && a[:attr_map]
471
- end
400
+ def validate_attr_accessor(read_or_write, options)
401
+ return unless (accessor = options[read_or_write])
402
+ raise ArgumentError, "Invalid parameter for #{read_or_write}" unless (accessor.is_a?(Symbol) || accessor.is_a?(Proc))
403
+ accessor
404
+ end
472
405
 
473
- false
406
+ def validate(map, class_name)
407
+ raise(NameError, "Expected attribute map :#{@name} to be defined for class '#{class_name}'") if map.nil?
408
+ map.resolve! unless map.resolved?
409
+ map
474
410
  end
475
411
 
476
412
  # Defines custom read/write attribute methods
477
413
  def define_attr_methods(name, model_attr, options)
414
+ return model_attr if is_model_attr?(model_attr)
415
+
478
416
  read_model_attr = define_attr_reader_method(name, model_attr, options)
479
417
  write_model_attr = define_attr_writer_method(name, model_attr, options)
480
418
 
@@ -486,10 +424,9 @@ module Delineate
486
424
  end
487
425
 
488
426
  def define_attr_reader_method(name, model_attr, options)
489
- return unless (reader = options[:read])
490
- raise ArgumentError, 'Invalid parameter for :read' unless (reader.is_a?(Symbol) || reader.is_a?(Proc))
427
+ return unless (reader = validate_attr_accessor(:read, options))
491
428
 
492
- returning(model_attr == name ? "#{name}_#{@name}" : model_attr) do |method_name|
429
+ returning accessor_method_name(name, model_attr) do |method_name|
493
430
  if reader.is_a?(Symbol)
494
431
  klass.class_eval %(
495
432
  def #{method_name}
@@ -511,10 +448,9 @@ module Delineate
511
448
  end
512
449
 
513
450
  def define_attr_writer_method(name, model_attr, options)
514
- return unless (writer = options[:write])
515
- raise ArgumentError, 'Invalid parameter for :write' unless (writer.is_a?(Symbol) || writer.is_a?(Proc))
451
+ return unless (writer = validate_attr_accessor(:write, options))
516
452
 
517
- returning(model_attr == name ? "#{name}_#{@name}" : model_attr) do |method_name|
453
+ returning accessor_method_name(name, model_attr) do |method_name|
518
454
  if writer.is_a?(Symbol)
519
455
  klass.class_eval %(
520
456
  def #{method_name}=(value)
@@ -537,6 +473,12 @@ module Delineate
537
473
  end
538
474
  end
539
475
 
476
+ def accessor_method_name(name, model_attr)
477
+ model_attr == name ? "#{name}_#{@name}" : model_attr
478
+ end
479
+
480
+
481
+ include Resolve
540
482
  include Serialization
541
483
  include Schema
542
484
 
@@ -8,6 +8,7 @@ module ActiveRecord
8
8
 
9
9
  # Collection of declared attribute maps for the model class
10
10
  class_inheritable_accessor :attribute_maps
11
+ #class_attribute :attribute_maps
11
12
  self.attribute_maps = {}
12
13
 
13
14
  # The map_attributes method lets an ActiveRecord model class define a set
@@ -112,7 +113,7 @@ module ActiveRecord
112
113
  end
113
114
 
114
115
  def attribute_map(map_name)
115
- self.class.attribute_maps[map_name]
116
+ self.class.attribute_map(map_name)
116
117
  end
117
118
 
118
119
  # Returns the attributes as specified in the attribut map. The +format+ paramater
@@ -179,6 +180,7 @@ module ActiveRecord
179
180
  raise "Base class for CTI subclass #{self.name} must specify attribute map #{map.name}" unless base_class_map
180
181
 
181
182
  map.copy(base_class_map)
183
+ map.associations.delete_if {|k, a| a[:klass_name] == self.name}
182
184
  map.instance_variable_set(:@resolved, false)
183
185
  end
184
186
 
@@ -0,0 +1,97 @@
1
+ module Delineate
2
+ module Resolve
3
+ extend ActiveSupport::Concern
4
+
5
+ # Returns true if this map is fully resolved
6
+ def resolved?
7
+ @resolved
8
+ end
9
+
10
+ # Attempts to resolve this map and the maps it depends on, including declared associations.
11
+ # Will raise an exception if the map cannot be fully resolved.
12
+ def resolve!
13
+ resolve(:must_resolve)
14
+ self
15
+ end
16
+
17
+ # Attempts to resolve this map and the maps it depends on, including declared associations,
18
+ # and returns success status as a boolean. If the +must_resolve+ parameter is truthy, raises
19
+ # raise an exception if the map cannot be fully resolved.
20
+ def resolve(must_resolve = false, resolving = [])
21
+ return true if @resolved
22
+ return true if resolving.include?(@klass_name) # prevent infinite recursion
23
+
24
+ resolving.push(@klass_name)
25
+
26
+ result = resolve_associations(must_resolve, resolving)
27
+ result = false unless resolve_sti_baseclass(must_resolve, resolving)
28
+
29
+ resolving.pop
30
+ @resolved = result
31
+ end
32
+
33
+ private
34
+
35
+ # Resolves association maps, and handles map merges as necessary
36
+ def resolve_associations(must_resolve, resolving)
37
+ result = true
38
+
39
+ @associations.each do |assoc_name, assoc|
40
+ detect_circular_merge!(assoc_name, assoc)
41
+
42
+ assoc_map = assoc[:attr_map] || assoc[:klass_name].constantize.attribute_maps.try(:fetch, @name, nil)
43
+ if assoc_map && assoc_map.resolve(must_resolve, resolving)
44
+ assoc_map = merge_assoc_map(assoc, assoc_map) if merge_option?(assoc[:options])
45
+ end
46
+
47
+ assoc[:attr_map] = assoc_map
48
+
49
+ if assoc_map.nil? or !assoc_map.resolve(false, resolving)
50
+ result = false
51
+ raise "Cannot resolve map for association :#{assoc_name} in #{@klass_name}:#{@name} map" if must_resolve
52
+ end
53
+ end
54
+
55
+ result
56
+ end
57
+
58
+ # If this is the map of an STI subclass, inherit/merge the map from the base class
59
+ def resolve_sti_baseclass(must_resolve, resolving)
60
+ result = true
61
+
62
+ if merge_option?(@options) && klass_sti_subclass? && !@sti_baseclass_merged
63
+ if klass.superclass.attribute_maps.try(:fetch, @name, nil).try(:resolve, must_resolve, resolving)
64
+ @resolved = @sti_baseclass_merged = true
65
+ self.copy(klass.superclass.attribute_maps[@name].dup.merge!(self))
66
+ else
67
+ result = false
68
+ raise "Can't resolve base class map for #{@klass_name}:#{@name} map" if must_resolve
69
+ end
70
+ end
71
+
72
+ result
73
+ end
74
+
75
+ # Raises exception if an association specifies a merge, and the association class's
76
+ # attribute map attempts to merge the association parent attribute map.
77
+ def detect_circular_merge!(assoc_name, assoc)
78
+ return if assoc.nil? || assoc[:attr_map].nil? || !merge_option?(assoc[:options])
79
+ return unless (map = assoc[:klass_name].constantize.attribute_maps.try(:fetch, @name, nil))
80
+
81
+ map.associations.each_value do |a|
82
+ if a[:klass_name] == @klass_name && merge_option?(a[:options]) && a[:attr_map]
83
+ raise "Detected attribute map circular merge references: class=#{@klass_name}, association=#{assoc_name}"
84
+ end
85
+ end
86
+ end
87
+
88
+ def merge_assoc_map(assoc, assoc_map)
89
+ if merge_option?(assoc[:options]) && assoc[:attr_map]
90
+ merge_map = assoc[:klass_name].constantize.attribute_maps[@name]
91
+ assoc_map = merge_map.dup.merge!(assoc_map, :with_options => true, :with_state => true)
92
+ end
93
+ assoc_map
94
+ end
95
+
96
+ end
97
+ end
@@ -14,8 +14,8 @@ module Delineate
14
14
  # of the attributes.
15
15
  def serialize(options = {})
16
16
  opts = options[:include_header] ?
17
- {:write_headers => true, :headers => serializable_header, :encoding => "UTF-8"} :
18
- {:encoding => "UTF-8"}
17
+ {:write_headers => true, :headers => serializable_header, :encoding => 'UTF-8'} :
18
+ {:encoding => 'UTF-8'}
19
19
 
20
20
  opts = remove_serializer_class_options(options).merge(opts)
21
21
  opts.delete(:include_header)
@@ -27,18 +27,18 @@ module Delineate
27
27
 
28
28
  # Returns the header row as a CSV string.
29
29
  def serialize_header(options = {})
30
- opts = {:encoding => "UTF-8"}.merge(remove_serializer_class_options(options))
30
+ opts = {:encoding => 'UTF-8'}.merge(remove_serializer_class_options(options))
31
31
  CSV.generate_line(serializable_header, opts)
32
32
  end
33
33
 
34
34
  # Not implemented yet.
35
35
  def serialize_in(csv_string, options = {})
36
- raise "Serializing from CSV is not supported at this time. You can inherit a class from CsvSerializer to write a custom importer."
36
+ raise 'Serializing from CSV is not supported. You can inherit a class from CsvSerializer to write a custom importer.'
37
37
  end
38
38
 
39
39
  # Returns the record's mapped attributes in the serializer's "internal"
40
40
  # format. For this class the representation is an array of one or more
41
- # rows, one row for each item in teh record's has_many collections. Each
41
+ # rows, one row for each item in the record's has_many collections. Each
42
42
  # row is an array of values ordered as follows:
43
43
  #
44
44
  # 1. All the record's mapped attributes in map order.
@@ -70,7 +70,7 @@ module Delineate
70
70
  end
71
71
 
72
72
  # Returns the header row as an array of strings, one for each
73
- # mapped attribute, including nested assoications. The items
73
+ # mapped attribute, including nested associations. The items
74
74
  # appear in the array in the same order as their corresponding
75
75
  # attribute values.
76
76
  def serializable_header(prefix = '')
@@ -93,7 +93,7 @@ module Delineate
93
93
 
94
94
  private
95
95
 
96
- # The diff here is that if the associaton record(s) is empty, we have to generate
96
+ # The diff here is that if the association record(s) is empty, we have to generate
97
97
  # a new empty record: @record.class.new.build_xxx or @record.class.new.xxx.build
98
98
  def add_includes(assoc_type)
99
99
  includes = @options.delete(:include)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: delineate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Smith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-23 00:00:00.000000000 Z
11
+ date: 2014-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  version_requirements: !ruby/object:Gem::Requirement
@@ -147,6 +147,7 @@ files:
147
147
  - lib/delineate/attribute_map.rb
148
148
  - lib/delineate/map_attributes.rb
149
149
  - lib/delineate/map_serializer.rb
150
+ - lib/delineate/resolve.rb
150
151
  - lib/delineate/schema.rb
151
152
  - lib/delineate/serialization.rb
152
153
  - lib/delineate/serializers/csv_serializer.rb