rom 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +81 -0
  3. data/.travis.yml +2 -1
  4. data/CHANGELOG.md +41 -0
  5. data/Gemfile +12 -8
  6. data/Guardfile +17 -11
  7. data/README.md +7 -7
  8. data/Rakefile +29 -0
  9. data/lib/rom.rb +9 -66
  10. data/lib/rom/adapter.rb +45 -12
  11. data/lib/rom/adapter/memory.rb +0 -4
  12. data/lib/rom/adapter/memory/commands.rb +0 -10
  13. data/lib/rom/adapter/memory/dataset.rb +18 -6
  14. data/lib/rom/adapter/memory/storage.rb +0 -3
  15. data/lib/rom/command_registry.rb +24 -43
  16. data/lib/rom/commands.rb +5 -6
  17. data/lib/rom/commands/create.rb +5 -5
  18. data/lib/rom/commands/delete.rb +8 -6
  19. data/lib/rom/commands/result.rb +82 -0
  20. data/lib/rom/commands/update.rb +5 -4
  21. data/lib/rom/commands/with_options.rb +1 -4
  22. data/lib/rom/config.rb +70 -0
  23. data/lib/rom/env.rb +11 -3
  24. data/lib/rom/global.rb +107 -0
  25. data/lib/rom/header.rb +122 -89
  26. data/lib/rom/header/attribute.rb +148 -0
  27. data/lib/rom/mapper.rb +46 -67
  28. data/lib/rom/mapper_builder.rb +20 -73
  29. data/lib/rom/mapper_builder/mapper_dsl.rb +114 -0
  30. data/lib/rom/mapper_builder/model_dsl.rb +29 -0
  31. data/lib/rom/mapper_registry.rb +21 -0
  32. data/lib/rom/model_builder.rb +11 -17
  33. data/lib/rom/processor.rb +28 -0
  34. data/lib/rom/processor/transproc.rb +105 -0
  35. data/lib/rom/reader.rb +81 -21
  36. data/lib/rom/reader_builder.rb +14 -4
  37. data/lib/rom/relation.rb +19 -5
  38. data/lib/rom/relation_builder.rb +20 -6
  39. data/lib/rom/repository.rb +0 -2
  40. data/lib/rom/setup.rb +156 -0
  41. data/lib/rom/{boot → setup}/base_relation_dsl.rb +4 -8
  42. data/lib/rom/setup/command_dsl.rb +46 -0
  43. data/lib/rom/setup/finalize.rb +125 -0
  44. data/lib/rom/setup/mapper_dsl.rb +19 -0
  45. data/lib/rom/{boot → setup}/relation_dsl.rb +1 -4
  46. data/lib/rom/setup/schema_dsl.rb +33 -0
  47. data/lib/rom/support/registry.rb +10 -6
  48. data/lib/rom/version.rb +1 -1
  49. data/rom.gemspec +3 -1
  50. data/spec/integration/adapters/extending_relations_spec.rb +0 -2
  51. data/spec/integration/commands/create_spec.rb +2 -9
  52. data/spec/integration/commands/delete_spec.rb +4 -5
  53. data/spec/integration/commands/error_handling_spec.rb +4 -3
  54. data/spec/integration/commands/update_spec.rb +3 -8
  55. data/spec/integration/mappers/deep_embedded_spec.rb +52 -0
  56. data/spec/integration/mappers/definition_dsl_spec.rb +0 -118
  57. data/spec/integration/mappers/embedded_spec.rb +82 -0
  58. data/spec/integration/mappers/group_spec.rb +170 -0
  59. data/spec/integration/mappers/prefixing_attributes_spec.rb +2 -2
  60. data/spec/integration/mappers/renaming_attributes_spec.rb +8 -6
  61. data/spec/integration/mappers/symbolizing_attributes_spec.rb +80 -0
  62. data/spec/integration/mappers/wrap_spec.rb +162 -0
  63. data/spec/integration/multi_repo_spec.rb +64 -0
  64. data/spec/integration/relations/reading_spec.rb +12 -8
  65. data/spec/integration/relations/registry_dsl_spec.rb +1 -3
  66. data/spec/integration/schema_spec.rb +10 -0
  67. data/spec/integration/setup_spec.rb +57 -6
  68. data/spec/spec_helper.rb +2 -1
  69. data/spec/unit/config_spec.rb +60 -0
  70. data/spec/unit/rom/adapter/memory/dataset_spec.rb +52 -0
  71. data/spec/unit/rom/adapter_spec.rb +31 -11
  72. data/spec/unit/rom/header_spec.rb +60 -16
  73. data/spec/unit/rom/mapper_builder_spec.rb +311 -0
  74. data/spec/unit/rom/mapper_registry_spec.rb +25 -0
  75. data/spec/unit/rom/mapper_spec.rb +4 -5
  76. data/spec/unit/rom/model_builder_spec.rb +15 -13
  77. data/spec/unit/rom/processor/transproc_spec.rb +331 -0
  78. data/spec/unit/rom/reader_spec.rb +73 -0
  79. data/spec/unit/rom/registry_spec.rb +38 -0
  80. data/spec/unit/rom/relation_spec.rb +0 -1
  81. data/spec/unit/rom/setup_spec.rb +55 -0
  82. data/spec/unit/rom_spec.rb +14 -0
  83. metadata +62 -22
  84. data/Gemfile.devtools +0 -71
  85. data/lib/rom/boot.rb +0 -197
  86. data/lib/rom/boot/command_dsl.rb +0 -48
  87. data/lib/rom/boot/dsl.rb +0 -37
  88. data/lib/rom/boot/mapper_dsl.rb +0 -23
  89. data/lib/rom/boot/schema_dsl.rb +0 -27
  90. data/lib/rom/ra.rb +0 -172
  91. data/lib/rom/ra/operation/group.rb +0 -47
  92. data/lib/rom/ra/operation/join.rb +0 -39
  93. data/lib/rom/ra/operation/wrap.rb +0 -45
  94. data/lib/rom/transformer.rb +0 -77
  95. data/spec/integration/ra/group_spec.rb +0 -46
  96. data/spec/integration/ra/join_spec.rb +0 -50
  97. data/spec/integration/ra/wrap_spec.rb +0 -37
  98. data/spec/unit/rom/ra/operation/group_spec.rb +0 -55
  99. data/spec/unit/rom/ra/operation/wrap_spec.rb +0 -29
  100. data/spec/unit/rom/transformer_spec.rb +0 -41
