rom 0.4.1 → 0.4.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: 1cedd21af22c9eed4d993f2550a24089b9f8cacb
4
- data.tar.gz: 5275819a3330d267f7ffd5dc8d9cc8a8ea186a9b
3
+ metadata.gz: 85a6e3c77b6e9a8d724fdd73056c4bdde3730beb
4
+ data.tar.gz: 6f964fb1181920ab5222f66d3482cf9ebcddf810
5
5
  SHA512:
6
- metadata.gz: c4d159975a7a1a8ed67c08f7f085e9f8fa57c1ac40c36ed71d20b6111d326b3ac8d18fe46d94f9e436e13fc73f099320c0eab1e50fb717c2c246459575261496
7
- data.tar.gz: a77514fb8bbafeeaef756c069e182b77d147999aca85000bc7089ff4a9d557cd3a003704b1766645fefe0d80c49c41456f19a38eaebed6697b5f8c96da0b1632
6
+ metadata.gz: 31e1ff16669d9481ba1d516f27d9016054ef35c1ac2daa3dad5de8792ef9bd30813b7de01f48132726574f52edb7a1b2a374245602932683b02a473e1c577e93
7
+ data.tar.gz: 62698c8268af71829168e3c8b27edac7553cc7e03b7c6362b485585e9404e10bd9bb1438f68cf3be9f8122c7d8670ac280e4867345f9f5a1d8aa6759e5e2d265
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## v0.4.2 2014-12-19
2
+
3
+ ### Added
4
+
5
+ * Mappers support tuple transformation using wrap and group operations (solnic)
6
+ * PORO model builder supports namespaced constants via `name: 'MyApp:Entities::User` (solnic)
7
+
8
+ ### Changed
9
+
10
+ * `ROM::RA` interface is no longer mixed into relations by default (solnic)
11
+ * ~2.5 x speed up in aggregate mapping (solnic)
12
+ * PORO model builder only defines attribute readers now (no writers!) (solnic)
13
+ * Registry objects in Env will now raise `KeyError` when unknown name is referenced (solnic)
14
+
15
+ [Compare v0.4.1...v0.4.2](https://github.com/rom-rb/rom/compare/v0.4.1...v0.4.2)
16
+
1
17
  ## v0.4.1 2014-12-15
2
18
 
3
19
  ### Added
@@ -5,6 +21,7 @@
5
21
  * Adapter can now implement `Adapter#dataset(name, header)` to return a dataset (solnic)
6
22
  * For multi-step setup the DSL is available in `ROM` too (solnic)
7
23
  * Global environment can be stored via `ROM.finalize` and accessible via `ROM.env` (solnic)
24
+ * Mapper won't infer attributes from the header if `:inherit_header` option is set to false (solnic)
8
25
 
9
26
  ### Changed
10
27
 
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ gemspec
4
4
 
5
5
  group :console do
6
6
  gem 'pry'
7
- gem 'pg'
7
+ gem 'pg', platforms: [:mri]
8
8
  end
9
9
 
10
10
  group :test do
@@ -31,6 +31,6 @@ group :sql do
31
31
  end
32
32
 
33
33
  group :benchmarks do
34
- gem 'activerecord'
34
+ gem 'activerecord', '4.2.0.rc3'
35
35
  gem 'benchmark-ips'
36
36
  end
data/lib/rom.rb CHANGED
@@ -7,6 +7,7 @@ require 'rom/support/registry'
7
7
 
8
8
  require 'rom/header'
9
9
  require 'rom/relation'
10
+ require 'rom/transformer'
10
11
  require 'rom/mapper'
11
12
  require 'rom/reader'
12
13
 
@@ -16,6 +17,8 @@ require 'rom/adapter'
16
17
  require 'rom/repository'
17
18
  require 'rom/env'
18
19
 
20
+ require 'rom/ra'
21
+
19
22
  require 'rom/boot'
20
23
 
21
24
  module ROM
@@ -8,6 +8,7 @@ module ROM
8
8
  @env = env
9
9
  @name = name
10
10
  @header = []
11
+ @repository = nil
11
12
  end
12
13
 
13
14
  def repository(name = nil)
data/lib/rom/header.rb CHANGED
@@ -24,7 +24,7 @@ module ROM
24
24
  end
25
25
 
26
26
  def mapping
27
- [name, header.mapping]
27
+ header.mapping
28
28
  end
29
29
 
30
30
  def embedded?
@@ -107,7 +107,7 @@ module ROM
107
107
  end
108
108
 
109
109
  def mapping
110
- Hash[map(&:mapping)]
110
+ Hash[select { |a| !a.embedded? }.map(&:mapping)]
111
111
  end
112
112
 
113
113
  def values
data/lib/rom/mapper.rb CHANGED
@@ -2,7 +2,7 @@ module ROM
2
2
 
3
3
  # @api private
4
4
  class Mapper
5
- attr_reader :header, :model, :loader
5
+ attr_reader :header, :model, :loader, :transformer
6
6
 
7
7
  class Basic < Mapper
8
8
  attr_reader :mapping
@@ -12,21 +12,36 @@ module ROM
12
12
  @mapping = header.mapping
13
13
  end
14
14
 
15
- def load(tuple, mapping = self.mapping)
16
- loader[Hash[call(tuple, mapping)]]
15
+ def load(tuple)
16
+ super(Hash[call(tuple)])
17
17
  end
18
18
 
19
- def call(tuple, mapping = self.mapping)
20
- tuple.map { |key, value| [mapping[key], value] }
19
+ def call(tuple)
20
+ tuple.map { |key, value| [header.mapping[key], value] }
21
21
  end
22
22
  end
23
23
 
24
24
  class Recursive < Basic
25
- def call(tuple, mapping = self.mapping)
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
+
26
39
  tuple.map do |key, value|
27
40
  case value
28
- when Hash then [key, Hash[call(value, mapping[key])]]
29
- when Array then [key, value.map { |v| Hash[call(v, mapping[key])] }]
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] }]
30
45
  else
