rom 3.0.0.beta3 → 3.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -7
- data/.yardopts +2 -0
- data/Gemfile +2 -0
- data/Rakefile +1 -1
- data/lib/rom/command.rb +1 -1
- data/lib/rom/commands/class_interface.rb +113 -0
- data/lib/rom/configuration.rb +2 -0
- data/lib/rom/relation/class_interface.rb +37 -9
- data/lib/rom/relation.rb +1 -1
- data/lib/rom/schema/attribute.rb +367 -0
- data/lib/rom/schema.rb +81 -15
- data/lib/rom/version.rb +1 -1
- data/rom.gemspec +2 -2
- data/spec/unit/rom/commands/pre_and_post_processors_spec.rb +25 -16
- data/spec/unit/rom/relation/view_spec.rb +11 -1
- data/spec/unit/rom/schema/type_spec.rb +8 -8
- metadata +14 -7
- data/lib/rom/schema/type.rb +0 -129
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b8faaf70d24fe2d5212f777e8023dfc4b6a2e34
|
4
|
+
data.tar.gz: 4fe312831401a4a99c9837ef1948af386b558762
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e46223303dd3c701780526df9184c5c2c03cec5aee5a5cc3d3ba64ebd6e962619c2b04af8c309db4410d19bbf999e41c2ef813a667bccd5d5c8c468d69ff9fef
|
7
|
+
data.tar.gz: 09b1af7a72438045fc804872a8cfa63ac8d9c2c3add1123a71f6e6117d7f61c65e005c241e35757a25b3d2ec19b877f97516ba6bae74b973083e5f013d2bb21f
|
data/.travis.yml
CHANGED
@@ -5,20 +5,17 @@ cache: bundler
|
|
5
5
|
bundler_args: --without sql benchmarks console tools
|
6
6
|
script: "bundle exec rake ci"
|
7
7
|
after_success:
|
8
|
-
- '[
|
8
|
+
- '[ "${TRAVIS_JOB_NUMBER#*.}" = "1" ] && [ "$TRAVIS_BRANCH" = "master" ] && bundle exec codeclimate-test-reporter'
|
9
9
|
rvm:
|
10
|
-
- 2.2.6
|
11
|
-
- 2.3.3
|
12
10
|
- 2.4.0
|
11
|
+
- 2.3
|
12
|
+
- 2.2
|
13
13
|
- rbx-3
|
14
|
-
- jruby
|
14
|
+
- jruby
|
15
15
|
env:
|
16
16
|
global:
|
17
17
|
- JRUBY_OPTS='--dev -J-Xmx1024M'
|
18
18
|
- COVERAGE='true'
|
19
|
-
matrix:
|
20
|
-
allow_failures:
|
21
|
-
- rvm: rbx-3
|
22
19
|
notifications:
|
23
20
|
webhooks:
|
24
21
|
urls:
|
data/.yardopts
ADDED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
data/lib/rom/command.rb
CHANGED
@@ -31,7 +31,7 @@ module ROM
|
|
31
31
|
extend Dry::Core::ClassAttributes
|
32
32
|
extend ClassInterface
|
33
33
|
|
34
|
-
defines :adapter, :relation, :result, :input, :register_as, :restrictable
|
34
|
+
defines :adapter, :relation, :result, :input, :register_as, :restrictable
|
35
35
|
|
36
36
|
# @attr_reader [Relation] relation The command's relation
|
37
37
|
param :relation
|
@@ -7,6 +7,15 @@ module ROM
|
|
7
7
|
# @private
|
8
8
|
class Command
|
9
9
|
module ClassInterface
|
10
|
+
# This hook sets up default class state
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
def inherited(klass)
|
14
|
+
super
|
15
|
+
klass.instance_variable_set(:'@before', before ? before.dup : [])
|
16
|
+
klass.instance_variable_set(:'@after', before ? after.dup : [])
|
17
|
+
end
|
18
|
+
|
10
19
|
# Return adapter specific sub-class based on the adapter identifier
|
11
20
|
#
|
12
21
|
# This is a syntax sugar to make things consistent
|
@@ -106,6 +115,110 @@ module ROM
|
|
106
115
|
include(relation_methods_mod(relation.class))
|
107
116
|
end
|
108
117
|
|
118
|
+
# Set before-execute hooks
|
119
|
+
#
|
120
|
+
# @overload before(hook)
|
121
|
+
# Set an before hook as a method name
|
122
|
+
#
|
123
|
+
# @example
|
124
|
+
# class CreateUser < ROM::Commands::Create[:sql]
|
125
|
+
# relation :users
|
126
|
+
# register_as :create
|
127
|
+
#
|
128
|
+
# before :my_hook
|
129
|
+
#
|
130
|
+
# def my_hook(tuple, *)
|
131
|
+
# puts "hook called#
|
132
|
+
# end
|
133
|
+
# end
|
134
|
+
#
|
135
|
+
# @overload before(hook_opts)
|
136
|
+
# Set an before hook as a method name with arguments
|
137
|
+
#
|
138
|
+
# @example
|
139
|
+
# class CreateUser < ROM::Commands::Create[:sql]
|
140
|
+
# relation :users
|
141
|
+
# register_as :create
|
142
|
+
#
|
143
|
+
# before my_hook: { arg1: 1, arg1: 2 }
|
144
|
+
#
|
145
|
+
# def my_hook(tuple, arg1:, arg2:)
|
146
|
+
# puts "hook called with args: #{arg1} and #{arg2}"
|
147
|
+
# end
|
148
|
+
# end
|
149
|
+
#
|
150
|
+
# @param [Hash<Symbol=>Hash>] hook Options with method name and pre-set args
|
151
|
+
#
|
152
|
+
# @return [Array<Hash, Symbol>] A list of all configured before hooks
|
153
|
+
#
|
154
|
+
# @api public
|
155
|
+
def before(*hooks)
|
156
|
+
if hooks.size > 0
|
157
|
+
set_hooks(:before, hooks)
|
158
|
+
else
|
159
|
+
@before
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Set after-execute hooks
|
164
|
+
#
|
165
|
+
# @overload after(hook)
|
166
|
+
# Set an after hook as a method name
|
167
|
+
#
|
168
|
+
# @example
|
169
|
+
# class CreateUser < ROM::Commands::Create[:sql]
|
170
|
+
# relation :users
|
171
|
+
# register_as :create
|
172
|
+
#
|
173
|
+
# after :my_hook
|
174
|
+
#
|
175
|
+
# def my_hook(tuple, *)
|
176
|
+
# puts "hook called#
|
177
|
+
# end
|
178
|
+
# end
|
179
|
+
#
|
180
|
+
# @overload after(hook_opts)
|
181
|
+
# Set an after hook as a method name with arguments
|
182
|
+
#
|
183
|
+
# @example
|
184
|
+
# class CreateUser < ROM::Commands::Create[:sql]
|
185
|
+
# relation :users
|
186
|
+
# register_as :create
|
187
|
+
#
|
188
|
+
# after my_hook: { arg1: 1, arg1: 2 }
|
189
|
+
#
|
190
|
+
# def my_hook(tuple, arg1:, arg2:)
|
191
|
+
# puts "hook called with args: #{arg1} and #{arg2}"
|
192
|
+
# end
|
193
|
+
# end
|
194
|
+
#
|
195
|
+
# @param [Hash<Symbol=>Hash>] hook Options with method name and pre-set args
|
196
|
+
#
|
197
|
+
# @return [Array<Hash, Symbol>] A list of all configured after hooks
|
198
|
+
#
|
199
|
+
# @api public
|
200
|
+
def after(*hooks)
|
201
|
+
if hooks.size > 0
|
202
|
+
set_hooks(:after, hooks)
|
203
|
+
else
|
204
|
+
@after
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Set new or more hooks
|
209
|
+
#
|
210
|
+
# @api private
|
211
|
+
def set_hooks(type, hooks)
|
212
|
+
ivar = :"@#{type}"
|
213
|
+
value = instance_variable_get(ivar)
|
214
|
+
|
215
|
+
if value.empty?
|
216
|
+
instance_variable_set(ivar, hooks)
|
217
|
+
else
|
218
|
+
value.concat(hooks)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
109
222
|
# Return default name of the command class based on its name
|
110
223
|
#
|
111
224
|
# During setup phase this is used by defalut as `register_as` option
|
data/lib/rom/configuration.rb
CHANGED
@@ -12,6 +12,7 @@ require 'rom/schema'
|
|
12
12
|
|
13
13
|
module ROM
|
14
14
|
class Relation
|
15
|
+
# @api public
|
15
16
|
module ClassInterface
|
16
17
|
include Dry::Core::Constants
|
17
18
|
|
@@ -185,20 +186,45 @@ module ROM
|
|
185
186
|
end
|
186
187
|
end
|
187
188
|
|
188
|
-
# Define a relation view with a specific
|
189
|
+
# Define a relation view with a specific schema
|
189
190
|
#
|
190
|
-
#
|
191
|
+
# Explicit relation views allow relation composition with auto-mapping
|
192
|
+
# in repositories. It's useful for cases like defining custom views
|
193
|
+
# for associations where relations (even from different databases) can
|
194
|
+
# be composed together and automatically mapped in memory to structs.
|
191
195
|
#
|
192
|
-
# @
|
193
|
-
#
|
194
|
-
#
|
195
|
-
#
|
196
|
+
# @overload view(name, schema, &block)
|
197
|
+
# @example View with the canonical schema
|
198
|
+
# class Users < ROM::Relation[:sql]
|
199
|
+
# view(:listing, schema) do
|
200
|
+
# order(:name)
|
201
|
+
# end
|
196
202
|
# end
|
197
203
|
#
|
198
|
-
#
|
199
|
-
#
|
204
|
+
# @example View with a projected schema
|
205
|
+
# class Users < ROM::Relation[:sql]
|
206
|
+
# view(:listing, schema.project(:id, :name)) do
|
207
|
+
# order(:name)
|
208
|
+
# end
|
209
|
+
# end
|
210
|
+
#
|
211
|
+
# @overload view(name, &block)
|
212
|
+
# @example View with the canonical schema and arguments
|
213
|
+
# class Users < ROM::Relation[:sql]
|
214
|
+
# view(:by_name) do |name|
|
215
|
+
# where(name: name)
|
216
|
+
# end
|
200
217
|
# end
|
201
|
-
#
|
218
|
+
#
|
219
|
+
# @example View with projected schema and arguments
|
220
|
+
# class Users < ROM::Relation[:sql]
|
221
|
+
# view(:by_name) do
|
222
|
+
# schema { project(:id, :name) }
|
223
|
+
# relation { |name| where(name: name) }
|
224
|
+
# end
|
225
|
+
# end
|
226
|
+
#
|
227
|
+
# @return [Symbol] view method name
|
202
228
|
#
|
203
229
|
# @api public
|
204
230
|
def view(*args, &block)
|
@@ -233,6 +259,8 @@ module ROM
|
|
233
259
|
schemas[name].(instance_exec(&relation_block))
|
234
260
|
end
|
235
261
|
end
|
262
|
+
|
263
|
+
name
|
236
264
|
end
|
237
265
|
|
238
266
|
# Dynamically define a method that will forward to the dataset and wrap
|
data/lib/rom/relation.rb
CHANGED
@@ -0,0 +1,367 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'dry/equalizer'
|
3
|
+
require 'dry/types/decorator'
|
4
|
+
|
5
|
+
module ROM
|
6
|
+
class Schema
|
7
|
+
# Schema attributes provide meta information about types and an API
|
8
|
+
# for additional operations. This class can be extended by adapters to provide
|
9
|
+
# database-specific features. In example rom-sql provides SQL::Attribute
|
10
|
+
# with more features like creating SQL expressions for queries.
|
11
|
+
#
|
12
|
+
# Schema attributes are accessible through canonical relation schemas and
|
13
|
+
# instance-level schemas.
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
class Attribute
|
17
|
+
include Dry::Equalizer(:type)
|
18
|
+
|
19
|
+
# !@attribute [r] type
|
20
|
+
# @return [Dry::Types::Definition, Dry::Types::Sum, Dry::Types::Constrained]
|
21
|
+
attr_reader :type
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
def initialize(type)
|
25
|
+
@type = type
|
26
|
+
end
|
27
|
+
|
28
|
+
# @api private
|
29
|
+
def [](input)
|
30
|
+
type[input]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return true if this attribute type is a primary key
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# class Users < ROM::Relation[:memory]
|
37
|
+
# schema do
|
38
|
+
# attribute :id, Types::Int
|
39
|
+
# attribute :name, Types::String
|
40
|
+
#
|
41
|
+
# primary_key :id
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# Users.schema[:id].primary_key?
|
46
|
+
# # => true
|
47
|
+
#
|
48
|
+
# Users.schema[:name].primary_key?
|
49
|
+
# # => false
|
50
|
+
#
|
51
|
+
# @return [TrueClass,FalseClass]
|
52
|
+
#
|
53
|
+
# @api public
|
54
|
+
def primary_key?
|
55
|
+
meta[:primary_key].equal?(true)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Return true if this attribute type is a foreign key
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# class Tasks < ROM::Relation[:memory]
|
62
|
+
# schema do
|
63
|
+
# attribute :id, Types::Int
|
64
|
+
# attribute :user_id, Types.ForeignKey(:users)
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# Users.schema[:user_id].foreign_key?
|
69
|
+
# # => true
|
70
|
+
#
|
71
|
+
# Users.schema[:id].foreign_key?
|
72
|
+
# # => false
|
73
|
+
#
|
74
|
+
# @return [TrueClass,FalseClass]
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
def foreign_key?
|
78
|
+
meta[:foreign_key].equal?(true)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Return true if this attribute type is a foreign key
|
82
|
+
#
|
83
|
+
# @example
|
84
|
+
# class Tasks < ROM::Relation[:memory]
|
85
|
+
# schema do
|
86
|
+
# attribute :user_id, Types::Int.meta(alias: :id)
|
87
|
+
# attribute :name, Types::String
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# Users.schema[:user_id].aliased?
|
92
|
+
# # => true
|
93
|
+
#
|
94
|
+
# Users.schema[:name].aliased?
|
95
|
+
# # => false
|
96
|
+
#
|
97
|
+
# @return [TrueClass,FalseClass]
|
98
|
+
#
|
99
|
+
# @api public
|
100
|
+
def aliased?
|
101
|
+
!meta[:alias].nil?
|
102
|
+
end
|
103
|
+
|
104
|
+
# Return source relation of this attribute type
|
105
|
+
#
|
106
|
+
# @example
|
107
|
+
# class Tasks < ROM::Relation[:memory]
|
108
|
+
# schema do
|
109
|
+
# attribute :id, Types::Int
|
110
|
+
# attribute :user_id, Types.ForeignKey(:users)
|
111
|
+
# end
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# Users.schema[:id].source
|
115
|
+
# # => :tasks
|
116
|
+
#
|
117
|
+
# Users.schema[:user_id].source
|
118
|
+
# # => :tasks
|
119
|
+
#
|
120
|
+
# @return [Symbol, Relation::Name]
|
121
|
+
#
|
122
|
+
# @api public
|
123
|
+
def source
|
124
|
+
meta[:source]
|
125
|
+
end
|
126
|
+
|
127
|
+
# Return target relation of this attribute type
|
128
|
+
#
|
129
|
+
# @example
|
130
|
+
# class Tasks < ROM::Relation[:memory]
|
131
|
+
# schema do
|
132
|
+
# attribute :id, Types::Int
|
133
|
+
# attribute :user_id, Types.ForeignKey(:users)
|
134
|
+
# end
|
135
|
+
# end
|
136
|
+
#
|
137
|
+
# Users.schema[:id].target
|
138
|
+
# # => nil
|
139
|
+
#
|
140
|
+
# Users.schema[:user_id].target
|
141
|
+
# # => :users
|
142
|
+
#
|
143
|
+
# @return [NilClass, Symbol, Relation::Name]
|
144
|
+
#
|
145
|
+
# @api public
|
146
|
+
def target
|
147
|
+
meta[:target]
|
148
|
+
end
|
149
|
+
|
150
|
+
# Return the canonical name of this attribute name
|
151
|
+
#
|
152
|
+
# This *always* returns the name that is used in the datastore, even when
|
153
|
+
# an attribute is aliased
|
154
|
+
#
|
155
|
+
# @example
|
156
|
+
# class Tasks < ROM::Relation[:memory]
|
157
|
+
# schema do
|
158
|
+
# attribute :user_id, Types::Int.meta(alias: :id)
|
159
|
+
# attribute :name, Types::String
|
160
|
+
# end
|
161
|
+
# end
|
162
|
+
#
|
163
|
+
# Users.schema[:id].name
|
164
|
+
# # => :id
|
165
|
+
#
|
166
|
+
# Users.schema[:user_id].name
|
167
|
+
# # => :user_id
|
168
|
+
#
|
169
|
+
# @return [Symbol]
|
170
|
+
#
|
171
|
+
# @api public
|
172
|
+
def name
|
173
|
+
meta[:name]
|
174
|
+
end
|
175
|
+
|
176
|
+
# Return attribute's alias
|
177
|
+
#
|
178
|
+
# @example
|
179
|
+
# class Tasks < ROM::Relation[:memory]
|
180
|
+
# schema do
|
181
|
+
# attribute :user_id, Types::Int.meta(alias: :id)
|
182
|
+
# attribute :name, Types::String
|
183
|
+
# end
|
184
|
+
# end
|
185
|
+
#
|
186
|
+
# Users.schema[:user_id].alias
|
187
|
+
# # => :user_id
|
188
|
+
#
|
189
|
+
# Users.schema[:name].alias
|
190
|
+
# # => nil
|
191
|
+
#
|
192
|
+
# @return [NilClass,Symbol]
|
193
|
+
#
|
194
|
+
# @api public
|
195
|
+
def alias
|
196
|
+
meta[:alias]
|
197
|
+
end
|
198
|
+
|
199
|
+
# Return new attribute type with provided alias
|
200
|
+
#
|
201
|
+
# @example
|
202
|
+
# class Tasks < ROM::Relation[:memory]
|
203
|
+
# schema do
|
204
|
+
# attribute :user_id, Types::Int
|
205
|
+
# attribute :name, Types::String
|
206
|
+
# end
|
207
|
+
# end
|
208
|
+
#
|
209
|
+
# aliased_user_id = Users.schema[:user_id].aliased(:id)
|
210
|
+
#
|
211
|
+
# aliased_user_id.aliased?
|
212
|
+
# # => true
|
213
|
+
#
|
214
|
+
# aliased_user_id.name
|
215
|
+
# # => :user_id
|
216
|
+
#
|
217
|
+
# aliased_user_id.alias
|
218
|
+
# # => :id
|
219
|
+
#
|
220
|
+
# @param [Symbol] name The alias
|
221
|
+
#
|
222
|
+
# @return [Schema::Attribute]
|
223
|
+
#
|
224
|
+
# @api public
|
225
|
+
def aliased(name)
|
226
|
+
meta(alias: name)
|
227
|
+
end
|
228
|
+
alias_method :as, :aliased
|
229
|
+
|
230
|
+
# Return new attribute type with an alias using provided prefix
|
231
|
+
#
|
232
|
+
# @example
|
233
|
+
# class Users < ROM::Relation[:memory]
|
234
|
+
# schema do
|
235
|
+
# attribute :id, Types::Int
|
236
|
+
# attribute :name, Types::String
|
237
|
+
# end
|
238
|
+
# end
|
239
|
+
#
|
240
|
+
# prefixed_id = Users.schema[:id].prefixed
|
241
|
+
#
|
242
|
+
# prefixed_id.aliased?
|
243
|
+
# # => true
|
244
|
+
#
|
245
|
+
# prefixed_id.name
|
246
|
+
# # => :id
|
247
|
+
#
|
248
|
+
# prefixed_id.alias
|
249
|
+
# # => :users_id
|
250
|
+
#
|
251
|
+
# prefixed_id = Users.schema[:id].prefixed(:user)
|
252
|
+
#
|
253
|
+
# prefixed_id.alias
|
254
|
+
# # => :user_id
|
255
|
+
#
|
256
|
+
# @param [Symbol] prefix The prefix (defaults to source.dataset)
|
257
|
+
#
|
258
|
+
# @return [Schema::Attribute]
|
259
|
+
#
|
260
|
+
# @api public
|
261
|
+
def prefixed(prefix = source.dataset)
|
262
|
+
aliased(:"#{prefix}_#{name}")
|
263
|
+
end
|
264
|
+
|
265
|
+
# Return if the attribute type is from a wrapped relation
|
266
|
+
#
|
267
|
+
# Wrapped attributes are used when two schemas from different relations
|
268
|
+
# are merged together. This way we can identify them easily and handle
|
269
|
+
# correctly in places like auto-mapping.
|
270
|
+
#
|
271
|
+
# @api public
|
272
|
+
def wrapped?
|
273
|
+
meta[:wrapped].equal?(true)
|
274
|
+
end
|
275
|
+
|
276
|
+
# Return attribute type wrapped for the specified relation name
|
277
|
+
#
|
278
|
+
# @param [Symbol] name The name of the source relation (defaults to source.dataset)
|
279
|
+
#
|
280
|
+
# @return [Schema::Attribute]
|
281
|
+
#
|
282
|
+
# @api public
|
283
|
+
def wrapped(name = source.dataset)
|
284
|
+
self.class.new(prefixed(name).meta(wrapped: true))
|
285
|
+
end
|
286
|
+
|
287
|
+
# Return attribute type with additional meta information
|
288
|
+
#
|
289
|
+
# Return meta information hash if no opts are provided
|
290
|
+
#
|
291
|
+
# @param [Hash] opts The meta options
|
292
|
+
#
|
293
|
+
# @return [Schema::Attribute]
|
294
|
+
#
|
295
|
+
# @api public
|
296
|
+
def meta(opts = nil)
|
297
|
+
if opts
|
298
|
+
self.class.new(type.meta(opts))
|
299
|
+
else
|
300
|
+
type.meta
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# Return string representation of the attribute type
|
305
|
+
#
|
306
|
+
# @return [String]
|
307
|
+
#
|
308
|
+
# @api public
|
309
|
+
def inspect
|
310
|
+
%(#<#{self.class}[#{type.name}] #{meta.map { |k, v| "#{k}=#{v.inspect}" }.join(' ')}>)
|
311
|
+
end
|
312
|
+
alias_method :pretty_inspect, :inspect
|
313
|
+
|
314
|
+
# Check if the attribute type is equal to another
|
315
|
+
#
|
316
|
+
# @param [Dry::Type, Schema::Attribute]
|
317
|
+
#
|
318
|
+
# @return [TrueClass,FalseClass]
|
319
|
+
#
|
320
|
+
# @api public
|
321
|
+
def eql?(other)
|
322
|
+
other.is_a?(self.class) ? super : type.eql?(other)
|
323
|
+
end
|
324
|
+
|
325
|
+
# Return if this attribute type has additional attribute type for reading
|
326
|
+
# tuple values
|
327
|
+
#
|
328
|
+
# @return [TrueClass, FalseClass]
|
329
|
+
#
|
330
|
+
# @api private
|
331
|
+
def read?
|
332
|
+
! meta[:read].nil?
|
333
|
+
end
|
334
|
+
|
335
|
+
# Return read type or self
|
336
|
+
#
|
337
|
+
# @return [Schema::Attribute]
|
338
|
+
#
|
339
|
+
# @api private
|
340
|
+
def to_read_type
|
341
|
+
read? ? meta[:read] : type
|
342
|
+
end
|
343
|
+
|
344
|
+
# @api private
|
345
|
+
def respond_to_missing?(name, include_private = false)
|
346
|
+
type.respond_to?(name) || super
|
347
|
+
end
|
348
|
+
|
349
|
+
private
|
350
|
+
|
351
|
+
# @api private
|
352
|
+
def method_missing(meth, *args, &block)
|
353
|
+
if type.respond_to?(meth)
|
354
|
+
response = type.__send__(meth, *args, &block)
|
355
|
+
|
356
|
+
if response.is_a?(type.class)
|
357
|
+
self.class.new(type)
|
358
|
+
else
|
359
|
+
response
|
360
|
+
end
|
361
|
+
else
|
362
|
+
super
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
data/lib/rom/schema.rb
CHANGED
@@ -1,15 +1,35 @@
|
|
1
1
|
require 'dry-equalizer'
|
2
2
|
|
3
|
-
require 'rom/schema/
|
3
|
+
require 'rom/schema/attribute'
|
4
4
|
require 'rom/schema/dsl'
|
5
5
|
require 'rom/association_set'
|
6
6
|
|
7
7
|
module ROM
|
8
8
|
# Relation schema
|
9
9
|
#
|
10
|
+
# Schemas hold detailed information about relation tuples, including their
|
11
|
+
# primitive types (String, Integer, Hash, etc. or custom classes), as well as
|
12
|
+
# various meta information like primary/foreign key and literally any other
|
13
|
+
# information that a given database adapter may need.
|
14
|
+
#
|
15
|
+
# Adapters can extend this class and it can be used in adapter-specific relations.
|
16
|
+
# In example rom-sql extends schema with Association DSL and many additional
|
17
|
+
# SQL-specific APIs in schema types.
|
18
|
+
#
|
19
|
+
# Schemas are used for projecting canonical relations into other relations and
|
20
|
+
# every relation object maintains its schema. This means that we always have
|
21
|
+
# all information about relation tuples, even when a relation was projected and
|
22
|
+
# diverged from its canonical form.
|
23
|
+
#
|
24
|
+
# Furthermore schema attributes know their source relations, which makes it
|
25
|
+
# possible to merge schemas from multiple relations and maintain information
|
26
|
+
# about the source relations. In example when two relations are joined, their
|
27
|
+
# schemas are merged, and we know which attributes belong to which relation.
|
28
|
+
#
|
10
29
|
# @api public
|
11
30
|
class Schema
|
12
31
|
EMPTY_ASSOCIATION_SET = AssociationSet.new(EMPTY_HASH).freeze
|
32
|
+
|
13
33
|
DEFAULT_INFERRER = proc { [EMPTY_ARRAY, EMPTY_ARRAY].freeze }
|
14
34
|
|
15
35
|
MissingAttributesError = Class.new(StandardError) do
|
@@ -45,20 +65,29 @@ module ROM
|
|
45
65
|
|
46
66
|
alias_method :to_ary, :attributes
|
47
67
|
|
68
|
+
# Define a relation schema from plain rom types
|
69
|
+
#
|
70
|
+
# Resulting schema will decorate plain rom types with adapter-specific types
|
71
|
+
# By default `Schema::Attribute` will be used
|
72
|
+
#
|
73
|
+
# @param [Relation::Name, Symbol] name The schema name, typically ROM::Relation::Name
|
74
|
+
#
|
75
|
+
# @return [Schema]
|
76
|
+
#
|
48
77
|
# @api public
|
49
|
-
def self.define(name,
|
78
|
+
def self.define(name, attr_class: Attribute, attributes: EMPTY_ARRAY, associations: EMPTY_ASSOCIATION_SET, inferrer: DEFAULT_INFERRER)
|
50
79
|
new(
|
51
80
|
name,
|
52
|
-
attributes: attributes(attributes,
|
81
|
+
attributes: attributes(attributes, attr_class),
|
53
82
|
associations: associations,
|
54
83
|
inferrer: inferrer,
|
55
|
-
|
84
|
+
attr_class: attr_class
|
56
85
|
)
|
57
86
|
end
|
58
87
|
|
59
88
|
# @api private
|
60
|
-
def self.attributes(attributes,
|
61
|
-
attributes.map { |type|
|
89
|
+
def self.attributes(attributes, attr_class)
|
90
|
+
attributes.map { |type| attr_class.new(type) }
|
62
91
|
end
|
63
92
|
|
64
93
|
# @api private
|
@@ -90,18 +119,26 @@ module ROM
|
|
90
119
|
|
91
120
|
# Iterate over schema's attributes
|
92
121
|
#
|
93
|
-
# @yield [Schema::
|
122
|
+
# @yield [Schema::Attribute]
|
94
123
|
#
|
95
124
|
# @api public
|
96
125
|
def each(&block)
|
97
126
|
attributes.each(&block)
|
98
127
|
end
|
99
128
|
|
129
|
+
# Check if schema has any attributes
|
130
|
+
#
|
131
|
+
# @return [TrueClass, FalseClass]
|
132
|
+
#
|
100
133
|
# @api public
|
101
134
|
def empty?
|
102
135
|
attributes.size == 0
|
103
136
|
end
|
104
137
|
|
138
|
+
# Coerce schema into a <AttributeName=>Attribute> Hash
|
139
|
+
#
|
140
|
+
# @return [Hash]
|
141
|
+
#
|
105
142
|
# @api public
|
106
143
|
def to_h
|
107
144
|
each_with_object({}) { |attr, h| h[attr.name] = attr }
|
@@ -109,6 +146,11 @@ module ROM
|
|
109
146
|
|
110
147
|
# Return attribute
|
111
148
|
#
|
149
|
+
# @param [Symbol] key The attribute name
|
150
|
+
# @param [Symbol, Relation::Name] src The source relation (for merged schemas)
|
151
|
+
#
|
152
|
+
# @raise KeyError
|
153
|
+
#
|
112
154
|
# @api public
|
113
155
|
def [](key, src = name.to_sym)
|
114
156
|
attr =
|
@@ -127,7 +169,7 @@ module ROM
|
|
127
169
|
|
128
170
|
# Project a schema to include only specified attributes
|
129
171
|
#
|
130
|
-
# @param [*Array] names Attribute names
|
172
|
+
# @param [*Array<Symbol, Schema::Attribute>] names Attribute names
|
131
173
|
#
|
132
174
|
# @return [Schema]
|
133
175
|
#
|
@@ -174,6 +216,15 @@ module ROM
|
|
174
216
|
new(map { |attr| attr.prefixed(prefix) })
|
175
217
|
end
|
176
218
|
|
219
|
+
# Return new schema with all attributes marked as prefixed and wrapped
|
220
|
+
#
|
221
|
+
# This is useful when relations are joined and the right side should be marked
|
222
|
+
# as wrapped
|
223
|
+
#
|
224
|
+
# @param [Symbol] prefix The prefix used for aliasing wrapped attributes
|
225
|
+
#
|
226
|
+
# @return [Schema]
|
227
|
+
#
|
177
228
|
# @api public
|
178
229
|
def wrap(prefix = name.dataset)
|
179
230
|
new(map { |attr| attr.wrapped(prefix) })
|
@@ -181,7 +232,7 @@ module ROM
|
|
181
232
|
|
182
233
|
# Return FK attribute for a given relation name
|
183
234
|
#
|
184
|
-
# @return [
|
235
|
+
# @return [Schema::Attribute]
|
185
236
|
#
|
186
237
|
# @api public
|
187
238
|
def foreign_key(relation)
|
@@ -190,7 +241,7 @@ module ROM
|
|
190
241
|
|
191
242
|
# Return primary key attributes
|
192
243
|
#
|
193
|
-
# @return [Array<Schema::
|
244
|
+
# @return [Array<Schema::Attribute>]
|
194
245
|
#
|
195
246
|
# @api public
|
196
247
|
def primary_key
|
@@ -211,7 +262,9 @@ module ROM
|
|
211
262
|
|
212
263
|
# Append more attributes to the schema
|
213
264
|
#
|
214
|
-
#
|
265
|
+
# This returns a new schema instance
|
266
|
+
#
|
267
|
+
# @param [*Array<Schema::Attribute>]
|
215
268
|
#
|
216
269
|
# @return [Schema]
|
217
270
|
#
|
@@ -222,7 +275,7 @@ module ROM
|
|
222
275
|
|
223
276
|
# Return a new schema with uniq attributes
|
224
277
|
#
|
225
|
-
# @param [*Array<Schema::
|
278
|
+
# @param [*Array<Schema::Attribute>]
|
226
279
|
#
|
227
280
|
# @return [Schema]
|
228
281
|
#
|
@@ -260,7 +313,7 @@ module ROM
|
|
260
313
|
inferred, missing = inferrer.call(name, gateway)
|
261
314
|
|
262
315
|
attr_names = map(&:name)
|
263
|
-
inferred_attrs = self.class.attributes(inferred,
|
316
|
+
inferred_attrs = self.class.attributes(inferred, attr_class).
|
264
317
|
reject { |attr| attr_names.include?(attr.name) }
|
265
318
|
|
266
319
|
attributes.concat(inferred_attrs)
|
@@ -282,6 +335,12 @@ module ROM
|
|
282
335
|
freeze
|
283
336
|
end
|
284
337
|
|
338
|
+
# Return coercion function using attribute read types
|
339
|
+
#
|
340
|
+
# This is used for `output_schema` in relations
|
341
|
+
#
|
342
|
+
# @return [Dry::Types::Hash]
|
343
|
+
#
|
285
344
|
# @api private
|
286
345
|
def to_output_hash
|
287
346
|
Types::Coercible::Hash.schema(
|
@@ -289,6 +348,13 @@ module ROM
|
|
289
348
|
)
|
290
349
|
end
|
291
350
|
|
351
|
+
# Return coercion function using attribute types
|
352
|
+
#
|
353
|
+
# This is used for `input_schema` in relations, typically commands use it
|
354
|
+
# for processing input
|
355
|
+
#
|
356
|
+
# @return [Dry::Types::Hash]
|
357
|
+
#
|
292
358
|
# @api private
|
293
359
|
def to_input_hash
|
294
360
|
Types::Coercible::Hash.schema(
|
@@ -317,8 +383,8 @@ module ROM
|
|
317
383
|
end
|
318
384
|
|
319
385
|
# @api private
|
320
|
-
def
|
321
|
-
options.fetch(:
|
386
|
+
def attr_class
|
387
|
+
options.fetch(:attr_class)
|
322
388
|
end
|
323
389
|
|
324
390
|
# @api private
|
data/lib/rom/version.rb
CHANGED
data/rom.gemspec
CHANGED
@@ -17,10 +17,10 @@ Gem::Specification.new do |gem|
|
|
17
17
|
|
18
18
|
gem.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
|
19
19
|
gem.add_runtime_dependency 'dry-equalizer', '~> 0.2'
|
20
|
-
gem.add_runtime_dependency 'dry-types', '~> 0.
|
20
|
+
gem.add_runtime_dependency 'dry-types', '~> 0.9', '>= 0.9.4'
|
21
21
|
gem.add_runtime_dependency 'dry-core', '~> 0.2', '>= 0.2.3'
|
22
22
|
gem.add_runtime_dependency 'dry-initializer', '~> 0.10', '>= 0.10.2'
|
23
|
-
gem.add_runtime_dependency 'rom-mapper', '~> 0.5.0.
|
23
|
+
gem.add_runtime_dependency 'rom-mapper', '~> 0.5.0.rc'
|
24
24
|
|
25
25
|
gem.add_development_dependency 'rake', '~> 10.3'
|
26
26
|
gem.add_development_dependency 'rspec', '~> 3.5'
|
@@ -1,49 +1,58 @@
|
|
1
1
|
require 'rom/command'
|
2
|
+
require 'rom/memory'
|
2
3
|
|
3
|
-
RSpec.describe ROM::
|
4
|
+
RSpec.describe ROM::Commands::Create[:memory], 'before/after hooks' do
|
4
5
|
let(:relation) do
|
5
6
|
spy(:relation)
|
6
7
|
end
|
7
8
|
|
8
9
|
describe '#before' do
|
9
10
|
subject(:command) do
|
10
|
-
Class.new(ROM::
|
11
|
+
Class.new(ROM::Commands::Create[:memory]) do
|
11
12
|
before :init
|
13
|
+
before :normalize
|
12
14
|
|
13
15
|
def init(*)
|
14
16
|
end
|
15
17
|
|
18
|
+
def normalize(*)
|
19
|
+
end
|
20
|
+
|
16
21
|
def prepare(*)
|
17
22
|
end
|
18
23
|
end.build(relation)
|
19
24
|
end
|
20
25
|
|
21
26
|
it 'returns a new command with configured before hooks' do
|
22
|
-
expect(command.before(:prepare).before_hooks).to eql(%i[init prepare])
|
27
|
+
expect(command.before(:prepare).before_hooks).to eql(%i[init normalize prepare])
|
23
28
|
end
|
24
29
|
end
|
25
30
|
|
26
31
|
describe '#after' do
|
27
32
|
subject(:command) do
|
28
|
-
Class.new(ROM::
|
33
|
+
Class.new(ROM::Commands::Create[:memory]) do
|
29
34
|
after :finalize
|
35
|
+
after :filter
|
30
36
|
|
31
37
|
def finalize(*)
|
32
38
|
end
|
33
39
|
|
40
|
+
def filter(*)
|
41
|
+
end
|
42
|
+
|
34
43
|
def prepare(*)
|
35
44
|
end
|
36
45
|
end.build(relation)
|
37
46
|
end
|
38
47
|
|
39
48
|
it 'returns a new command with configured after hooks' do
|
40
|
-
expect(command.after(:prepare).after_hooks).to eql(%i[finalize prepare])
|
49
|
+
expect(command.after(:prepare).after_hooks).to eql(%i[finalize filter prepare])
|
41
50
|
end
|
42
51
|
end
|
43
52
|
|
44
53
|
context 'without curried args' do
|
45
54
|
subject(:command) do
|
46
|
-
Class.new(ROM::
|
55
|
+
Class.new(ROM::Commands::Create[:memory]) do
|
47
56
|
result :many
|
48
57
|
before :prepare
|
49
58
|
after :finalize
|
@@ -87,7 +96,7 @@ RSpec.describe ROM::Command, 'before/after hooks' do
|
|
87
96
|
|
88
97
|
context 'with curried args' do
|
89
98
|
subject(:command) do
|
90
|
-
Class.new(ROM::
|
99
|
+
Class.new(ROM::Commands::Create[:memory]) do
|
91
100
|
result :many
|
92
101
|
before :prepare
|
93
102
|
after :finalize
|
@@ -135,10 +144,10 @@ RSpec.describe ROM::Command, 'before/after hooks' do
|
|
135
144
|
|
136
145
|
context 'with pre-set opts' do
|
137
146
|
subject(:command) do
|
138
|
-
Class.new(ROM::
|
147
|
+
Class.new(ROM::Commands::Create[:memory]) do
|
139
148
|
result :many
|
140
|
-
before
|
141
|
-
after
|
149
|
+
before prepare: { prepared: true }
|
150
|
+
after finalize: { finalized: true }
|
142
151
|
|
143
152
|
def execute(tuples)
|
144
153
|
input = tuples.map.with_index { |tuple, idx| tuple.merge(id: idx + 1) }
|
@@ -183,10 +192,10 @@ RSpec.describe ROM::Command, 'before/after hooks' do
|
|
183
192
|
|
184
193
|
context 'with pre-set opts for a curried command' do
|
185
194
|
subject(:command) do
|
186
|
-
Class.new(ROM::
|
195
|
+
Class.new(ROM::Commands::Create[:memory]) do
|
187
196
|
result :many
|
188
|
-
before
|
189
|
-
after
|
197
|
+
before prepare: { prepared: true }
|
198
|
+
after finalize: { finalized: true }
|
190
199
|
|
191
200
|
def execute(tuples)
|
192
201
|
input = tuples.map.with_index { |tuple, idx| tuple.merge(id: idx + 1) }
|
@@ -231,10 +240,10 @@ RSpec.describe ROM::Command, 'before/after hooks' do
|
|
231
240
|
|
232
241
|
context 'calling with multiple args' do
|
233
242
|
subject(:command) do
|
234
|
-
Class.new(ROM::
|
243
|
+
Class.new(ROM::Commands::Create[:memory]) do
|
235
244
|
result :many
|
236
|
-
before
|
237
|
-
after
|
245
|
+
before prepare: { prepared: true }
|
246
|
+
after finalize: { finalized: true }
|
238
247
|
|
239
248
|
def execute(tuples)
|
240
249
|
input = tuples.map.with_index { |tuple, idx| tuple.merge(id: idx + 1) }
|
@@ -1,9 +1,19 @@
|
|
1
1
|
require 'rom'
|
2
2
|
require 'rom/memory'
|
3
3
|
|
4
|
-
RSpec.describe ROM::Relation do
|
4
|
+
RSpec.describe ROM::Relation, '.view' do
|
5
5
|
subject(:relation) { relation_class.new(ROM::Memory::Dataset.new([])) }
|
6
6
|
|
7
|
+
it 'returns view method name' do
|
8
|
+
klass = Class.new(ROM::Relation[:memory]) {
|
9
|
+
schema { attribute :id, ROM::Types::Int }
|
10
|
+
}
|
11
|
+
|
12
|
+
name = klass.view(:by_id, klass.schema) { self }
|
13
|
+
|
14
|
+
expect(name).to be(:by_id)
|
15
|
+
end
|
16
|
+
|
7
17
|
shared_context 'relation with views' do
|
8
18
|
before do
|
9
19
|
relation << { id: 1, name: 'Joe' }
|
@@ -1,31 +1,31 @@
|
|
1
|
-
require 'rom/schema/
|
1
|
+
require 'rom/schema/attribute'
|
2
2
|
|
3
|
-
RSpec.describe ROM::Schema::
|
3
|
+
RSpec.describe ROM::Schema::Attribute do
|
4
4
|
describe '#inspect' do
|
5
5
|
context 'with a primitive definition' do
|
6
6
|
subject(:type) do
|
7
|
-
ROM::Schema::
|
7
|
+
ROM::Schema::Attribute.new(ROM::Types::Int).meta(name: :id, primary_key: true)
|
8
8
|
end
|
9
9
|
|
10
10
|
specify do
|
11
|
-
expect(type.inspect).to eql("#<ROM::Schema::
|
11
|
+
expect(type.inspect).to eql("#<ROM::Schema::Attribute[Integer] name=:id primary_key=true>")
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
15
|
context 'with a sum' do
|
16
16
|
subject(:type) do
|
17
|
-
ROM::Schema::
|
17
|
+
ROM::Schema::Attribute.new(ROM::Types::Bool).meta(name: :admin)
|
18
18
|
end
|
19
19
|
|
20
20
|
specify do
|
21
|
-
expect(type.inspect).to eql("#<ROM::Schema::
|
21
|
+
expect(type.inspect).to eql("#<ROM::Schema::Attribute[TrueClass | FalseClass] name=:admin>")
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
26
|
describe '#aliased' do
|
27
27
|
subject(:type) do
|
28
|
-
ROM::Schema::
|
28
|
+
ROM::Schema::Attribute.new(ROM::Types::String).meta(name: :user_name)
|
29
29
|
end
|
30
30
|
|
31
31
|
specify do
|
@@ -35,7 +35,7 @@ RSpec.describe ROM::Schema::Type do
|
|
35
35
|
|
36
36
|
describe '#method_missing' do
|
37
37
|
subject(:type) do
|
38
|
-
ROM::Schema::
|
38
|
+
ROM::Schema::Attribute.new(ROM::Types::Int).meta(name: :id, primary_key: true)
|
39
39
|
end
|
40
40
|
|
41
41
|
specify do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rom
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.0.
|
4
|
+
version: 3.0.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Solnica
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-01-
|
11
|
+
date: 2017-01-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -44,14 +44,20 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '0.
|
47
|
+
version: '0.9'
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 0.9.4
|
48
51
|
type: :runtime
|
49
52
|
prerelease: false
|
50
53
|
version_requirements: !ruby/object:Gem::Requirement
|
51
54
|
requirements:
|
52
55
|
- - "~>"
|
53
56
|
- !ruby/object:Gem::Version
|
54
|
-
version: '0.
|
57
|
+
version: '0.9'
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 0.9.4
|
55
61
|
- !ruby/object:Gem::Dependency
|
56
62
|
name: dry-core
|
57
63
|
requirement: !ruby/object:Gem::Requirement
|
@@ -98,14 +104,14 @@ dependencies:
|
|
98
104
|
requirements:
|
99
105
|
- - "~>"
|
100
106
|
- !ruby/object:Gem::Version
|
101
|
-
version: 0.5.0.
|
107
|
+
version: 0.5.0.rc
|
102
108
|
type: :runtime
|
103
109
|
prerelease: false
|
104
110
|
version_requirements: !ruby/object:Gem::Requirement
|
105
111
|
requirements:
|
106
112
|
- - "~>"
|
107
113
|
- !ruby/object:Gem::Version
|
108
|
-
version: 0.5.0.
|
114
|
+
version: 0.5.0.rc
|
109
115
|
- !ruby/object:Gem::Dependency
|
110
116
|
name: rake
|
111
117
|
requirement: !ruby/object:Gem::Requirement
|
@@ -145,6 +151,7 @@ files:
|
|
145
151
|
- ".rubocop.yml"
|
146
152
|
- ".rubocop_todo.yml"
|
147
153
|
- ".travis.yml"
|
154
|
+
- ".yardopts"
|
148
155
|
- CHANGELOG.md
|
149
156
|
- CODE_OF_CONDUCT.md
|
150
157
|
- CONTRIBUTING.md
|
@@ -226,8 +233,8 @@ files:
|
|
226
233
|
- lib/rom/relation/view_dsl.rb
|
227
234
|
- lib/rom/relation_registry.rb
|
228
235
|
- lib/rom/schema.rb
|
236
|
+
- lib/rom/schema/attribute.rb
|
229
237
|
- lib/rom/schema/dsl.rb
|
230
|
-
- lib/rom/schema/type.rb
|
231
238
|
- lib/rom/setup.rb
|
232
239
|
- lib/rom/setup/auto_registration.rb
|
233
240
|
- lib/rom/setup/auto_registration_strategies/base.rb
|
data/lib/rom/schema/type.rb
DELETED
@@ -1,129 +0,0 @@
|
|
1
|
-
require 'delegate'
|
2
|
-
require 'dry/equalizer'
|
3
|
-
require 'dry/types/decorator'
|
4
|
-
|
5
|
-
module ROM
|
6
|
-
class Schema
|
7
|
-
class Type
|
8
|
-
include Dry::Equalizer(:type)
|
9
|
-
|
10
|
-
attr_reader :type
|
11
|
-
|
12
|
-
def initialize(type)
|
13
|
-
@type = type
|
14
|
-
end
|
15
|
-
|
16
|
-
# @api private
|
17
|
-
def [](input)
|
18
|
-
type[input]
|
19
|
-
end
|
20
|
-
|
21
|
-
# @api private
|
22
|
-
def read?
|
23
|
-
! meta[:read].nil?
|
24
|
-
end
|
25
|
-
|
26
|
-
def to_read_type
|
27
|
-
read? ? meta[:read] : type
|
28
|
-
end
|
29
|
-
|
30
|
-
# @api public
|
31
|
-
def primary_key?
|
32
|
-
meta[:primary_key].equal?(true)
|
33
|
-
end
|
34
|
-
|
35
|
-
# @api public
|
36
|
-
def foreign_key?
|
37
|
-
meta[:foreign_key].equal?(true)
|
38
|
-
end
|
39
|
-
|
40
|
-
# @api public
|
41
|
-
def aliased?
|
42
|
-
!meta[:alias].nil?
|
43
|
-
end
|
44
|
-
|
45
|
-
# @api public
|
46
|
-
def source
|
47
|
-
meta[:source]
|
48
|
-
end
|
49
|
-
|
50
|
-
# @api public
|
51
|
-
def target
|
52
|
-
meta[:target]
|
53
|
-
end
|
54
|
-
|
55
|
-
# @api public
|
56
|
-
def name
|
57
|
-
meta[:name]
|
58
|
-
end
|
59
|
-
|
60
|
-
# @api public
|
61
|
-
def alias
|
62
|
-
meta[:alias]
|
63
|
-
end
|
64
|
-
|
65
|
-
# @api public
|
66
|
-
def aliased(name)
|
67
|
-
meta(alias: name)
|
68
|
-
end
|
69
|
-
alias_method :as, :aliased
|
70
|
-
|
71
|
-
# @api public
|
72
|
-
def prefixed(prefix = source.dataset)
|
73
|
-
aliased(:"#{prefix}_#{name}")
|
74
|
-
end
|
75
|
-
|
76
|
-
# @api public
|
77
|
-
def wrapped?
|
78
|
-
meta[:wrapped].equal?(true)
|
79
|
-
end
|
80
|
-
|
81
|
-
# @api public
|
82
|
-
def wrapped(name = source.dataset)
|
83
|
-
self.class.new(prefixed(name).meta(wrapped: true))
|
84
|
-
end
|
85
|
-
|
86
|
-
# @api public
|
87
|
-
def meta(opts = nil)
|
88
|
-
if opts
|
89
|
-
self.class.new(type.meta(opts))
|
90
|
-
else
|
91
|
-
type.meta
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
# @api public
|
96
|
-
def inspect
|
97
|
-
%(#<#{self.class}[#{type.name}] #{meta.map { |k, v| "#{k}=#{v.inspect}" }.join(' ')}>)
|
98
|
-
end
|
99
|
-
alias_method :pretty_inspect, :inspect
|
100
|
-
|
101
|
-
# @api public
|
102
|
-
def eql?(other)
|
103
|
-
other.is_a?(self.class) ? super : type.eql?(other)
|
104
|
-
end
|
105
|
-
|
106
|
-
# @api private
|
107
|
-
def respond_to_missing?(name, include_private = false)
|
108
|
-
type.respond_to?(name) || super
|
109
|
-
end
|
110
|
-
|
111
|
-
private
|
112
|
-
|
113
|
-
# @api private
|
114
|
-
def method_missing(meth, *args, &block)
|
115
|
-
if type.respond_to?(meth)
|
116
|
-
response = type.__send__(meth, *args, &block)
|
117
|
-
|
118
|
-
if response.is_a?(type.class)
|
119
|
-
self.class.new(type)
|
120
|
-
else
|
121
|
-
response
|
122
|
-
end
|
123
|
-
else
|
124
|
-
super
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|