@@ -0,0 +1,148 @@
1
+ module ROM
2
+ class Header
3
+ # An attribute provides information about a specific attribute in a tuple
4
+ #
5
+ # This may include information about how an attribute should be renamed,
6
+ # or how its value should coerced.
7
+ #
8
+ # More complex attributes describe how an attribute should be transformed.
9
+ #
10
+ # @private
11
+ class Attribute
12
+ include Equalizer.new(:name, :key, :type)
13
+
14
+ # @return [Symbol] name of an attribute
15
+ #
16
+ # @api private
17
+ attr_reader :name
18
+
19
+ # @return [Symbol] key of an attribute that corresponds to tuple attribute
20
+ #
21
+ # @api private
22
+ attr_reader :key
23
+
24
+ # @return [Symbol] type identifier (defaults to :object)
25
+ #
26
+ # @api private
27
+ attr_reader :type
28
+
29
+ # @return [Hash] additional meta information
30
+ #
31
+ # @api private
32
+ attr_reader :meta
33
+
34
+ # Return attribute class for a give meta hash
35
+ #
36
+ # @param [Hash] hash with type information and optional transformation info
37
+ #
38
+ # @return [Class]
39
+ #
40
+ # @api private
41
+ def self.[](meta)
42
+ type = meta[:type]
43
+
44
+ if type.equal?(:hash)
45
+ meta[:wrap] ? Wrap : Hash
46
+ elsif type.equal?(:array)
47
+ meta[:group] ? Group : Array
48
+ else
49
+ self
50
+ end
51
+ end
52
+
53
+ # Coerce an array with attribute meta-data into an attribute object
54
+ #
55
+ # @param [Array<Symbol,Hash>] name/options pair
56
+ #
57
+ # @return [Attribute]
58
+ #
59
+ # @api private
60
+ def self.coerce(input)
61
+ name = input[0]
62
+ meta = (input[1] || {}).dup
63
+
64
+ meta[:type] ||= :object
65
+
66
+ if meta.key?(:header)
67
+ meta[:header] = Header.coerce(meta[:header], meta[:model])
68
+ end
69
+
70
+ self[meta].new(name, meta)
71
+ end
72
+
73
+ # @api private
74
+ def initialize(name, meta)
75
+ @name = name
76
+ @meta = meta
77
+ @key = meta.fetch(:from) { name }
78
+ @type = meta.fetch(:type)
79
+ end
80
+
81
+ # Return if an attribute has a specific type identifier
82
+ #
83
+ # @api private
84
+ def typed?
85
+ type != :object
86
+ end
87
+
88
+ # Return if an attribute should be aliased
89
+ #
90
+ # @api private
91
+ def aliased?
92
+ key != name
93
+ end
94
+
95
+ # Return :key-to-:name mapping hash
96
+ #
97
+ # @return [Hash]
98
+ #
99
+ # @api private
100
+ def mapping
101
+ { key => name }
102
+ end
103
+ end
104
+
105
+ # Embedded attribute is a special attribute type that has a header
106
+ #
107
+ # This is the base of complex attributes like Hash or Group
108
+ #
109
+ # @private
110
+ class Embedded < Attribute
111
+ include Equalizer.new(:name, :key, :type, :header)
112
+
113
+ # return [Header] header of an attribute
114
+ #
115
+ # @api private
116
+ attr_reader :header
117
+
118
+ # @api private
119
+ def initialize(*)
120
+ super
121
+ @header = meta.fetch(:header)
122
+ end
123
+
124
+ # Return tuple keys from the header
125
+ #
126
+ # @return [Array<Symbol>]
127
+ #
128
+ # @api private
129
+ def tuple_keys
130
+ header.tuple_keys
131
+ end
132
+ end
133
+
134
+ # Array is an embedded attribute type
135
+ Array = Class.new(Embedded)
136
+
137
+ # Hash is an embedded attribute type
138
+ Hash = Class.new(Embedded)
139
+
140
+ # Wrap is a special type of Hash attribute that requires wrapping
141
+ # transformation
142
+ Wrap = Class.new(Hash)
143
+
144
+ # Group is a special type of Array attribute that requires grouping
145
+ # transformation
146
+ Group = Class.new(Array)
147
+ end
148
+ end
@@ -1,83 +1,62 @@
1
1
  module ROM