31
46
  [mapping[key], value]
32
47
  end
@@ -44,12 +59,7 @@ module ROM
44
59
  self
45
60
  end
46
61
 
47
- loader =
48
- if model
49
- -> tuple { model.new(tuple) }
50
- else
51
- -> tuple { tuple }
52
- end
62
+ loader = Proc.new { |tuple, m| m ? m.new(tuple) : tuple }
53
63
 
54
64
  klass.new(header, model, loader)
55
65
  end
@@ -60,8 +70,12 @@ module ROM
60
70
  @loader = loader
61
71
  end
62
72
 
73
+ def process(relation)
74
+ relation.each { |tuple| yield(load(tuple)) }
75
+ end
76
+
63
77
  def load(tuple)
64
- loader[tuple]
78
+ loader[tuple, model]
65
79
  end
66
80
 
67
81
  end
@@ -6,7 +6,7 @@ module ROM
6
6
  class MapperBuilder
7
7
 
8
8
  class AttributeDSL
9
- attr_reader :attributes
9
+ attr_reader :attributes, :model_class, :model_builder
10
10
 
11
11
  def initialize
12
12
  @attributes = []
@@ -16,6 +16,20 @@ module ROM
16
16
  Header.coerce(attributes)
17
17
  end
18
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
+
19
33
  def attribute(name, options = {})
20
34
  attributes << [name, options]
21
35
  end
@@ -78,7 +92,7 @@ module ROM
78
92
  if block
79
93
  dsl = AttributeDSL.new
80
94
  dsl.instance_exec(&block)
81
- attributes << [options, header: dsl.header, type: type]
95
+ attributes << [options, header: dsl.header, type: type, model: dsl.model]
82
96
  else
83
97
  options.each do |name, header|
84
98
  attributes << [name, header: header.zip, type: type]
@@ -2,7 +2,7 @@ module ROM
2
2
 
3
3
  # @api private
4
4
  class ModelBuilder
5
- attr_reader :options, :const_name, :klass
5
+ attr_reader :options, :const_name, :namespace, :klass
6
6
 
7
7
  def self.[](type)
8
8
  case type
@@ -16,18 +16,31 @@ module ROM
16
16
  new(*args).call
17
17
  end
18
18
 
19
- def initialize(options)
19
+ def initialize(options = {})
20
20
  @options = options
21
- @const_name = options[:name]
21
+
22
+ if options[:name]
23
+ split = options[:name].split('::')
24
+
25
+ @const_name = split.last
26
+
27
+ @namespace =
28
+ if split.size > 1
29
+ Inflecto.constantize((split-[const_name]).join('::'))
30
+ else
31
+ Object
32
+ end
33
+ end
22
34
  end
23
35
 
24
36
  def define_const
25
- Object.const_set(const_name, klass)
37
+ namespace.const_set(const_name, klass)
26
38
  end
27
39
 
28
40
  def call(header)
29
41
  define_class(header)
30
42
  define_const if const_name
43
+ @klass
31
44
  end
32
45
 
33
46
  class PORO < ModelBuilder
@@ -37,15 +50,12 @@ module ROM
37
50
 
38
51
  attributes = header.keys
39
52
 
