rom-sql 0.9.1 → 1.0.0.beta1
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 +32 -0
- data/Gemfile +4 -1
- data/lib/rom/plugins/relation/sql/auto_wrap.rb +1 -3
- data/lib/rom/sql/association.rb +33 -14
- data/lib/rom/sql/association/many_to_many.rb +17 -10
- data/lib/rom/sql/association/many_to_one.rb +29 -13
- data/lib/rom/sql/association/name.rb +12 -4
- data/lib/rom/sql/association/one_to_many.rb +21 -10
- data/lib/rom/sql/commands/create.rb +0 -1
- data/lib/rom/sql/commands/update.rb +1 -49
- data/lib/rom/sql/dsl.rb +29 -0
- data/lib/rom/sql/expression.rb +26 -0
- data/lib/rom/sql/function.rb +23 -0
- data/lib/rom/sql/gateway.rb +24 -9
- data/lib/rom/sql/migration.rb +6 -7
- data/lib/rom/sql/migration/migrator.rb +7 -8
- data/lib/rom/sql/order_dsl.rb +20 -0
- data/lib/rom/sql/plugin/associates.rb +58 -45
- data/lib/rom/sql/plugin/pagination.rb +8 -11
- data/lib/rom/sql/plugins.rb +0 -2
- data/lib/rom/sql/projection_dsl.rb +41 -0
- data/lib/rom/sql/qualified_attribute.rb +2 -2
- data/lib/rom/sql/relation.rb +35 -67
- data/lib/rom/sql/relation/reading.rb +77 -25
- data/lib/rom/sql/restriction_dsl.rb +24 -0
- data/lib/rom/sql/schema.rb +73 -7
- data/lib/rom/sql/schema/associations_dsl.rb +4 -3
- data/lib/rom/sql/schema/dsl.rb +5 -2
- data/lib/rom/sql/schema/inferrer.rb +21 -11
- data/lib/rom/sql/transaction.rb +19 -0
- data/lib/rom/sql/type.rb +76 -0
- data/lib/rom/sql/version.rb +1 -1
- data/rom-sql.gemspec +3 -4
- data/spec/extensions/postgres/inferrer_spec.rb +19 -9
- data/spec/integration/association/many_to_many/custom_fks_spec.rb +73 -0
- data/spec/integration/association/many_to_many/from_view_spec.rb +81 -0
- data/spec/integration/association/many_to_many_spec.rb +2 -2
- data/spec/integration/association/many_to_one/custom_fks_spec.rb +59 -0
- data/spec/integration/association/many_to_one/from_view_spec.rb +74 -0
- data/spec/integration/association/many_to_one/self_ref_spec.rb +51 -0
- data/spec/integration/association/many_to_one_spec.rb +4 -2
- data/spec/integration/association/one_to_many/custom_fks_spec.rb +48 -0
- data/spec/integration/association/one_to_many/from_view_spec.rb +57 -0
- data/spec/integration/association/one_to_many/self_ref_spec.rb +52 -0
- data/spec/integration/association/one_to_many_spec.rb +1 -1
- data/spec/integration/association/one_to_one_spec.rb +1 -1
- data/spec/integration/association/one_to_one_through_spec.rb +2 -2
- data/spec/integration/commands/create_spec.rb +11 -27
- data/spec/integration/commands/update_spec.rb +54 -109
- data/spec/integration/gateway_spec.rb +31 -17
- data/spec/integration/plugins/associates_spec.rb +27 -0
- data/spec/integration/plugins/auto_wrap_spec.rb +8 -8
- data/spec/integration/schema/call_spec.rb +24 -0
- data/spec/integration/schema/prefix_spec.rb +18 -0
- data/spec/integration/schema/qualified_spec.rb +18 -0
- data/spec/integration/schema/rename_spec.rb +23 -0
- data/spec/integration/schema/view_spec.rb +29 -0
- data/spec/integration/schema_inference_spec.rb +31 -14
- data/spec/spec_helper.rb +2 -2
- data/spec/support/helpers.rb +7 -0
- data/spec/unit/gateway_spec.rb +5 -4
- data/spec/unit/projection_dsl_spec.rb +54 -0
- data/spec/unit/relation/dataset_spec.rb +3 -3
- data/spec/unit/relation/distinct_spec.rb +8 -7
- data/spec/unit/relation/exclude_spec.rb +2 -4
- data/spec/unit/relation/having_spec.rb +6 -4
- data/spec/unit/relation/inner_join_spec.rb +47 -2
- data/spec/unit/relation/invert_spec.rb +2 -3
- data/spec/unit/relation/left_join_spec.rb +44 -3
- data/spec/unit/relation/order_spec.rb +40 -0
- data/spec/unit/relation/prefix_spec.rb +2 -0
- data/spec/unit/relation/project_spec.rb +3 -1
- data/spec/unit/relation/qualified_columns_spec.rb +2 -0
- data/spec/unit/relation/rename_spec.rb +2 -0
- data/spec/unit/relation/right_join_spec.rb +59 -0
- data/spec/unit/relation/select_append_spec.rb +21 -0
- data/spec/unit/relation/select_spec.rb +41 -0
- data/spec/unit/relation/where_spec.rb +28 -0
- data/spec/unit/restriction_dsl_spec.rb +34 -0
- metadata +62 -40
- data/lib/rom/plugins/relation/sql/base_view.rb +0 -31
- data/lib/rom/sql/header.rb +0 -61
- data/lib/rom/sql/plugin/assoc_macros.rb +0 -133
- data/lib/rom/sql/plugin/assoc_macros/class_interface.rb +0 -128
- data/spec/integration/read_spec.rb +0 -111
- data/spec/unit/association_errors_spec.rb +0 -19
- data/spec/unit/plugin/assoc_macros/combined_associations_spec.rb +0 -73
- data/spec/unit/plugin/assoc_macros/many_to_many_spec.rb +0 -53
- data/spec/unit/plugin/assoc_macros/many_to_one_spec.rb +0 -61
- data/spec/unit/plugin/assoc_macros/one_to_many_spec.rb +0 -78
- data/spec/unit/plugin/base_view_spec.rb +0 -18
data/lib/rom/sql/schema.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'rom/schema'
|
2
|
-
require 'rom/
|
2
|
+
require 'rom/sql/order_dsl'
|
3
|
+
require 'rom/sql/projection_dsl'
|
4
|
+
require 'rom/sql/restriction_dsl'
|
3
5
|
|
4
6
|
module ROM
|
5
7
|
module SQL
|
@@ -13,19 +15,83 @@ module ROM
|
|
13
15
|
# @return [Array<Symbol>] A list of all pk names
|
14
16
|
attr_reader :primary_key_names
|
15
17
|
|
18
|
+
# @api private
|
16
19
|
def initialize(*)
|
17
20
|
super
|
18
|
-
|
19
|
-
|
21
|
+
initialize_primary_key_names
|
22
|
+
end
|
23
|
+
|
24
|
+
# @api public
|
25
|
+
def restriction(&block)
|
26
|
+
RestrictionDSL.new(self).call(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @api public
|
30
|
+
def order(&block)
|
31
|
+
OrderDSL.new(self).call(&block)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Return a new schema with attributes marked as qualified
|
35
|
+
#
|
36
|
+
# @return [Schema]
|
37
|
+
#
|
38
|
+
# @api public
|
39
|
+
def qualified
|
40
|
+
new(map(&:qualified))
|
41
|
+
end
|
42
|
+
|
43
|
+
# @api public
|
44
|
+
def project(*names, &block)
|
45
|
+
if block
|
46
|
+
super(*(names + ProjectionDSL.new(self).(&block)))
|
47
|
+
else
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# @api private
|
53
|
+
def project_pk
|
54
|
+
project(*primary_key_names)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @api private
|
58
|
+
def project_fk(mapping)
|
59
|
+
new(rename(mapping).map(&:foreign_key))
|
60
|
+
end
|
61
|
+
|
62
|
+
# @api public
|
63
|
+
def join(other)
|
64
|
+
merge(other.joined)
|
65
|
+
end
|
66
|
+
|
67
|
+
# @api public
|
68
|
+
def joined
|
69
|
+
new(map(&:joined))
|
70
|
+
end
|
71
|
+
|
72
|
+
# Create a new relation based on the schema definition
|
73
|
+
#
|
74
|
+
# @param [Relation] relation The source relation
|
75
|
+
#
|
76
|
+
# @return [Relation]
|
77
|
+
#
|
78
|
+
# @api public
|
79
|
+
def call(relation)
|
80
|
+
relation.new(relation.dataset.select(*self), schema: self)
|
20
81
|
end
|
21
82
|
|
22
83
|
# @api private
|
23
84
|
def finalize!(*)
|
24
85
|
super do
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
86
|
+
initialize_primary_key_names
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# @api private
|
91
|
+
def initialize_primary_key_names
|
92
|
+
if primary_key.size > 0
|
93
|
+
@primary_key_name = primary_key[0].meta[:name]
|
94
|
+
@primary_key_names = primary_key.map { |type| type.meta[:name] }
|
29
95
|
end
|
30
96
|
end
|
31
97
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'dry/core/inflector'
|
1
2
|
require 'rom/sql/association'
|
2
3
|
|
3
4
|
module ROM
|
@@ -42,11 +43,11 @@ module ROM
|
|
42
43
|
end
|
43
44
|
|
44
45
|
def belongs_to(name, options = {})
|
45
|
-
many_to_one(dataset_name(name),
|
46
|
+
many_to_one(dataset_name(name), {as: name}.merge(options))
|
46
47
|
end
|
47
48
|
|
48
49
|
def has_one(name, options = {})
|
49
|
-
one_to_one(dataset_name(name),
|
50
|
+
one_to_one(dataset_name(name), {as: name}.merge(options))
|
50
51
|
end
|
51
52
|
|
52
53
|
def call
|
@@ -60,7 +61,7 @@ module ROM
|
|
60
61
|
end
|
61
62
|
|
62
63
|
def dataset_name(name)
|
63
|
-
Inflector.pluralize(name).to_sym
|
64
|
+
::Dry::Core::Inflector.pluralize(name).to_sym
|
64
65
|
end
|
65
66
|
end
|
66
67
|
end
|
data/lib/rom/sql/schema/dsl.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'rom/sql/type'
|
1
2
|
require 'rom/sql/schema/inferrer'
|
2
3
|
require 'rom/sql/schema/associations_dsl'
|
3
4
|
|
@@ -8,11 +9,13 @@ module ROM
|
|
8
9
|
attr_reader :associations_dsl
|
9
10
|
|
10
11
|
def associations(&block)
|
11
|
-
@associations_dsl = AssociationsDSL.new(
|
12
|
+
@associations_dsl = AssociationsDSL.new(relation, &block)
|
12
13
|
end
|
13
14
|
|
14
15
|
def call
|
15
|
-
SQL::Schema.
|
16
|
+
SQL::Schema.define(
|
17
|
+
relation, opts.merge(attributes: attributes.values, type_class: SQL::Type)
|
18
|
+
)
|
16
19
|
end
|
17
20
|
|
18
21
|
def opts
|
@@ -1,9 +1,11 @@
|
|
1
|
+
require 'dry/core/class_attributes'
|
2
|
+
|
1
3
|
module ROM
|
2
4
|
module SQL
|
3
5
|
class Schema < ROM::Schema
|
4
6
|
# @api private
|
5
7
|
class Inferrer
|
6
|
-
extend
|
8
|
+
extend Dry::Core::ClassAttributes
|
7
9
|
|
8
10
|
defines :ruby_type_mapping, :numeric_pk_type, :db_type, :db_registry
|
9
11
|
|
@@ -37,14 +39,21 @@ module ROM
|
|
37
39
|
end
|
38
40
|
|
39
41
|
# @api private
|
40
|
-
def call(
|
42
|
+
def call(source, gateway)
|
43
|
+
dataset = source.dataset
|
44
|
+
|
41
45
|
columns = gateway.connection.schema(dataset)
|
42
46
|
fks = fks_for(gateway, dataset)
|
43
47
|
|
44
|
-
columns.
|
48
|
+
inferred = columns.map do |(name, definition)|
|
45
49
|
type = build_type(definition.merge(foreign_key: fks[name]))
|
46
|
-
|
47
|
-
|
50
|
+
|
51
|
+
if type
|
52
|
+
type.meta(name: name, source: source)
|
53
|
+
end
|
54
|
+
end.compact
|
55
|
+
|
56
|
+
[inferred, columns.map(&:first) - inferred.map { |attr| attr.meta[:name] }]
|
48
57
|
end
|
49
58
|
|
50
59
|
private
|
@@ -54,9 +63,12 @@ module ROM
|
|
54
63
|
map_pk_type(type, db_type)
|
55
64
|
else
|
56
65
|
mapped_type = map_type(type, db_type, rest)
|
57
|
-
|
58
|
-
|
59
|
-
|
66
|
+
|
67
|
+
if mapped_type
|
68
|
+
mapped_type = mapped_type.optional if allow_null
|
69
|
+
mapped_type = mapped_type.meta(foreign_key: true, target: foreign_key) if foreign_key
|
70
|
+
mapped_type
|
71
|
+
end
|
60
72
|
end
|
61
73
|
end
|
62
74
|
|
@@ -65,9 +77,7 @@ module ROM
|
|
65
77
|
end
|
66
78
|
|
67
79
|
def map_type(ruby_type, db_type, **_kw)
|
68
|
-
self.class.ruby_type_mapping
|
69
|
-
raise UnknownDBTypeError, "Cannot find corresponding type for #{ruby_type || db_type}"
|
70
|
-
}
|
80
|
+
self.class.ruby_type_mapping[ruby_type]
|
71
81
|
end
|
72
82
|
|
73
83
|
# @api private
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ROM
|
2
|
+
module SQL
|
3
|
+
# @api private
|
4
|
+
class Transaction < ::ROM::Transaction
|
5
|
+
attr_reader :connection
|
6
|
+
private :connection
|
7
|
+
|
8
|
+
def initialize(connection)
|
9
|
+
@connection = connection
|
10
|
+
end
|
11
|
+
|
12
|
+
def run(opts = EMPTY_HASH)
|
13
|
+
connection.transaction(opts) { yield(self) }
|
14
|
+
rescue ::ROM::Transaction::Rollback
|
15
|
+
# noop
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/rom/sql/type.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'rom/schema/type'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module SQL
|
5
|
+
class Type < ROM::Schema::Type
|
6
|
+
# Return a new type marked as a FK
|
7
|
+
#
|
8
|
+
# @return [SQL::Type]
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
def foreign_key
|
12
|
+
meta(foreign_key: true)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return a new type marked as qualified
|
16
|
+
#
|
17
|
+
# @return [SQL::Type]
|
18
|
+
#
|
19
|
+
# @api public
|
20
|
+
def qualified
|
21
|
+
meta(qualified: true)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return a new type marked as joined
|
25
|
+
#
|
26
|
+
# @return [SQL::Type]
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
def joined
|
30
|
+
meta(joined: true)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return if an attribute was used in a join
|
34
|
+
#
|
35
|
+
# @return [Boolean]
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def joined?
|
39
|
+
meta[:joined].equal?(true)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return if an attribute type is qualified
|
43
|
+
#
|
44
|
+
# @return [Boolean]
|
45
|
+
#
|
46
|
+
# @api public
|
47
|
+
def qualified?
|
48
|
+
meta[:qualified].equal?(true)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @api public
|
52
|
+
def to_sym
|
53
|
+
@_to_sym ||=
|
54
|
+
if qualified? && aliased?
|
55
|
+
:"#{source.dataset}__#{name}___#{meta[:alias]}"
|
56
|
+
elsif qualified?
|
57
|
+
:"#{source.dataset}__#{name}"
|
58
|
+
elsif aliased?
|
59
|
+
:"#{name}___#{meta[:alias]}"
|
60
|
+
else
|
61
|
+
name
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# @api private
|
66
|
+
def sql_literal(ds)
|
67
|
+
sql_expr.sql_literal(ds)
|
68
|
+
end
|
69
|
+
|
70
|
+
# @api private
|
71
|
+
def sql_expr
|
72
|
+
Sequel.expr(to_sym)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/rom/sql/version.rb
CHANGED
data/rom-sql.gemspec
CHANGED
@@ -18,12 +18,11 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_runtime_dependency 'sequel', '~> 4.
|
21
|
+
spec.add_runtime_dependency 'sequel', '~> 4.42'
|
22
22
|
spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
|
23
23
|
spec.add_runtime_dependency 'dry-types', '~> 0.9'
|
24
|
-
spec.add_runtime_dependency 'dry-core', '~> 0.2'
|
25
|
-
spec.add_runtime_dependency 'rom', '~>
|
26
|
-
spec.add_runtime_dependency 'rom-support', '~> 2.0'
|
24
|
+
spec.add_runtime_dependency 'dry-core', '~> 0.2', '>= 0.2.3'
|
25
|
+
spec.add_runtime_dependency 'rom', '~> 3.0.0.beta'
|
27
26
|
|
28
27
|
spec.add_development_dependency 'bundler'
|
29
28
|
spec.add_development_dependency 'rake', '~> 10.0'
|
@@ -18,10 +18,15 @@ RSpec.describe 'ROM::SQL::Schema::PostgresInferrer', :postgres do
|
|
18
18
|
Decimal :money, null: false
|
19
19
|
column :tags, "text[]"
|
20
20
|
column :tag_ids, "bigint[]"
|
21
|
+
column :ip, "inet"
|
21
22
|
rainbow :color
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
26
|
+
after do
|
27
|
+
conn.drop_table?(:test_inferrence)
|
28
|
+
end
|
29
|
+
|
25
30
|
let(:dataset) { :test_inferrence }
|
26
31
|
|
27
32
|
let(:schema) { container.relations[dataset].schema }
|
@@ -30,19 +35,24 @@ RSpec.describe 'ROM::SQL::Schema::PostgresInferrer', :postgres do
|
|
30
35
|
before do
|
31
36
|
dataset = self.dataset
|
32
37
|
conf.relation(dataset) do
|
33
|
-
schema(dataset, infer: true)
|
38
|
+
schema(dataset, infer: true) do
|
39
|
+
attribute :ip, ROM::SQL::Types::String
|
40
|
+
end
|
34
41
|
end
|
35
42
|
end
|
36
43
|
|
37
44
|
it 'can infer attributes for dataset' do
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
45
|
+
source = container.relations[:test_inferrence].name
|
46
|
+
|
47
|
+
expect(schema.to_h).to eql(
|
48
|
+
id: ROM::SQL::Types::PG::UUID.meta(name: :id, source: source, primary_key: true),
|
49
|
+
json_data: ROM::SQL::Types::PG::JSON.optional.meta(name: :json_data, source: source),
|
50
|
+
jsonb_data: ROM::SQL::Types::PG::JSONB.optional.meta(name: :jsonb_data, source: source),
|
51
|
+
money: ROM::SQL::Types::Decimal.meta(name: :money, source: source),
|
52
|
+
tags: ROM::SQL::Types::PG::Array('text').optional.meta(name: :tags, source: source),
|
53
|
+
tag_ids: ROM::SQL::Types::PG::Array('biging').optional.meta(name: :tag_ids, source: source),
|
54
|
+
color: ROM::SQL::Types::String.enum(*colors).optional.meta(name: :color, source: source),
|
55
|
+
ip: ROM::SQL::Types::String.meta(name: :ip, source: source)
|
46
56
|
)
|
47
57
|
end
|
48
58
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
RSpec.describe ROM::SQL::Association::ManyToMany, '#call' do
|
2
|
+
subject(:assoc) do
|
3
|
+
relations[:users].associations[:puzzles]
|
4
|
+
end
|
5
|
+
|
6
|
+
include_context 'database setup'
|
7
|
+
|
8
|
+
with_adapters do
|
9
|
+
before do
|
10
|
+
conn.create_table(:puzzles) do
|
11
|
+
primary_key :id
|
12
|
+
column :text, String, null: false
|
13
|
+
end
|
14
|
+
|
15
|
+
conn.create_table(:puzzle_solvers) do
|
16
|
+
foreign_key :solver_id, :users, null: false
|
17
|
+
foreign_key :puzzle_id, :puzzles, null: false
|
18
|
+
primary_key [:solver_id, :puzzle_id]
|
19
|
+
end
|
20
|
+
|
21
|
+
conf.relation(:puzzle_solvers) do
|
22
|
+
schema(infer: true) do
|
23
|
+
associations do
|
24
|
+
belongs_to :user, foreign_key: :solver_id
|
25
|
+
belongs_to :puzzle
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
conf.relation(:users) do
|
31
|
+
schema(infer: true) do
|
32
|
+
associations do
|
33
|
+
has_many :puzzle_solvers
|
34
|
+
has_many :puzzles, through: :puzzle_solvers, foreign_key: :solver_id
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
joe_id = relations[:users].insert(name: 'Joe')
|
40
|
+
jane_id = relations[:users].insert(name: 'Jane')
|
41
|
+
|
42
|
+
p1_id = relations[:puzzles].insert(text: 'P1')
|
43
|
+
p2_id = relations[:puzzles].insert(text: 'P2')
|
44
|
+
p3_id = relations[:puzzles].insert(text: 'P3')
|
45
|
+
|
46
|
+
relations[:puzzle_solvers].insert(solver_id: joe_id, puzzle_id: p2_id)
|
47
|
+
relations[:puzzle_solvers].insert(solver_id: jane_id, puzzle_id: p2_id)
|
48
|
+
|
49
|
+
relations[:puzzle_solvers].insert(solver_id: joe_id, puzzle_id: p1_id)
|
50
|
+
relations[:puzzle_solvers].insert(solver_id: jane_id, puzzle_id: p3_id)
|
51
|
+
end
|
52
|
+
|
53
|
+
after do
|
54
|
+
conn.drop_table?(:puzzle_solvers)
|
55
|
+
conn.drop_table?(:puzzles)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'prepares joined relations using custom FK' do
|
59
|
+
relation = assoc.call(relations).order(:puzzles__text)
|
60
|
+
|
61
|
+
expect(relation.schema.map(&:to_sym)).
|
62
|
+
to eql(%i[puzzles__id puzzles__text puzzle_solvers__solver_id])
|
63
|
+
|
64
|
+
expect(relation.to_a).
|
65
|
+
to eql([
|
66
|
+
{ id: 1, solver_id: 1, text: 'P1' },
|
67
|
+
{ id: 2, solver_id: 1, text: 'P2' },
|
68
|
+
{ id: 2, solver_id: 2, text: 'P2' },
|
69
|
+
{ id: 3, solver_id: 2, text: 'P3' }
|
70
|
+
])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|