2
-
3
- # @api private
2
+ # Mapper is a simple object that uses a transformer to load relations
3
+ #
4
+ # @private
4
5
  class Mapper
5
- attr_reader :header, :model, :loader, :transformer
6
-
7
- class Basic < Mapper
8
- attr_reader :mapping
9
-
10
- def initialize(*args)
11
- super
12
- @mapping = header.mapping
13
- end
14
-
15
- def load(tuple)
16
- super(Hash[call(tuple)])
17
- end
18
-
19
- def call(tuple)
20
- tuple.map { |key, value| [header.mapping[key], value] }
21
- end
6
+ # @return [Object] transformer object built by a processor
7
+ #
8
+ # @api private
9
+ attr_reader :transformer
10
+
11
+ # @return [Header] header that was used to build the transformer
12
+ #
13
+ # @api private
14
+ attr_reader :header
15
+
16
+ # @return [Hash] registered processors
17
+ #
18
+ # @api private
19
+ def self.processors
20
+ @_processors ||= {}
22
21
  end
23
22
 
24
- class Recursive < Basic
25
- attr_reader :transformer
26
-
27
- def initialize(*args)
28
- super
29
- @transformer = Transformer.build(header)
30
- end
31
-
32
- def process(relation)
33
- transformer.call(relation.to_a).each { |tuple| yield(load(tuple)) }
34
- end
35
-
36
- def call(tuple, header = self.header)
37
- mapping = header.mapping
38
-
39
- tuple.map do |key, value|
40
- case value
41
- when Hash
42
- [key, loader[Hash[call(value, header[key])], header[key].model]]
43
- when Array
44
- [key, value.map { |v| loader[Hash[call(v, header[key])], header[key].model] }]
45
- else
46
- [mapping[key], value]
47
- end
48
- end
49
- end
23
+ # Register a processor class
24
+ #
25
+ # @return [Hash]
26
+ #
27
+ # @api private
28
+ def self.register_processor(processor)
29
+ name = processor.name.split('::').last.downcase.to_sym
30
+ processors.update(name => processor)
50
31
  end
51
32
 