40
- @klass.send(:attr_accessor, *attributes)
41
-
42
- ivar_list = attributes.map { |name| "@#{name}" }.join(", ")
43
- sym_list = attributes.map { |name| ":#{name}" }.join(", ")
53
+ @klass.send(:attr_reader, *attributes)
44
54
 
45
55
  @klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
46
- def initialize(params)
47
- #{ivar_list} = params.values_at(#{sym_list})
48
- end
56
+ def initialize(params)
57
+ #{attributes.map { |name| "@#{name} = params[:#{name}]" }.join("\n")}
58
+ end
49
59
  RUBY
50
60
 
51
61
  self
data/lib/rom/ra.rb CHANGED
@@ -9,186 +9,162 @@ module ROM
9
9
  # @api private
10
10
  module RA
11
11
 
12
- # Exposes in-memory relational operations
12
+ # Join two relations in-memory using natural-join
13
13
  #
14
- # See examples for join, group and wrap operations
14
+ # @example
15
+ #
16
+ # require 'rom'
17
+ # require 'rom/adapter/memory'
18
+ #
19
+ # setup = ROM.setup(memory: 'memory://localhost')
20
+ #
21
+ # setup.schema do
22
+ # base_relation(:users) do
23
+ # repository :memory
24
+ #
25
+ # attribute :user_id
26
+ # attribute :name
27
+ # end
28
+ #
29
+ # base_relation(:tasks) do
30
+ # repository :memory
31
+ #
32
+ # attribute :user_id
33
+ # attribute :title
34
+ # end
35
+ # end
36
+ #
37
+ # setup.relation(:tasks)
38
+ #
39
+ # setup.relation(:users) do
40
+ # def with_tasks
41
+ # in_memory { join(tasks) }
42
+ # end
43
+ # end
44
+ #
45
+ # rom.relations.users.insert user_id: 1, name: 'Piotr'
46
+ # rom.relations.tasks.insert user_id: 1, title: 'Relax'
47
+ #
48
+ # rom.relations.users.with_tasks.to_a
49
+ # => [{:user_id=>1, :name=>"Piotr", :title=>"Relax"}]
15
50
  #
16
51
  # @api public
17
- def in_memory(&block)
18
- DSL.new(self).instance_exec(&block)
52
+ def join(*args)
53
+ left, right = args.size > 1 ? args : [self, args.first]
54
+ Operation::Join.new(left, right)
19
55
  end
20
56
 
