delineate 0.6.1 → 0.6.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: 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