rom 3.0.0.beta3 → 3.0.0.rc1
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/.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
|