21
- class DSL
22
- include Concord.new(:relation)
23
-
24
- # Join two relations in-memory using natural-join
25
- #
26
- # @example
27
- #
28
- # require 'rom'
29
- # require 'rom/adapter/memory'
30
- #
31
- # setup = ROM.setup(memory: 'memory://localhost')
32
- #
33
- # setup.schema do
34
- # base_relation(:users) do
35
- # repository :memory
36
- #
37
- # attribute :user_id
38
- # attribute :name
39
- # end
40
- #
41
- # base_relation(:tasks) do
42
- # repository :memory
43
- #
44
- # attribute :user_id
45
- # attribute :title
46
- # end
47
- # end
48
- #
49
- # setup.relation(:tasks)
50
- #
51
- # setup.relation(:users) do
52
- # def with_tasks
53
- # in_memory { join(tasks) }
54
- # end
55
- # end
56
- #
57
- # rom.relations.users.insert user_id: 1, name: 'Piotr'
58
- # rom.relations.tasks.insert user_id: 1, title: 'Relax'
59
- #
60
- # rom.relations.users.with_tasks.to_a
61
- # => [{:user_id=>1, :name=>"Piotr", :title=>"Relax"}]
62
- #
63
- # @api public
64
- def join(*args)
65
- left, right = args.size > 1 ? args : [relation, args.first]
66
- Operation::Join.new(left, right)
67
- end
68
-
69
- # Groups two relations in-memory using group operation
70
- #
71
- # @example
72
- #
73
- # require 'rom'
74
- # require 'rom/adapter/memory'
75
- #
76
- # setup = ROM.setup(memory: 'memory://localhost')
77
- #
78
- # setup.schema do
79
- # base_relation(:users) do
80
- # repository :memory
81
- #
82
- # attribute :user_id
83
- # attribute :name
84
- # end
85
- #
86
- # base_relation(:tasks) do
87
- # repository :memory
88
- #
89
- # attribute :user_id
90
- # attribute :title
91
- # end
92
- # end
93
- #
94
- # setup.relation(:tasks)
95
- #
96
- # setup.relation(:users) do
97
- # def with_tasks
98
- # in_memory { group(join(tasks), tasks: [:title]) }
99
- # end
100
- # end
101
- #
102
- # rom.relations.users.insert user_id: 1, name: 'Piotr'
103
- # rom.relations.tasks.insert user_id: 1, title: 'Work'
104
- # rom.relations.tasks.insert user_id: 1, title: 'Relax'
105
- #
106
- # rom.relations.users.with_tasks.to_a
107
- # => [{:user_id=>1, :name=>"Piotr", tasks: [{:title=>"Relax"}, {:title=>"Work"}]}]
108
- #
109
- # @api public
110
- def group(*args)
111
- with_options(*args) { |relation, options|
112
- Operation::Group.new(relation, options)
113
- }
114
- end
115
-
116
- # Embed one relation in another in-memory using wrap operation
117
- #
118
- # @example
119
- #
120
- # require 'rom'
121
- # require 'rom/adapter/memory'
122
- #
123
- # setup = ROM.setup(memory: 'memory://localhost')
124
- #
125
- # setup.schema do
126
- # base_relation(:users) do
127
- # repository :memory
128
- #
129
- # attribute :user_id
130
- # attribute :name
131
- # end
132
- #
133
- # base_relation(:addresses) do
134
- # repository :memory
135
- #
136
- # attribute :user_id
137
- # attribute :street
138
- # attribute :zipcode
139
- # attribute :city
140
- # end
141
- # end
142
- #
143
- # setup.relation(:addresses)
144
- #
145
- # setup.relation(:users) do
146
- # def with_address
147
- # in_memory { wrap(join(addresses), address: [:street, :zipcode, :city]) }
148
- # end
149
- # end
150
- #
151
- # rom = setup.finalize
152
- #
153
- # rom.relations.users.insert user_id: 1, name: 'Piotr'
154
- # rom.relations.addresses.insert user_id: 1, street: 'Street 1', zipcode: '123', city: 'Kraków'
155
- #
156
- # rom.relations.users.with_address.to_a
157
- # => [{:user_id=>1, :name=>"Piotr", :address=>{:street=>"Street 1", :zipcode=>"123", :city=>"Kraków"}}]
158
- #
159
- # @api public
160
- def wrap(*args)
161
- with_options(*args) { |relation, options|
162
- Operation::Wrap.new(relation, options)
163
- }
164
- end
165
-
166
- # @api private
167
- def respond_to_missing?(name, include_private = false)
168
- relation.respond_to?(name) || super
169
- end
170
-
171
- private
57
+ # Groups two relations in-memory using group operation
58
+ #
59
+ # @example
60
+ #
61
+ # require 'rom'
62
+ # require 'rom/adapter/memory'
63
+ #
64
+ # setup = ROM.setup(memory: 'memory://localhost')
65
+ #
66
+ # setup.schema do
67
+ # base_relation(:users) do
68
+ # repository :memory
69
+ #
70
+ # attribute :user_id
71
+ # attribute :name
72
+ # end
73
+ #
74
+ # base_relation(:tasks) do
75
+ # repository :memory
76
+ #
77
+ # attribute :user_id
78
+ # attribute :title
79
+ # end
80
+ # end
81
+ #
82
+ # setup.relation(:tasks)
83
+ #
84
+ # setup.relation(:users) do
85
+ # def with_tasks
86
+ # in_memory { group(join(tasks), tasks: [:title]) }
87
+ # end
88
+ # end
89
+ #
90
+ # rom.relations.users.insert user_id: 1, name: 'Piotr'
91
+ # rom.relations.tasks.insert user_id: 1, title: 'Work'
92
+ # rom.relations.tasks.insert user_id: 1, title: 'Relax'
93
+ #
94
+ # rom.relations.users.with_tasks.to_a
95
+ # => [{:user_id=>1, :name=>"Piotr", tasks: [{:title=>"Relax"}, {:title=>"Work"}]}]
96
+ #
97
+ # @api public
98
+ def group(*args)
99
+ ra_with_options(*args) { |relation, options|
100
+ Operation::Group.new(relation, options)
101
+ }
102
+ end
172
103
 