52
- def self.build(header, model)
53
- klass =
54
- if header.any? { |attribute| attribute.embedded? }
55
- Recursive
56
- elsif header.any? { |attribute| attribute.aliased? }
57
- Basic
58
- else
59
- self
60
- end
61
-
62
- loader = Proc.new { |tuple, m| m ? m.new(tuple) : tuple }
63
-
64
- klass.new(header, model, loader)
33
+ # Build a mapper using provided processor type
34
+ #
35
+ # @return [Mapper]
36
+ #
37
+ # @api private
38
+ def self.build(header, processor = :transproc)
39
+ new(processors.fetch(processor).build(header), header)
65
40
  end
66
41
 
67
- def initialize(header, model, loader)
42
+ # @api private
43
+ def initialize(transformer, header)
44
+ @transformer = transformer
68
45
  @header = header
69
- @model = model
70
- @loader = loader
71
46
  end
72
47
 
73
- def process(relation)
74
- relation.each { |tuple| yield(load(tuple)) }
48
+ # @return [Class] optional model that is instantiated by a mapper
49
+ #
50
+ # @api private
51
+ def model
52
+ header.model
75
53
  end
76
54
 
77
- def load(tuple)
78
- loader[tuple, model]
55
+ # Process a relation using the transfomer
56
+ #
57
+ # @api private
58
+ def process(relation, &block)
59
+ transformer[relation.to_a].each(&block)
79
60
  end
80
-
81
61
  end
82
-
83
62
  end
@@ -1,105 +1,52 @@
1
- require 'rom/model_builder'
1
+ require 'rom/mapper_builder/model_dsl'
2
+ require 'rom/mapper_builder/mapper_dsl'
2
3
 
3
4
  module ROM
4
-
5
5
  # @api private
6
6
  class MapperBuilder
7
+ attr_reader :name, :root, :options, :prefix, :symbolize_keys, :dsl
7
8
 
8
- class AttributeDSL
9
- attr_reader :attributes, :model_class, :model_builder
10
-
11
- def initialize
12
- @attributes = []
13
- end
14
-
15
- def header
16
- Header.coerce(attributes)
17
- end
18
-
19
- def model(options = nil)
20
- if options.is_a?(Class)
21
- @model_class = options
22
- elsif options
23
- @model_builder = ModelBuilder[options.fetch(:type) { :poro }].new(options)
24
- end
25
-
26
- if options
27
- self
28
- else
29
- model_class || (model_builder && model_builder.call(header))
30
- end
31
- end
32
-
33
- def attribute(name, options = {})
34
- attributes << [name, options]
35
- end
36
- end
37
-
38
- attr_reader :name, :root, :prefix,
39
- :model_builder, :model_class, :attributes
9
+ DEFAULT_PROCESSOR = :transproc
40
10
 
41
11
  def initialize(name, root, options = {})
42
12
  @name = name
13
+ @options = options
43
14
  @root = root
44
15
  @prefix = options[:prefix]
16
+ @symbolize_keys = options[:symbolize_keys]
45
17
 
46
- @attributes =
18
+ attributes =
47
19
  if options[:inherit_header]
48
20
  root.header.map { |attr| [prefix ? :"#{prefix}_#{attr}" : attr] }
49
21
  else
50
22
  []
51
23
  end
52
- end
53
24
 
54
- def model(options)
55
- if options.is_a?(Class)
56
- @model_class = options
57
- else
58
- @model_builder = ModelBuilder[options.fetch(:type) { :poro }].new(options)
59
- end
60
-
61
- self
62
- end
63
-
64
- def attribute(name, options = {})
65
- options[:from] = :"#{prefix}_#{name}" if prefix
66
- attributes << [name, options]
67
- end
68
-
69
- def exclude(name)
70
- attributes.delete([name])
71
- end
25
+ @dsl = MapperDSL.new(attributes, options)
72
26
 
73
- def group(options, &block)
74
- attribute_dsl(options, Array, &block)
27
+ @processor = DEFAULT_PROCESSOR
75
28
  end
76
29
 
77
- def wrap(options, &block)
78
- attribute_dsl(options, Hash, &block)
30
+ def processor(identifier = nil)
31
+ if identifier
32
+ @processor = identifier
33
+ else
34
+ @processor
35
+ end
79
36
  end
80
37
 
81
38
  def call
