rom-sql 1.0.0.beta2 → 1.0.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/CHANGELOG.md +4 -0
- data/circle.yml +1 -1
- data/lib/rom/plugins/relation/sql/auto_combine.rb +1 -1
- data/lib/rom/sql/association.rb +5 -1
- data/lib/rom/sql/association/many_to_many.rb +8 -4
- data/lib/rom/sql/association/many_to_one.rb +2 -3
- data/lib/rom/sql/association/one_to_many.rb +2 -3
- data/lib/rom/sql/commands/create.rb +8 -1
- data/lib/rom/sql/commands/update.rb +7 -0
- data/lib/rom/sql/extensions.rb +8 -0
- data/lib/rom/sql/extensions/active_support_notifications.rb +7 -18
- data/lib/rom/sql/extensions/mysql.rb +1 -0
- data/lib/rom/sql/extensions/mysql/inferrer.rb +10 -0
- data/lib/rom/sql/extensions/postgres/commands.rb +1 -1
- data/lib/rom/sql/extensions/postgres/inferrer.rb +4 -0
- data/lib/rom/sql/extensions/postgres/types.rb +20 -22
- data/lib/rom/sql/extensions/sqlite.rb +1 -0
- data/lib/rom/sql/extensions/sqlite/inferrer.rb +10 -0
- data/lib/rom/sql/function.rb +6 -2
- data/lib/rom/sql/order_dsl.rb +1 -1
- data/lib/rom/sql/plugin/associates.rb +28 -5
- data/lib/rom/sql/relation.rb +18 -0
- data/lib/rom/sql/relation/reading.rb +2 -2
- data/lib/rom/sql/relation/sequel_api.rb +119 -0
- data/lib/rom/sql/schema/inferrer.rb +26 -2
- data/lib/rom/sql/tasks/migration_tasks.rake +1 -1
- data/lib/rom/sql/type.rb +13 -2
- data/lib/rom/sql/types.rb +5 -3
- data/lib/rom/sql/version.rb +1 -1
- data/spec/extensions/postgres/types_spec.rb +29 -0
- data/spec/integration/association/many_to_many_spec.rb +8 -0
- data/spec/integration/association/many_to_one_spec.rb +11 -0
- data/spec/integration/association/one_to_many_spec.rb +9 -0
- data/spec/integration/schema/inferrer/mysql_spec.rb +36 -0
- data/spec/integration/schema/inferrer/postgres_spec.rb +118 -0
- data/spec/integration/schema/inferrer/sqlite_spec.rb +36 -0
- data/spec/integration/{schema_inference_spec.rb → schema/inferrer_spec.rb} +44 -15
- data/spec/integration/sequel_api_spec.rb +31 -0
- data/spec/support/helpers.rb +4 -0
- data/spec/unit/function_spec.rb +35 -0
- data/spec/unit/order_dsl_spec.rb +35 -0
- data/spec/unit/relation/assoc_spec.rb +38 -0
- data/spec/unit/relation/inner_join_spec.rb +15 -0
- data/spec/unit/types_spec.rb +53 -1
- metadata +23 -6
- data/spec/extensions/postgres/inferrer_spec.rb +0 -59
data/lib/rom/sql/relation.rb
CHANGED
@@ -3,6 +3,7 @@ require 'rom/sql/schema'
|
|
3
3
|
|
4
4
|
require 'rom/sql/relation/reading'
|
5
5
|
require 'rom/sql/relation/writing'
|
6
|
+
require 'rom/sql/relation/sequel_api'
|
6
7
|
|
7
8
|
require 'rom/plugins/relation/key_inference'
|
8
9
|
require 'rom/plugins/relation/sql/auto_combine'
|
@@ -88,6 +89,23 @@ module ROM
|
|
88
89
|
|
89
90
|
option :primary_key, reader: true, default: -> rel { rel.schema.primary_key_name }
|
90
91
|
|
92
|
+
# Return relation that will load associated tuples of this relation
|
93
|
+
#
|
94
|
+
# This method is useful for defining custom relation views for relation
|
95
|
+
# composition when you want to enhance default association query
|
96
|
+
#
|
97
|
+
# @example
|
98
|
+
# assoc(:tasks).where(tasks[:title] => "Task One")
|
99
|
+
#
|
100
|
+
# @param [Symbol] name The association name
|
101
|
+
#
|
102
|
+
# @return [Relation]
|
103
|
+
#
|
104
|
+
# @api public
|
105
|
+
def assoc(name)
|
106
|
+
associations[name].(__registry__)
|
107
|
+
end
|
108
|
+
|
91
109
|
# Return raw column names
|
92
110
|
#
|
93
111
|
# @return [Array<Symbol>]
|
@@ -558,10 +558,10 @@ module ROM
|
|
558
558
|
private
|
559
559
|
|
560
560
|
# @api private
|
561
|
-
def __join__(type, other, opts = EMPTY_HASH, &block)
|
561
|
+
def __join__(type, other, join_cond = EMPTY_HASH, opts = EMPTY_HASH, &block)
|
562
562
|
case other
|
563
563
|
when Symbol, Association::Name
|
564
|
-
new(dataset.__send__(type, other.to_sym, opts, &block))
|
564
|
+
new(dataset.__send__(type, other.to_sym, join_cond, opts, &block))
|
565
565
|
when Relation
|
566
566
|
__send__(type, other.name.dataset, join_keys(other))
|
567
567
|
else
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module ROM
|
2
|
+
module SQL
|
3
|
+
# Query API for SQL::Relation
|
4
|
+
#
|
5
|
+
# @api public
|
6
|
+
module SequelAPI
|
7
|
+
# Select specific columns for select clause
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# users.select(:id, :name).first
|
11
|
+
# # {:id => 1, :name => "Jane" }
|
12
|
+
#
|
13
|
+
# @return [Relation]
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
def select(*args, &block)
|
17
|
+
new(dataset.__send__(__method__, *args, &block))
|
18
|
+
end
|
19
|
+
|
20
|
+
# Append specific columns to select clause
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# users.select(:id, :name).select_append(:email)
|
24
|
+
# # {:id => 1, :name => "Jane", :email => "jane@doe.org"}
|
25
|
+
#
|
26
|
+
# @param [Array<Symbol>] *args A list with column names
|
27
|
+
#
|
28
|
+
# @return [Relation]
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
def select_append(*args, &block)
|
32
|
+
new(dataset.__send__(__method__, *args, &block))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Restrict a relation to match criteria
|
36
|
+
#
|
37
|
+
# If block is passed it'll be executed in the context of a condition
|
38
|
+
# builder object.
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# users.where(name: 'Jane')
|
42
|
+
#
|
43
|
+
# users.where { age >= 18 }
|
44
|
+
#
|
45
|
+
# @param [Hash] *args An optional hash with conditions for WHERE clause
|
46
|
+
#
|
47
|
+
# @return [Relation]
|
48
|
+
#
|
49
|
+
# @see http://sequel.jeremyevans.net/rdoc/files/doc/dataset_filtering_rdoc.html
|
50
|
+
#
|
51
|
+
# @api public
|
52
|
+
def where(*args, &block)
|
53
|
+
new(dataset.__send__(__method__, *args, &block))
|
54
|
+
end
|
55
|
+
|
56
|
+
# Restrict a relation to match grouping criteria
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# users.with_task_count.having( task_count: 2 )
|
60
|
+
#
|
61
|
+
# users.with_task_count.having { task_count > 3 }
|
62
|
+
#
|
63
|
+
# @param [Hash] *args An optional hash with conditions for HAVING clause
|
64
|
+
#
|
65
|
+
# @return [Relation]
|
66
|
+
#
|
67
|
+
# @see http://sequel.jeremyevans.net/rdoc/files/doc/dataset_filtering_rdoc.html
|
68
|
+
#
|
69
|
+
# @api public
|
70
|
+
def having(*args, &block)
|
71
|
+
new(dataset.__send__(__method__, *args, &block))
|
72
|
+
end
|
73
|
+
|
74
|
+
# Set order for the relation
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# users.order(:name)
|
78
|
+
#
|
79
|
+
# @param [Array<Symbol>] *args A list with column names
|
80
|
+
#
|
81
|
+
# @return [Relation]
|
82
|
+
#
|
83
|
+
# @api public
|
84
|
+
def order(*args, &block)
|
85
|
+
new(dataset.__send__(__method__, *args, &block))
|
86
|
+
end
|
87
|
+
|
88
|
+
# Join with another relation using INNER JOIN
|
89
|
+
#
|
90
|
+
# @example
|
91
|
+
# users.inner_join(:tasks, id: :user_id)
|
92
|
+
#
|
93
|
+
# @param [Symbol] relation name
|
94
|
+
# @param [Hash] join keys
|
95
|
+
#
|
96
|
+
# @return [Relation]
|
97
|
+
#
|
98
|
+
# @api public
|
99
|
+
def inner_join(*args, &block)
|
100
|
+
new(dataset.__send__(__method__, *args, &block))
|
101
|
+
end
|
102
|
+
|
103
|
+
# Join other relation using LEFT OUTER JOIN
|
104
|
+
#
|
105
|
+
# @example
|
106
|
+
# users.left_join(:tasks, id: :user_id)
|
107
|
+
#
|
108
|
+
# @param [Symbol] relation name
|
109
|
+
# @param [Hash] join keys
|
110
|
+
#
|
111
|
+
# @return [Relation]
|
112
|
+
#
|
113
|
+
# @api public
|
114
|
+
def left_join(*args, &block)
|
115
|
+
new(dataset.__send__(__method__, *args, &block))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -26,6 +26,7 @@ module ROM
|
|
26
26
|
db_registry Hash.new(self)
|
27
27
|
|
28
28
|
CONSTRAINT_DB_TYPE = 'add_constraint'.freeze
|
29
|
+
DECIMAL_REGEX = /(?:decimal|numeric)\((\d+)(?:,\s*(\d+))?\)/.freeze
|
29
30
|
|
30
31
|
def self.inherited(klass)
|
31
32
|
super
|
@@ -72,9 +73,16 @@ module ROM
|
|
72
73
|
mapped_type = map_type(type, db_type, rest)
|
73
74
|
|
74
75
|
if mapped_type
|
76
|
+
read_type = mapped_type.meta[:read]
|
75
77
|
mapped_type = mapped_type.optional if allow_null
|
76
78
|
mapped_type = mapped_type.meta(foreign_key: true, target: foreign_key) if foreign_key
|
77
|
-
|
79
|
+
if read_type && allow_null
|
80
|
+
mapped_type.meta(read: read_type.optional)
|
81
|
+
elsif read_type
|
82
|
+
mapped_type.meta(read: read_type)
|
83
|
+
else
|
84
|
+
mapped_type
|
85
|
+
end
|
78
86
|
end
|
79
87
|
end
|
80
88
|
end
|
@@ -87,7 +95,7 @@ module ROM
|
|
87
95
|
type = self.class.ruby_type_mapping[ruby_type]
|
88
96
|
|
89
97
|
if db_type.is_a?(String) && db_type.include?('numeric') || db_type.include?('decimal')
|
90
|
-
|
98
|
+
map_decimal_type(db_type)
|
91
99
|
else
|
92
100
|
type
|
93
101
|
end
|
@@ -110,6 +118,22 @@ module ROM
|
|
110
118
|
columns[0]
|
111
119
|
end
|
112
120
|
end
|
121
|
+
|
122
|
+
# @api private
|
123
|
+
def map_decimal_type(type)
|
124
|
+
precision = DECIMAL_REGEX.match(type)
|
125
|
+
|
126
|
+
if precision
|
127
|
+
prcsn, scale = precision[1..2].map(&:to_i)
|
128
|
+
|
129
|
+
self.class.ruby_type_mapping[:decimal].meta(
|
130
|
+
precision: prcsn,
|
131
|
+
scale: scale
|
132
|
+
)
|
133
|
+
else
|
134
|
+
self.class.ruby_type_mapping[:decimal]
|
135
|
+
end
|
136
|
+
end
|
113
137
|
end
|
114
138
|
end
|
115
139
|
end
|
@@ -24,11 +24,11 @@ module ROM
|
|
24
24
|
end
|
25
25
|
|
26
26
|
namespace :db do
|
27
|
-
desc "Perform migration reset (full erase and migration up)"
|
28
27
|
task :rom_configuration do
|
29
28
|
Rake::Task["db:setup"].invoke
|
30
29
|
end
|
31
30
|
|
31
|
+
desc "Perform migration reset (full erase and migration up)"
|
32
32
|
task reset: :rom_configuration do
|
33
33
|
ROM::SQL::RakeSupport.run_migrations(target: 0)
|
34
34
|
ROM::SQL::RakeSupport.run_migrations
|
data/lib/rom/sql/type.rb
CHANGED
@@ -3,6 +3,8 @@ require 'rom/schema/type'
|
|
3
3
|
module ROM
|
4
4
|
module SQL
|
5
5
|
class Type < ROM::Schema::Type
|
6
|
+
QualifyError = Class.new(StandardError)
|
7
|
+
|
6
8
|
# Return a new type marked as a FK
|
7
9
|
#
|
8
10
|
# @return [SQL::Type]
|
@@ -13,9 +15,10 @@ module ROM
|
|
13
15
|
end
|
14
16
|
|
15
17
|
# @api public
|
16
|
-
def
|
18
|
+
def aliased(name)
|
17
19
|
super.meta(sql_expr: sql_expr.as(name))
|
18
20
|
end
|
21
|
+
alias_method :as, :aliased
|
19
22
|
|
20
23
|
# Return a new type marked as qualified
|
21
24
|
#
|
@@ -23,7 +26,15 @@ module ROM
|
|
23
26
|
#
|
24
27
|
# @api public
|
25
28
|
def qualified
|
26
|
-
|
29
|
+
return self if qualified?
|
30
|
+
|
31
|
+
case sql_expr
|
32
|
+
when Sequel::SQL::AliasedExpression, Sequel::SQL::Identifier
|
33
|
+
type = meta(qualified: true)
|
34
|
+
type.meta(qualified: true, sql_expr: Sequel[type.to_sym])
|
35
|
+
else
|
36
|
+
raise QualifyError, "can't qualify #{name.inspect} (#{sql_expr.inspect})"
|
37
|
+
end
|
27
38
|
end
|
28
39
|
|
29
40
|
# Return a new type marked as joined
|
data/lib/rom/sql/types.rb
CHANGED
@@ -5,11 +5,13 @@ module ROM
|
|
5
5
|
module Types
|
6
6
|
include ROM::Types
|
7
7
|
|
8
|
+
def self.Constructor(*args, &block)
|
9
|
+
ROM::Types.Constructor(*args, &block)
|
10
|
+
end
|
11
|
+
|
8
12
|
Serial = Int.constrained(gt: 0).meta(primary_key: true)
|
9
13
|
|
10
|
-
Blob =
|
11
|
-
.new(Sequel::SQL::Blob)
|
12
|
-
.constructor(Sequel::SQL::Blob.method(:new))
|
14
|
+
Blob = Constructor(Sequel::SQL::Blob, &Sequel::SQL::Blob.method(:new))
|
13
15
|
end
|
14
16
|
end
|
15
17
|
end
|
data/lib/rom/sql/version.rb
CHANGED
@@ -112,4 +112,33 @@ RSpec.describe 'ROM::SQL::Types' do
|
|
112
112
|
expect(output).to be_instance_of(BigDecimal)
|
113
113
|
end
|
114
114
|
end
|
115
|
+
|
116
|
+
describe ROM::SQL::Types::PG::IPAddress do
|
117
|
+
it 'converts IPAddr to a string' do
|
118
|
+
expect(described_class[IPAddr.new('127.0.0.1')]).to eql('127.0.0.1')
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'coerces to builtin IPAddr type on read' do
|
122
|
+
expect(described_class.meta[:read]['127.0.0.1']).to eql(IPAddr.new('127.0.0.1'))
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'supports networks' do
|
126
|
+
class_a = described_class.meta[:read]['10.0.0.0/8']
|
127
|
+
|
128
|
+
expect(class_a).to eql(IPAddr.new('10.0.0.0/8'))
|
129
|
+
expect(class_a).to include(IPAddr.new('10.8.8.8'))
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe ROM::SQL::Types::PG::PointT do
|
134
|
+
let(:point) { ROM::SQL::Types::PG::Point.new(7.5, 30.5) }
|
135
|
+
|
136
|
+
it 'serializes a point down to a string' do
|
137
|
+
expect(described_class[point]).to eql('(7.5,30.5)')
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'reads serialized format' do
|
141
|
+
expect(described_class.meta[:read]['(7.5,30.5)']).to eql(point)
|
142
|
+
end
|
143
|
+
end
|
115
144
|
end
|
@@ -133,6 +133,14 @@ RSpec.describe ROM::SQL::Association::ManyToMany do
|
|
133
133
|
|
134
134
|
expect(relation.to_a).to eql([id: 1, name: 'important', task_id: 1])
|
135
135
|
end
|
136
|
+
|
137
|
+
it 'maintains original relation' do
|
138
|
+
relation = tags.
|
139
|
+
select_append(tags[:name].as(:tag)).
|
140
|
+
for_combine(assoc).call(tasks.call)
|
141
|
+
|
142
|
+
expect(relation.to_a).to eql([id: 1, tag: 'important', name: 'important', task_id: 1])
|
143
|
+
end
|
136
144
|
end
|
137
145
|
end
|
138
146
|
end
|
@@ -66,6 +66,17 @@ RSpec.describe ROM::SQL::Association::ManyToOne, helpers: true do
|
|
66
66
|
{ id: 1, task_id: 2, name: 'Jane' }
|
67
67
|
])
|
68
68
|
end
|
69
|
+
|
70
|
+
it 'maintains original relation' do
|
71
|
+
users.accounts.insert(user_id: 2, number: 'a1', balance: 0)
|
72
|
+
|
73
|
+
relation = users.
|
74
|
+
join(:accounts, user_id: :id).
|
75
|
+
select_append(users.accounts[:number].as(:account_num)).
|
76
|
+
for_combine(assoc).call(tasks.call)
|
77
|
+
|
78
|
+
expect(relation.to_a).to eql([{ id: 2, task_id: 1, name: 'Joe', account_num: 'a1' }])
|
79
|
+
end
|
69
80
|
end
|
70
81
|
|
71
82
|
context 'arbitrary name conventions' do
|
@@ -53,6 +53,15 @@ RSpec.describe ROM::SQL::Association::OneToMany do
|
|
53
53
|
{ id: 2, user_id: 1, title: "Jane's task" }
|
54
54
|
])
|
55
55
|
end
|
56
|
+
|
57
|
+
it 'maintains original relation' do
|
58
|
+
relation = tasks.
|
59
|
+
join(:task_tags, tag_id: :id).
|
60
|
+
select_append(tasks.task_tags[:tag_id].qualified).
|
61
|
+
for_combine(assoc).call(users.call)
|
62
|
+
|
63
|
+
expect(relation.to_a).to eql([{ id: 1, user_id: 2, title: "Joe's task", tag_id: 1 }])
|
64
|
+
end
|
56
65
|
end
|
57
66
|
end
|
58
67
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
RSpec.describe 'ROM::SQL::Schema::MysqlInferrer', :mysql do
|
2
|
+
include_context 'database setup'
|
3
|
+
|
4
|
+
before do
|
5
|
+
conn.drop_table?(:test_inferrence)
|
6
|
+
|
7
|
+
conn.create_table :test_inferrence do
|
8
|
+
tinyint :tiny
|
9
|
+
mediumint :medium
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
conn.drop_table?(:test_inferrence)
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:dataset) { :test_inferrence }
|
18
|
+
|
19
|
+
let(:schema) { container.relations[dataset].schema }
|
20
|
+
|
21
|
+
before do
|
22
|
+
dataset = self.dataset
|
23
|
+
conf.relation(dataset) do
|
24
|
+
schema(dataset, infer: true)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'can infer attributes for dataset' do
|
29
|
+
source = container.relations[:test_inferrence].name
|
30
|
+
|
31
|
+
expect(schema.to_h).to eql(
|
32
|
+
tiny: ROM::SQL::Types::Int.optional.meta(name: :tiny, source: source),
|
33
|
+
medium: ROM::SQL::Types::Int.optional.meta(name: :medium, source: source),
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
|
2
|
+
RSpec.describe 'ROM::SQL::Schema::PostgresInferrer', :postgres do
|
3
|
+
include_context 'database setup'
|
4
|
+
|
5
|
+
colors = %w(red orange yellow green blue purple)
|
6
|
+
|
7
|
+
before do
|
8
|
+
conn.extension :pg_enum
|
9
|
+
|
10
|
+
conn.drop_table?(:test_inferrence)
|
11
|
+
conn.drop_enum(:rainbow, if_exists: true)
|
12
|
+
|
13
|
+
conn.create_enum(:rainbow, colors)
|
14
|
+
|
15
|
+
conn.create_table :test_inferrence do
|
16
|
+
primary_key :id, :uuid
|
17
|
+
Json :json_data
|
18
|
+
Jsonb :jsonb_data
|
19
|
+
Decimal :money, null: false
|
20
|
+
column :tags, "text[]"
|
21
|
+
column :tag_ids, "bigint[]"
|
22
|
+
column :ip, "inet"
|
23
|
+
column :subnet, "cidr"
|
24
|
+
column :hw_address, "macaddr"
|
25
|
+
rainbow :color
|
26
|
+
point :center
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
after do
|
31
|
+
conn.drop_table?(:test_inferrence)
|
32
|
+
end
|
33
|
+
|
34
|
+
let(:dataset) { :test_inferrence }
|
35
|
+
|
36
|
+
let(:schema) { container.relations[dataset].schema }
|
37
|
+
|
38
|
+
context 'inferring db-specific attributes' do
|
39
|
+
before do
|
40
|
+
dataset = self.dataset
|
41
|
+
conf.relation(dataset) do
|
42
|
+
schema(dataset, infer: true)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'can infer attributes for dataset' do
|
47
|
+
source = container.relations[:test_inferrence].name
|
48
|
+
|
49
|
+
expect(schema.to_h).to eql(
|
50
|
+
id: ROM::SQL::Types::PG::UUID.meta(name: :id, source: source, primary_key: true),
|
51
|
+
json_data: ROM::SQL::Types::PG::JSON.optional.meta(name: :json_data, source: source),
|
52
|
+
jsonb_data: ROM::SQL::Types::PG::JSONB.optional.meta(name: :jsonb_data, source: source),
|
53
|
+
money: ROM::SQL::Types::Decimal.meta(name: :money, source: source),
|
54
|
+
tags: ROM::SQL::Types::PG::Array('text').optional.meta(name: :tags, source: source),
|
55
|
+
tag_ids: ROM::SQL::Types::PG::Array('biging').optional.meta(name: :tag_ids, source: source),
|
56
|
+
color: ROM::SQL::Types::String.enum(*colors).optional.meta(name: :color, source: source),
|
57
|
+
ip: ROM::SQL::Types::PG::IPAddress.optional.meta(
|
58
|
+
name: :ip,
|
59
|
+
source: source,
|
60
|
+
read: ROM::SQL::Types::PG::IPAddressR.optional
|
61
|
+
),
|
62
|
+
subnet: ROM::SQL::Types::PG::IPAddress.optional.meta(
|
63
|
+
name: :subnet,
|
64
|
+
source: source,
|
65
|
+
read: ROM::SQL::Types::PG::IPAddressR.optional
|
66
|
+
),
|
67
|
+
hw_address: ROM::SQL::Types::String.optional.meta(name: :hw_address, source: source),
|
68
|
+
center: ROM::SQL::Types::PG::PointT.optional.meta(
|
69
|
+
name: :center,
|
70
|
+
source: source,
|
71
|
+
read: ROM::SQL::Types::PG::PointTR.optional
|
72
|
+
)
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'with a table without columns' do
|
78
|
+
before do
|
79
|
+
conn.create_table(:dummy) unless conn.table_exists?(:dummy)
|
80
|
+
conf.relation(:dummy) { schema(infer: true) }
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'does not fail with a weird error when a relation does not have attributes' do
|
84
|
+
expect(container.relations[:dummy].schema).to be_empty
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'with a column with bi-directional mapping' do
|
89
|
+
before do
|
90
|
+
conn.drop_table?(:test_bidirectional)
|
91
|
+
conn.create_table(:test_bidirectional) do
|
92
|
+
primary_key :id
|
93
|
+
inet :ip
|
94
|
+
point :center
|
95
|
+
end
|
96
|
+
|
97
|
+
conf.relation(:test_bidirectional) { schema(infer: true) }
|
98
|
+
|
99
|
+
conf.commands(:test_bidirectional) do
|
100
|
+
define(:create) do
|
101
|
+
result :one
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
let(:point) { ROM::SQL::Types::PG::Point.new(7.5, 30.5) }
|
107
|
+
let(:dns) { IPAddr.new('8.8.8.8') }
|
108
|
+
|
109
|
+
let(:relation) { container.relations[:test_bidirectional] }
|
110
|
+
let(:create) { commands[:test_bidirectional].create }
|
111
|
+
|
112
|
+
it 'writes and reads data' do
|
113
|
+
inserted = create.call(id: 1, center: point, ip: dns)
|
114
|
+
expect(inserted).to eql(id: 1, center: point, ip: dns)
|
115
|
+
expect(relation.to_a).to eql([inserted])
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|