173
- # @api private
174
- def with_options(*args)
175
- relation =
176
- if args.size > 1
177
- args.first
178
- else
179
- self.relation
180
- end
104
+ # Embed one relation in another in-memory using wrap operation
105
+ #
106
+ # @example
107
+ #
108
+ # require 'rom'
109
+ # require 'rom/adapter/memory'
110
+ #
111
+ # setup = ROM.setup(memory: 'memory://localhost')
112
+ #
113
+ # setup.schema do
114
+ # base_relation(:users) do
115
+ # repository :memory
116
+ #
117
+ # attribute :user_id
118
+ # attribute :name
119
+ # end
120
+ #
121
+ # base_relation(:addresses) do
122
+ # repository :memory
123
+ #
124
+ # attribute :user_id
125
+ # attribute :street
126
+ # attribute :zipcode
127
+ # attribute :city
128
+ # end
129
+ # end
130
+ #
131
+ # setup.relation(:addresses)
132
+ #
133
+ # setup.relation(:users) do
134
+ # def with_address
135
+ # in_memory { wrap(join(addresses), address: [:street, :zipcode, :city]) }
136
+ # end
137
+ # end
138
+ #
139
+ # rom = setup.finalize
140
+ #
141
+ # rom.relations.users.insert user_id: 1, name: 'Piotr'
142
+ # rom.relations.addresses.insert user_id: 1, street: 'Street 1', zipcode: '123', city: 'Kraków'
143
+ #
144
+ # rom.relations.users.with_address.to_a
145
+ # => [{:user_id=>1, :name=>"Piotr", :address=>{:street=>"Street 1", :zipcode=>"123", :city=>"Kraków"}}]
146
+ #
147
+ # @api public
148
+ def wrap(*args)
149
+ ra_with_options(*args) { |relation, options|
150
+ Operation::Wrap.new(relation, options)
151
+ }
152
+ end
181
153
 
182
- options = args.last
154
+ private
183
155
 
184
- yield(relation, options)
185
- end
156
+ # @api private
157
+ def ra_with_options(*args)
158
+ relation =
159
+ if args.size > 1
160
+ args.first
161
+ else
162
+ self
163
+ end
186
164
 
187
- # @api private
188
- def method_missing(name, *args, &block)
189
- relation.public_send(name, *args, &block)
190
- end
165
+ options = args.last
191
166
 
167
+ yield(relation, options)
192
168
  end
193
169
 
194
170
  end
@@ -20,9 +20,7 @@ module ROM
20
20
  def each(&block)
21
21
  return to_enum unless block
22
22
 
23
- tuples = relation.to_a
24
-
25
- result = tuples.each_with_object({}) do |tuple, grouped|
23
+ result = relation.each_with_object({}) do |tuple, grouped|
26
24
  left = tuple.reject { |k,_| attribute_names.include?(k) }
27
25
  right = tuple.reject { |k,_| !attribute_names.include?(k) }
28
26
 
data/lib/rom/reader.rb CHANGED
@@ -36,7 +36,7 @@ module ROM
36
36
  #
37
37
  # @api public
38
38
  def each
39
- relation.each { |tuple| yield(mapper.load(tuple)) }
39
+ mapper.process(relation) { |tuple| yield(tuple) }
40
40
  end
41
41
 
42
42
  # @api private
data/lib/rom/relation.rb CHANGED
@@ -1,5 +1,3 @@
1
- require 'rom/ra'
2
-
3
1
  module ROM
4
2
 
5
3
  # Base relation class
@@ -22,7 +20,6 @@ module ROM
22
20
  class Relation
23
21
  include Charlatan.new(:dataset)
24
22
  include Equalizer.new(:header, :dataset)
25
- include RA
26
23
 
27
24
  # @api private
28
25
  attr_reader :header
@@ -17,7 +17,7 @@ module ROM
17
17
  end
18
18
 
19
19
  def [](name)
20
- elements[name]
20
+ elements.fetch(name)
21
21
  end
22
22
 
23
23
  def respond_to_missing?(name, include_private = false)
@@ -27,7 +27,11 @@ module ROM
27
27
  private
28
28
 
29
29
  def method_missing(name, *args)
30
- self[name]
30
+ if elements.key?(name)
31
+ self[name]
32
+ else
33
+ super
34
+ end
31
35
  end
32
36
 
33
37
  end