82
- header = Header.coerce(attributes)
83
-
84
- @model_class = model_builder.call(header) if model_builder
85
-
86
- Mapper.build(header, model_class)
39
+ Mapper.build(dsl.header, processor)
87
40
  end
88
41
 
89
42
  private
90
43
 
91
- def attribute_dsl(options, type, &block)
92
- if block
93
- dsl = AttributeDSL.new
94
- dsl.instance_exec(&block)
95
- attributes << [options, header: dsl.header, type: type, model: dsl.model]
44
+ def method_missing(name, *args, &block)
45
+ if dsl.respond_to?(name)
46
+ dsl.public_send(name, *args, &block)
96
47
  else
97
- options.each do |name, header|
98
- attributes << [name, header: header.zip, type: type]
99
- end
48
+ super
100
49
  end
101
50
  end
102
-
103
51
  end
104
-
105
52
  end
@@ -0,0 +1,114 @@
1
+ require 'rom/mapper_builder/model_dsl'
2
+
3
+ module ROM
4
+ class MapperBuilder
5
+ # @api private
6
+ class MapperDSL
7
+ include ModelDSL
8
+
9
+ attr_reader :attributes, :options, :symbolize_keys, :prefix
10
+
11
+ def initialize(attributes, options)
12
+ @attributes = attributes
13
+ @options = options
14
+ @symbolize_keys = options[:symbolize_keys]
15
+ @prefix = options[:prefix]
16
+ end
17
+
18
+ def attribute(name, options = EMPTY_HASH)
19
+ with_attr_options(name, options) do |attr_options|
20
+ add_attribute(name, attr_options)
21
+ end
22
+ end
23
+
24
+ def exclude(*names)
25
+ names.each { |name| attributes.delete([name]) }
26
+ end
27
+
28
+ def embedded(name, options, &block)
29
+ with_attr_options(name) do |attr_options|
30
+ dsl = new(options, &block)
31
+
32
+ attr_options.update(options)
33
+
34
+ add_attribute(
35
+ name, { header: dsl.header, type: :array }.update(attr_options)
36
+ )
37
+ end
38
+ end
39
+
40
+ def wrap(*args, &block)
41
+ with_name_or_options(*args) do |name, options|
42
+ dsl(name, { type: :hash, wrap: true }.update(options), &block)
43
+ end
44
+ end
45
+
46
+ def group(*args, &block)
47
+ with_name_or_options(*args) do |name, options|
48
+ dsl(name, { type: :array, group: true }.update(options), &block)
49
+ end
50
+ end
51
+
52
+ def header
53
+ Header.coerce(attributes, model)
54
+ end
55
+
56
+ private
57
+
58
+ def with_attr_options(name, options = EMPTY_HASH)
59
+ attr_options = options.dup
60
+
61
+ attr_options[:from] ||= :"#{prefix}_#{name}" if prefix
62
+
63
+ if symbolize_keys
64
+ attr_options.update(from: attr_options.fetch(:from) { name }.to_s)
65
+ end
66
+
67
+ yield(attr_options)
68
+ end
69
+
70
+ def with_name_or_options(*args)
71
+ name, options =
72
+ if args.size > 1
73
+ args
74
+ else
75
+ [args.first, {}]
76
+ end
77
+
78
+ yield(name, options)
79
+ end
80
+
81
+ def dsl(name_or_attrs, options, &block)
82
+ if block
83
+ attributes_from_block(name_or_attrs, options, &block)
84
+ else
85
+ attributes_from_hash(name_or_attrs, options)
86
+ end
87
+ end
88
+
89
+ def attributes_from_block(name, options, &block)
90
+ dsl = new(options, &block)
91
+ add_attribute(name, options.update(header: dsl.header))
92
+ end
93
+
94
+ def attributes_from_hash(hash, options)
95
+ hash.each do |name, header|
96
+ with_attr_options(name, options) do |attr_options|
97
+ add_attribute(name, attr_options.update(header: header.zip))
98
+ end
99
+ end
100
+ end
101
+
102
+ def add_attribute(name, options)
103
+ exclude(name, name.to_s)
104
+ attributes << [name, options]
105
+ end
106
+
107
+ def new(options, &block)
108
+ dsl = self.class.new([], @options.merge(options))
109
+ dsl.instance_exec(&block)
110
+ dsl
111
+ end
112
+ end
113
+ end
114
+ end