rom 0.4.1 → 0.4.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: 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