@@ -0,0 +1,77 @@
1
+ module ROM
2
+
3
+ class Transformer
4
+ attr_reader :operations
5
+
6
+ class Operation
7
+ attr_reader :attribute, :key, :names
8
+
9
+ def initialize(attribute)
10
+ @attribute = attribute
11
+ @key = attribute.key
12
+ @names = attribute.header.map(&:key)
13
+ end
14
+ end
15
+
16
+ class Wrap < Operation
17
+ def call(tuples)
18
+ keys = nil
19
+
20
+ tuples.map { |tuple|
21
+ keys ||= tuple.keys - names
22
+
23
+ root = Hash[keys.zip(tuple.values_at(*keys))]
24
+ child = Hash[names.zip(tuple.values_at(*names))]
25
+
26
+ root.merge(key => child)
27
+ }
28
+ end
29
+ end
30
+
31
+ class Group < Operation
32
+ def call(tuples)
33
+ keys = nil
34
+
35
+ tuples.
36
+ group_by { |tuple|
37
+ keys ||= tuple.keys - names
38
+ Hash[keys.zip(tuple.values_at(*keys))]
39
+ }.map { |root, children|
40
+ root.merge(
41
+ key => children.map { |child| Hash[names.zip(child.values_at(*names))] }
42
+ )
43
+ }
44
+ end
45
+ end
46
+
47
+ def self.build(header)
48
+ operations = header.map do |attribute|
49
+ type = attribute.type
50
+
51
+ if type == Hash
52
+ Wrap.new(attribute)
53
+ elsif type == Array
54
+ Group.new(attribute)
55
+ end
56
+ end.compact
57
+
58
+ sorted_ops =
59
+ operations.select { |op| Group === op } +
60
+ operations.select { |op| Wrap === op }
61
+
62
+ new(sorted_ops.flatten)
63
+ end
64
+
65
+ def initialize(operations)
66
+ @operations = operations
67
+ end
68
+
69
+ def call(input)
70
+ output = input
71
+ operations.each { |op| output = op.call(output) }
72
+ output
73
+ end
74
+
75
+ end
76
+
77
+ end
data/lib/rom/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module ROM
2
- VERSION = '0.4.1'.freeze
2
+ VERSION = '0.4.2'.freeze
3
3
  end
@@ -99,7 +99,6 @@ describe 'Mapper definition DSL' do
99
99
  it 'builds a new model' do
100
100
  expect(mapper.model).to be(UserWithoutName)
101
101
  end
102
-
103
102
  end
104
103
 
105
104
  describe 'grouped relation mapper' do
@@ -107,8 +106,10 @@ describe 'Mapper definition DSL' do
107
106
  setup.relation(:tasks)
108
107
 
109
108
  setup.relation(:users) do
109
+ include ROM::RA
110
+
110
111
  def with_tasks
111
- in_memory { group(join(tasks), tasks: [:title, :priority]) }
112
+ join(tasks)
112
113
  end
113
114
  end
114
115
 
@@ -179,13 +180,48 @@ describe 'Mapper definition DSL' do
179
180
  )
180
181
  end
181
182
 
183
+ it 'allows defining grouped attributes mapped to a model via block' do
184
+ setup.mappers do
185
+ define(:with_tasks, parent: :users) do
186
+ model name: 'UserWithTasks'
187
+
188
+ attribute :name
189
+ attribute :email
190
+
191
+ group :tasks do
192
+ model name: 'Task'
193
+
194
+ attribute :title
195
+ attribute :priority
196
+ end
197
+ end
198
+ end
199
+
200
+ rom = setup.finalize
201
+
202
+ UserWithTasks.send(:include, Equalizer.new(:name, :email, :tasks))
203
+ Task.send(:include, Equalizer.new(:title, :priority))
204
+
205
+ jane = rom.read(:users).with_tasks.to_a.last
206
+
207
+ expect(jane).to eql(
208
+ UserWithTasks.new(
209
+ name: 'Jane',
210
+ email: 'jane@doe.org',
211
+ tasks: [Task.new(title: 'be cool', priority: 2)]
212
+ )
213
+ )
214
+ end
215
+
182
216
  end
183
217
 
184
218
  describe 'wrapped relation mapper' do
185
219
  before do
186
220
  setup.relation(:tasks) do
221
+ include ROM::RA
222
+
187
223
  def with_user
188
- in_memory { wrap(join(users), user: [:email]) }
224
+ join(users)
189
225
  end
190
226
  end
191
227
 
@@ -257,6 +293,37 @@ describe 'Mapper definition DSL' do
257
293
  )
258
294
  end
259
295
 
296
+ it 'allows defining wrapped attributes mapped to a model' do
297
+ setup.mappers do
298
+ define(:with_user, parent: :tasks) do
299
+ model name: 'TaskWithUser'
300
+
301
+ attribute :title
302
+ attribute :priority
303
+
304
+ wrap :user do
305
+ model name: 'User'
306
+ attribute :email
307
+ end
308
+ end
309
+ end
310
+
311
+ rom = setup.finalize
312
+
313
+ TaskWithUser.send(:include, Equalizer.new(:title, :priority, :user))
314
+ User.send(:include, Equalizer.new(:email))
315
+
316
+ jane = rom.read(:tasks).with_user.to_a.last
317
+
318
+ expect(jane).to eql(
319
+ TaskWithUser.new(
320
+ title: 'be cool',
321
+ priority: 2,
322
+ user: User.new(email: 'jane@doe.org')
323
+ )
324
+ )
325
+ end
326
+
260
327
  end
