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 +4 -4
- data/CHANGELOG.md +17 -0
- data/Gemfile +2 -2
- data/lib/rom.rb +3 -0
- data/lib/rom/boot/base_relation_dsl.rb +1 -0
- data/lib/rom/header.rb +2 -2
- data/lib/rom/mapper.rb +29 -15
- data/lib/rom/mapper_builder.rb +16 -2
- data/lib/rom/model_builder.rb +21 -11
- data/lib/rom/ra.rb +146 -170
- data/lib/rom/ra/operation/group.rb +1 -3
- data/lib/rom/reader.rb +1 -1
- data/lib/rom/relation.rb +0 -3
- data/lib/rom/support/registry.rb +6 -2
- data/lib/rom/transformer.rb +77 -0
- data/lib/rom/version.rb +1 -1
- data/spec/integration/mappers/definition_dsl_spec.rb +70 -3
- data/spec/integration/mappers/renaming_attributes_spec.rb +4 -6
- data/spec/integration/ra/group_spec.rb +2 -3
- data/spec/integration/ra/join_spec.rb +6 -2
- data/spec/integration/ra/wrap_spec.rb +3 -1
- data/spec/integration/relations/reading_spec.rb +6 -2
- data/spec/integration/relations/registry_dsl_spec.rb +3 -1
- data/spec/integration/schema_spec.rb +1 -1
- data/spec/unit/rom/model_builder_spec.rb +43 -0
- data/spec/unit/rom/ra/operation/group_spec.rb +2 -2
- data/spec/unit/rom/ra/operation/wrap_spec.rb +2 -2
- data/spec/unit/rom/transformer_spec.rb +41 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85a6e3c77b6e9a8d724fdd73056c4bdde3730beb
|
4
|
+
data.tar.gz: 6f964fb1181920ab5222f66d3482cf9ebcddf810
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/rom/header.rb
CHANGED
@@ -24,7 +24,7 @@ module ROM
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def mapping
|
27
|
-
|
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
|
16
|
-
|
15
|
+
def load(tuple)
|
16
|
+
super(Hash[call(tuple)])
|
17
17
|
end
|
18
18
|
|
19
|
-
def call(tuple
|
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
|
-
|
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
|
29
|
-
|
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
|
data/lib/rom/mapper_builder.rb
CHANGED
@@ -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]
|
data/lib/rom/model_builder.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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(:
|
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
|
-
|
47
|
-
|
48
|
-
|
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
|
-
#
|
12
|
+
# Join two relations in-memory using natural-join
|
13
13
|
#
|
14
|
-
#
|
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
|
18
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
154
|
+
private
|
183
155
|
|
184
|
-
|
185
|
-
|
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
|
-
|
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
|
-
|
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
data/lib/rom/relation.rb
CHANGED
data/lib/rom/support/registry.rb
CHANGED
@@ -17,7 +17,7 @@ module ROM
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def [](name)
|
20
|
-
elements
|
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
|
-
|
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
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
86
|
+
join(tasks)
|
83
87
|
end
|
84
88
|
|
85
89
|
def sorted
|
@@ -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
|
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) {
|
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.
|
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.
|
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-
|
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
|