261
328
 
262
329
  end
@@ -23,16 +23,14 @@ describe 'Mappers / Renaming attributes' do
23
23
  setup.relation(:addresses)
24
24
 
25
25
  setup.relation(:users) do
26
+ include ROM::RA
27
+
26
28
  def with_address
27
- in_memory {
28
- wrap(join(users, addresses), address: [:address_id, :address_street])
29
- }
29
+ join(addresses)
30
30
  end
31
31
 
32
32
  def with_addresses
33
- in_memory {
34
- group(join(users, addresses), addresses: [:address_id, :address_street])
35
- }
33
+ join(addresses)
36
34
  end
37
35
  end
38
36
  end
@@ -5,11 +5,10 @@ describe 'Group operation' do
5
5
 
6
6
  specify 'defining a grouped relation' do
7
7
  setup.relation(:users) do
8
+ include ROM::RA
8
9
 
9
10
  def with_tasks
10
- in_memory {
11
- group(join(tasks), tasks: [:title, :priority])
12
- }
11
+ group(join(tasks), tasks: [:title, :priority])
13
12
  end
14
13
 
15
14
  def by_name(name)
@@ -5,8 +5,10 @@ describe 'Join operation' do
5
5
 
6
6
  specify 'defining a joined one-to-many relation' do
7
7
  setup.relation(:users) do
8
+ include ROM::RA
9
+
8
10
  def with_tasks
9
- in_memory { join(tasks) }
11
+ join(tasks)
10
12
  end
11
13
  end
12
14
 
@@ -27,8 +29,10 @@ describe 'Join operation' do
27
29
  setup.relation(:users)
28
30
 
29
31
  setup.relation(:tasks) do
32
+ include ROM::RA
33
+
30
34
  def with_user
31
- in_memory { join(users) }
35
+ join(users)
32
36
  end
33
37
  end
34
38
 
@@ -5,8 +5,10 @@ describe 'Wrap operation' do
5
5
 
6
6
  specify 'defining a wrapped relation' do
7
7
  setup.relation(:users) do
8
+ include ROM::RA
9
+
8
10
  def with_task
9
- in_memory { wrap(join(tasks), task: [:title, :priority]) }
11
+ wrap(join(tasks), task: [:title, :priority])
10
12
  end
11
13
  end
12
14
 
@@ -32,8 +32,10 @@ describe 'Reading relations' do
32
32
 
33
33
  it 'maps grouped relations' do
34
34
  setup.relation(:users) do
35
+ include ROM::RA
36
+
35
37
  def with_tasks
36
- in_memory { group(join(tasks), tasks: [:title, :priority]) }
38
+ join(tasks)
37
39
  end
38
40
 
39
41
  def sorted
@@ -78,8 +80,10 @@ describe 'Reading relations' do
78
80
 
79
81
  it 'maps wrapped relations' do
80
82
  setup.relation(:users) do
83
+ include ROM::RA
84
+
81
85
  def with_task
82
- in_memory { wrap(join(tasks), task: [:title, :priority]) }
86
+ join(tasks)
83
87
  end
84
88
 
85
89
  def sorted
@@ -15,8 +15,10 @@ describe 'Relation registration DSL' do
15
15
  end
16
16
 
17
17
  setup.relation(:users) do
18
+ include ROM::RA
19
+
18
20
  def with_tasks
19
- in_memory { join(tasks) }
21
+ join(tasks)
20
22
  end
21
23
  end
22
24
 
@@ -27,7 +27,7 @@ describe 'Defining schema' do
27
27
 
28
28
  describe '.schema' do
29
29
  it 'returns an empty schema if it was not defined' do
30
- expect(schema.users).to be_nil
30
+ expect { schema.users }.to raise_error(NoMethodError)
31
31
  end
32
32
 
33
33
  context 'with an adapter that supports header injection' do
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe ROM::ModelBuilder do
4
+ describe '#call' do
5
+ it 'builds a class with a constructor accepting attributes' do
6
+ builder = ROM::ModelBuilder::PORO.new
7
+
8
+ klass = builder.call(name: :user_name)
9
+
10
+ object = klass.new(name: 'Jane')
11
+
12
+ expect(object.name).to eql('Jane')
13
+
14
+ expect { object.name = 'Jane' }.to raise_error(NoMethodError)
15
+
16
+ klass = builder.call(name: :user_name, email: :user_email)
17
+
18
+ object = klass.new(name: 'Jane', email: 'jane@doe.org')
19
+
20
+ expect(object.name).to eql('Jane')
21
+ expect(object.email).to eql('jane@doe.org')
22
+ end
23
+
24
+ it 'defines a constant for the model when :name option is present' do
25
+ builder = ROM::ModelBuilder::PORO.new(name: 'User')
26
+
27
+ builder.call(name: :user_name, email: :user_email)
28
+
29
+ expect(Object.const_defined?(:User)).to be(true)
30
+ end
31
+
32
+ it 'defines a constant within a namespace for the model when :name option is present' do
33
+ module MyApp; module Entities; end; end
34
+
35
+ builder = ROM::ModelBuilder::PORO.new(name: 'MyApp::Entities::User')
36
+
37
+ builder.call(name: :user_name, email: :user_email)
38
+
39
+ expect(MyApp::Entities.const_defined?(:User)).to be(true)
40
+ expect(Object.const_defined?(:User)).to be(false)
41
+ end
42
+ end
43
+ end
@@ -1,10 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe 'Group operation' do
4
- subject(:group) { opts = options; relation.in_memory { group(opts) } }
4
+ subject(:group) { relation.group(options) }
5
5
 
6
6
  let(:relation) do
7
- ROM::Relation.new(dataset, header)
7
+ ROM::Relation.new(dataset, header).extend(ROM::RA)
8
8
  end
9
9
 
10
10
  let(:header) do
@@ -1,14 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe 'Wrap operation' do
4
- subject(:wrap) { relation.in_memory { wrap(address: [:street, :zipcode, :city]) } }
4
+ subject(:wrap) { relation.wrap(address: [:street, :zipcode, :city]) }
5
5
 
6
6
  let(:relation) do
7
7
  ROM::Relation.new([{ name: 'Jane',
8
8
  email: 'jane@doe.org',
9
9
  street: 'Street 1',
10
10
  zipcode: '1234',
11
- city: 'Cracow' }], header)
11
+ city: 'Cracow' }], header).extend(ROM::RA)
12
12
  end
13
13
 
14
14
  let(:header) do
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe ROM::Transformer do
4
+ subject(:transformer) { ROM::Transformer.build(header) }
5
+
6
+ let(:header) do
7
+ ROM::Header.coerce([
8
+ [:name],
9
+ [:address, type: Hash, header: [[:street], [:zipcode]]],
10
+ [:tasks, type: Array, header: [[:title], [:priority]]]
11
+ ])
12
+ end
13
+
14
+ let(:relation) do
15
+ [{ name: 'Jane',
16
+ street: 'Street 1',
17
+ zipcode: '123',
18
+ title: 'Sing a song',
19
+ priority: 'high' },
20
+ { name: 'Jane',
21
+ street: 'Street 1',
22
+ zipcode: '123',
23
+ title: 'Relax',
24
+ priority: 'very-high' }]
25
+ end
26
+
27
+ it 'transforms a tuple' do
28
+ expect(transformer.call(relation)).to eql([
29
+ name: 'Jane',
30
+ address: { street: 'Street 1', zipcode: '123' },
31
+ tasks: [
32
+ { title: 'Sing a song', priority: 'high' },
33
+ { title: 'Relax', priority: 'very-high' }
34
+ ]
35
+ ])
36
+ end
37
+
38
+ it 'skip transforming if tuple is empty' do
39
+ expect(transformer.call([])).to eql([])
40
+ end
41
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rom
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-12-15 00:00:00.000000000 Z
12
+ date: 2014-12-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: addressable
@@ -198,6 +198,7 @@ files:
198
198
  - lib/rom/relation_builder.rb
199
199
  - lib/rom/repository.rb
200
200
  - lib/rom/support/registry.rb
201
+ - lib/rom/transformer.rb
201
202
  - lib/rom/version.rb
202
203
  - rom.gemspec
203
204
  - spec/integration/adapters/extending_relations_spec.rb
@@ -224,10 +225,12 @@ files:
224
225
  - spec/unit/rom/env_spec.rb
225
226
  - spec/unit/rom/header_spec.rb
226
227
  - spec/unit/rom/mapper_spec.rb
228
+ - spec/unit/rom/model_builder_spec.rb
227
229
  - spec/unit/rom/ra/operation/group_spec.rb
228
230
  - spec/unit/rom/ra/operation/wrap_spec.rb
229
231
  - spec/unit/rom/relation_spec.rb
230
232
  - spec/unit/rom/repository_spec.rb
233
+ - spec/unit/rom/transformer_spec.rb
231
234
  homepage: http://rom-rb.org
232
235
  licenses:
233
236
  - MIT