rom-sql 1.2.2 → 1.3.0
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 +9 -5
- data/CHANGELOG.md +30 -0
- data/lib/rom/plugins/relation/sql/auto_wrap.rb +2 -2
- data/lib/rom/sql/association/many_to_many.rb +12 -0
- data/lib/rom/sql/attribute.rb +99 -10
- data/lib/rom/sql/extensions/postgres/commands.rb +5 -2
- data/lib/rom/sql/extensions/postgres/types.rb +160 -0
- data/lib/rom/sql/gateway.rb +1 -9
- data/lib/rom/sql/migration.rb +91 -34
- data/lib/rom/sql/plugin/associates.rb +4 -8
- data/lib/rom/sql/relation.rb +10 -8
- data/lib/rom/sql/relation/reading.rb +21 -2
- data/lib/rom/sql/schema.rb +1 -1
- data/lib/rom/sql/schema/inferrer.rb +3 -1
- data/lib/rom/sql/tasks/migration_tasks.rake +17 -1
- data/lib/rom/sql/version.rb +1 -1
- data/rom-sql.gemspec +2 -2
- data/spec/extensions/postgres/attribute_spec.rb +128 -0
- data/spec/extensions/postgres/integration_spec.rb +21 -0
- data/spec/integration/commands/update_spec.rb +1 -1
- data/spec/integration/migration_spec.rb +41 -10
- data/spec/integration/plugins/auto_wrap_spec.rb +52 -5
- data/spec/integration/schema/inferrer_spec.rb +36 -5
- data/spec/shared/users.rb +1 -1
- data/spec/shared/users_and_tasks.rb +1 -1
- data/spec/spec_helper.rb +10 -4
- data/spec/unit/attribute_spec.rb +84 -4
- data/spec/unit/migration_tasks_spec.rb +12 -1
- data/spec/unit/order_dsl_spec.rb +8 -0
- data/spec/unit/plugin/associates_spec.rb +99 -0
- data/spec/unit/relation/by_pk_spec.rb +8 -0
- data/spec/unit/relation/exist_predicate_spec.rb +25 -0
- metadata +19 -7
@@ -17,6 +17,7 @@ RSpec.describe 'PostgreSQL extension', :postgres do
|
|
17
17
|
|
18
18
|
conf.commands(:people) do
|
19
19
|
define(:create)
|
20
|
+
define(:update)
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
@@ -35,4 +36,24 @@ RSpec.describe 'PostgreSQL extension', :postgres do
|
|
35
36
|
expect(people_relation.to_a).to eq([id: 1, name: 'John Doe', tags: []])
|
36
37
|
end
|
37
38
|
end
|
39
|
+
|
40
|
+
describe 'using retrurning' do
|
41
|
+
let(:create_person) { commands[:people].create }
|
42
|
+
let(:update_person) { commands[:people].update }
|
43
|
+
let(:composite_relation) { people_relation >> -> r { r.to_a.map { |x| x.fetch(:name).upcase } } }
|
44
|
+
|
45
|
+
context 'with pipeline' do
|
46
|
+
it 'works with create' do
|
47
|
+
mapped_people = create_person.new(composite_relation).call(name: 'John Doe', tags: ['foo'])
|
48
|
+
expect(mapped_people).to eql(['JOHN DOE'])
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'works with update' do
|
52
|
+
create_person.call(name: 'John Doe', tags: ['foo'])
|
53
|
+
|
54
|
+
mapped_people = update_person.new(composite_relation).call(name: 'Jane Doe')
|
55
|
+
expect(mapped_people).to eql(['JANE DOE'])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
38
59
|
end
|
@@ -1,24 +1,55 @@
|
|
1
|
-
RSpec.describe ROM::SQL, '.migration'
|
1
|
+
RSpec.describe ROM::SQL, '.migration' do
|
2
2
|
include_context 'database setup'
|
3
3
|
|
4
4
|
before do
|
5
5
|
inferrable_relations.concat %i(dragons schema_migrations)
|
6
6
|
end
|
7
7
|
|
8
|
-
|
8
|
+
with_adapters do
|
9
|
+
before { conf }
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
it 'creates a migration for a specific gateway' do
|
12
|
+
migration = ROM::SQL.migration(container) do
|
13
|
+
change do
|
14
|
+
create_table :dragons do
|
15
|
+
primary_key :id
|
16
|
+
column :name, String
|
17
|
+
end
|
16
18
|
end
|
17
19
|
end
|
20
|
+
|
21
|
+
migration.apply(conn, :up)
|
22
|
+
|
23
|
+
expect(conn.table_exists?(:dragons)).to be(true)
|
18
24
|
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'with non-default gateway' do
|
28
|
+
with_adapters(:postgres) do
|
29
|
+
let(:conf) do
|
30
|
+
ROM::Configuration.new(
|
31
|
+
default: [:sql, conn, inferrable_relations: %i(schema_migrations)],
|
32
|
+
in_memory: [:sql, DB_URIS[:sqlite], inferrable_relations: %i(schema_migrations)]
|
33
|
+
)
|
34
|
+
end
|
19
35
|
|
20
|
-
|
36
|
+
let(:in_memory_connection) { container.gateways[:in_memory].connection }
|
21
37
|
|
22
|
-
|
38
|
+
it 'creates a migration for a specific gateway' do
|
39
|
+
in_memory_migration = ROM::SQL.migration(container, :in_memory) do
|
40
|
+
change do
|
41
|
+
create_table :turtles do
|
42
|
+
primary_key :id
|
43
|
+
column :name, String
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
in_memory_migration.apply(in_memory_connection, :up)
|
49
|
+
|
50
|
+
expect(in_memory_connection.table_exists?(:dragons)).to be(false)
|
51
|
+
expect(in_memory_connection.table_exists?(:turtles)).to be(true)
|
52
|
+
end
|
53
|
+
end
|
23
54
|
end
|
24
55
|
end
|
@@ -49,16 +49,63 @@ RSpec.describe 'Plugins / :auto_wrap' do
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
|
-
context 'using association' do
|
52
|
+
context 'using association with inferred relation name' do
|
53
53
|
before do
|
54
|
-
conf.relation(:tasks)
|
55
|
-
schema(infer: true)
|
56
|
-
|
54
|
+
conf.relation(:tasks) do
|
55
|
+
schema(infer: true) do
|
56
|
+
associations do
|
57
|
+
belongs_to :user
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
57
61
|
|
58
|
-
conf.relation(:users)
|
62
|
+
conf.relation(:users) do
|
63
|
+
schema(infer: true)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
include_context 'joined tuple' do
|
68
|
+
let(:name) { :user }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'using association with an alias' do
|
73
|
+
before do
|
74
|
+
conf.relation(:tasks) do
|
75
|
+
schema(infer: true) do
|
76
|
+
associations do
|
77
|
+
belongs_to :users, as: :assignee
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
conf.relation(:users) do
|
83
|
+
schema(infer: true)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
include_context 'joined tuple' do
|
88
|
+
let(:name) { :assignee }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'using association with an aliased relation' do
|
93
|
+
before do
|
94
|
+
conf.relation(:tasks) do
|
95
|
+
schema(infer: true) do
|
96
|
+
associations do
|
97
|
+
belongs_to :users, as: :assignee, relation: :people
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
conf.relation(:people) do
|
103
|
+
schema(:users, infer: true)
|
104
|
+
end
|
59
105
|
end
|
60
106
|
|
61
107
|
include_context 'joined tuple' do
|
108
|
+
let(:users) { relations[:people] }
|
62
109
|
let(:name) { :assignee }
|
63
110
|
end
|
64
111
|
end
|
@@ -2,7 +2,7 @@ RSpec.describe 'Schema inference for common datatypes', seeds: false do
|
|
2
2
|
include_context 'users and tasks'
|
3
3
|
|
4
4
|
before do
|
5
|
-
inferrable_relations.concat %i(test_inferrence test_numeric)
|
5
|
+
inferrable_relations.concat %i(test_characters test_inferrence test_numeric)
|
6
6
|
end
|
7
7
|
|
8
8
|
let(:schema) { container.relations[dataset].schema }
|
@@ -29,7 +29,7 @@ RSpec.describe 'Schema inference for common datatypes', seeds: false do
|
|
29
29
|
expect(schema.to_h).
|
30
30
|
to eql(
|
31
31
|
id: ROM::SQL::Types::Serial.meta(name: :id, source: source),
|
32
|
-
name: ROM::SQL::Types::String.meta(name: :name, source: source)
|
32
|
+
name: ROM::SQL::Types::String.meta(name: :name, limit: 255, source: source)
|
33
33
|
)
|
34
34
|
end
|
35
35
|
end
|
@@ -48,7 +48,7 @@ RSpec.describe 'Schema inference for common datatypes', seeds: false do
|
|
48
48
|
expect(schema.to_h).
|
49
49
|
to eql(
|
50
50
|
id: ROM::SQL::Types::Serial.meta(name: :id, source: source),
|
51
|
-
title: ROM::SQL::Types::String.optional.meta(name: :title, source: source),
|
51
|
+
title: ROM::SQL::Types::String.meta(limit: 255).optional.meta(name: :title, source: source),
|
52
52
|
user_id: ROM::SQL::Types::Int.optional.meta(
|
53
53
|
name: :user_id,
|
54
54
|
foreign_key: true,
|
@@ -66,7 +66,7 @@ RSpec.describe 'Schema inference for common datatypes', seeds: false do
|
|
66
66
|
|
67
67
|
conn.create_table :test_inferrence do
|
68
68
|
primary_key :id
|
69
|
-
String :text, null: false
|
69
|
+
String :text, text: false, null: false
|
70
70
|
Time :time
|
71
71
|
Date :date
|
72
72
|
|
@@ -97,7 +97,7 @@ RSpec.describe 'Schema inference for common datatypes', seeds: false do
|
|
97
97
|
expect(schema.to_h).
|
98
98
|
to eql(
|
99
99
|
id: ROM::SQL::Types::Serial.meta(name: :id, source: source),
|
100
|
-
text: ROM::SQL::Types::String.meta(name: :text, source: source),
|
100
|
+
text: ROM::SQL::Types::String.meta(name: :text, limit: 255, source: source),
|
101
101
|
time: ROM::SQL::Types::Time.optional.meta(name: :time, source: source),
|
102
102
|
date: date_type.optional.meta(name: :date, source: source),
|
103
103
|
datetime: ROM::SQL::Types::Time.meta(name: :datetime, source: source),
|
@@ -106,6 +106,37 @@ RSpec.describe 'Schema inference for common datatypes', seeds: false do
|
|
106
106
|
end
|
107
107
|
end
|
108
108
|
|
109
|
+
context 'character datatypes' do
|
110
|
+
before do
|
111
|
+
conn.create_table :test_characters do
|
112
|
+
String :text1, text: false, null: false
|
113
|
+
String :text2, size: 100, null: false
|
114
|
+
column :text3, 'char(100)', null: false
|
115
|
+
column :text4, 'varchar', null: false
|
116
|
+
column :text5, 'varchar(100)', null: false
|
117
|
+
String :text6, size: 100
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
let(:dataset) { :test_characters }
|
122
|
+
let(:source) { ROM::Relation::Name[dataset] }
|
123
|
+
|
124
|
+
let(:char_t) { ROM::SQL::Types::String.meta(source: source) }
|
125
|
+
|
126
|
+
it 'infers attributes with limit' do
|
127
|
+
expect(schema.to_h).to eql(
|
128
|
+
text1: char_t.meta(name: :text1, limit: 255),
|
129
|
+
text2: char_t.meta(name: :text2, limit: 100),
|
130
|
+
text3: char_t.meta(name: :text3, limit: 100),
|
131
|
+
text4: char_t.meta(name: :text4, limit: 255),
|
132
|
+
text5: char_t.meta(name: :text5, limit: 100),
|
133
|
+
text6: ROM::SQL::Types::String.meta(limit: 100).optional.meta(
|
134
|
+
name: :text6, source: source
|
135
|
+
)
|
136
|
+
)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
109
140
|
context 'numeric datatypes' do
|
110
141
|
before do
|
111
142
|
conn.create_table :test_numeric do
|
data/spec/shared/users.rb
CHANGED
@@ -17,7 +17,7 @@ RSpec.shared_context 'users and tasks' do
|
|
17
17
|
conn.create_table :tasks do
|
18
18
|
primary_key :id
|
19
19
|
foreign_key :user_id, :users
|
20
|
-
String :title
|
20
|
+
String :title, text: false
|
21
21
|
constraint(:title_length) { char_length(title) > 1 } if ctx.postgres?(example)
|
22
22
|
constraint(:title_length) { length(title) > 1 } if ctx.sqlite?(example)
|
23
23
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
require 'bundler'
|
2
2
|
Bundler.setup
|
3
3
|
|
4
|
-
if RUBY_ENGINE == 'ruby' && ENV['
|
5
|
-
require '
|
6
|
-
|
7
|
-
|
4
|
+
if RUBY_ENGINE == 'ruby' && ENV['COVERAGE'] == 'true'
|
5
|
+
require 'yaml'
|
6
|
+
rubies = YAML.load(File.read(File.join(__dir__, '..', '.travis.yml')))['rvm']
|
7
|
+
latest_mri = rubies.select { |v| v =~ /\A\d+\.\d+.\d+\z/ }.max
|
8
|
+
|
9
|
+
if RUBY_VERSION == latest_mri
|
10
|
+
require 'simplecov'
|
11
|
+
SimpleCov.start do
|
12
|
+
add_filter '/spec/'
|
13
|
+
end
|
8
14
|
end
|
9
15
|
end
|
10
16
|
|
data/spec/unit/attribute_spec.rb
CHANGED
@@ -6,12 +6,92 @@ RSpec.describe ROM::SQL::Attribute, :postgres do
|
|
6
6
|
let(:ds) { users.dataset }
|
7
7
|
|
8
8
|
describe '#is' do
|
9
|
-
|
10
|
-
|
9
|
+
context 'with a standard value' do
|
10
|
+
it 'returns a boolean expression' do
|
11
|
+
expect(users[:id].is(1).sql_literal(ds)).to eql('("id" = 1)')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'returns a boolean equality expression for qualified attribute' do
|
15
|
+
expect((users[:id].qualified.is(1)).sql_literal(ds)).to eql('("users"."id" = 1)')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'with a nil value' do
|
20
|
+
it 'returns an IS NULL expression' do
|
21
|
+
expect(users[:id].is(nil).sql_literal(ds)).to eql('("id" IS NULL)')
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns an IS NULL expression for qualified attribute' do
|
25
|
+
expect((users[:id].qualified.is(nil)).sql_literal(ds)).to eql('("users"."id" IS NULL)')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with a boolean true' do
|
30
|
+
it 'returns an IS TRUE expression' do
|
31
|
+
expect(users[:id].is(true).sql_literal(ds)).to eql('("id" IS TRUE)')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns an IS TRUE expression for qualified attribute' do
|
35
|
+
expect((users[:id].qualified.is(true)).sql_literal(ds)).to eql('("users"."id" IS TRUE)')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'with a boolean false' do
|
40
|
+
it 'returns an IS FALSE expression' do
|
41
|
+
expect(users[:id].is(false).sql_literal(ds)).to eql('("id" IS FALSE)')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'returns an IS FALSE expression for qualified attribute' do
|
45
|
+
expect((users[:id].qualified.is(false)).sql_literal(ds)).to eql('("users"."id" IS FALSE)')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#not' do
|
51
|
+
context 'with a standard value' do
|
52
|
+
it 'returns a negated boolean equality expression' do
|
53
|
+
expect(users[:id].not(1).sql_literal(ds)).to eql('("id" != 1)')
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'returns a negated boolean equality expression for qualified attribute' do
|
57
|
+
expect((users[:id].qualified.not(1)).sql_literal(ds)).to eql('("users"."id" != 1)')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'with a nil value' do
|
62
|
+
it 'returns an IS NOT NULL expression' do
|
63
|
+
expect(users[:id].not(nil).sql_literal(ds)).to eql('("id" IS NOT NULL)')
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'returns an IS NOT NULL expression for qualified attribute' do
|
67
|
+
expect((users[:id].qualified.not(nil)).sql_literal(ds)).to eql('("users"."id" IS NOT NULL)')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'with a boolean true' do
|
72
|
+
it 'returns an IS NOT TRUE expression' do
|
73
|
+
expect(users[:id].not(true).sql_literal(ds)).to eql('("id" IS NOT TRUE)')
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'returns an IS NOT TRUE expression for qualified attribute' do
|
77
|
+
expect((users[:id].qualified.not(true)).sql_literal(ds)).to eql('("users"."id" IS NOT TRUE)')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'with a boolean false' do
|
82
|
+
it 'returns an IS NOT FALSE expression' do
|
83
|
+
expect(users[:id].not(false).sql_literal(ds)).to eql('("id" IS NOT FALSE)')
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'returns an IS NOT FALSE expression for qualified attribute' do
|
87
|
+
expect((users[:id].qualified.not(false)).sql_literal(ds)).to eql('("users"."id" IS NOT FALSE)')
|
88
|
+
end
|
11
89
|
end
|
90
|
+
end
|
12
91
|
|
13
|
-
|
14
|
-
|
92
|
+
describe '#!' do
|
93
|
+
it 'returns a new attribute with negated sql expr' do
|
94
|
+
expect((!users[:id].is(1)).sql_literal(ds)).to eql('("id" != 1)')
|
15
95
|
end
|
16
96
|
end
|
17
97
|
|
@@ -12,7 +12,9 @@ RSpec.describe 'MigrationTasks', :postgres, skip_tables: true do
|
|
12
12
|
let(:migrator) { container.gateways[:default].migrator }
|
13
13
|
|
14
14
|
before do
|
15
|
-
|
15
|
+
ROM::SQL::Gateway.instance = nil
|
16
|
+
ROM::SQL::RakeSupport.env = nil
|
17
|
+
conf
|
16
18
|
end
|
17
19
|
|
18
20
|
context 'db:reset' do
|
@@ -46,6 +48,15 @@ RSpec.describe 'MigrationTasks', :postgres, skip_tables: true do
|
|
46
48
|
}.to output("<= db:migrate executed\n").to_stdout
|
47
49
|
end
|
48
50
|
end
|
51
|
+
|
52
|
+
it 'raises an error on missing both env and Gateway.instance' do
|
53
|
+
ROM::SQL::RakeSupport.env = nil
|
54
|
+
ROM::SQL::Gateway.instance = nil
|
55
|
+
|
56
|
+
expect {
|
57
|
+
Rake::Task["db:migrate"].execute
|
58
|
+
}.to raise_error(ROM::SQL::RakeSupport::MissingEnv)
|
59
|
+
end
|
49
60
|
end
|
50
61
|
|
51
62
|
context 'db:clean' do
|
data/spec/unit/order_dsl_spec.rb
CHANGED
@@ -31,5 +31,13 @@ RSpec.describe ROM::SQL::OrderDSL, :postgres, helpers: true do
|
|
31
31
|
expect(dsl.call { nullif(id.qualified, `''`).desc }.first.sql_literal(conn[:users])).
|
32
32
|
to eql(%(NULLIF("users"."id", '') DESC))
|
33
33
|
end
|
34
|
+
|
35
|
+
it 'allows to set nulls first/last' do
|
36
|
+
expect(dsl.call { id.desc(nulls: :first) }.first.sql_literal(conn[:users])).
|
37
|
+
to eql(%("id" DESC NULLS FIRST))
|
38
|
+
|
39
|
+
expect(dsl.call { id.desc(nulls: :last) }.first.sql_literal(conn[:users])).
|
40
|
+
to eql(%("id" DESC NULLS LAST))
|
41
|
+
end
|
34
42
|
end
|
35
43
|
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'rom/sql/commands'
|
3
|
+
|
4
|
+
RSpec.describe ROM::SQL::Plugin::Associates do
|
5
|
+
subject(:command) do
|
6
|
+
command_class.build(posts).with_association(:tags)
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:posts) do
|
10
|
+
instance_double(Class.new(ROM::SQL::Relation), schema?: false, associations: associations)
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:tags) do
|
14
|
+
instance_double(ROM::SQL::Relation, associations: associations)
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:join_relation) do
|
18
|
+
instance_double(ROM::SQL::Relation)
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:registry) do
|
22
|
+
Hash.new { |h, k| h.fetch(k.to_sym) }.update(posts: posts, tags: tags)
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:command_class) do
|
26
|
+
Class.new(ROM::SQL::Commands::Create) do
|
27
|
+
use :associates, tags: []
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
let(:associations) do
|
32
|
+
Hash.new { |h, k| h.fetch(k.to_sym) }.update(posts: posts_assoc)
|
33
|
+
end
|
34
|
+
|
35
|
+
let(:tags_assoc) do
|
36
|
+
ROM::SQL::Association::ManyToMany.new(:posts, :tags, through: :posts_tags)
|
37
|
+
end
|
38
|
+
|
39
|
+
let(:posts_assoc) do
|
40
|
+
ROM::SQL::Association::ManyToMany.new(:tags, :posts, through: :posts_tags)
|
41
|
+
end
|
42
|
+
|
43
|
+
before do
|
44
|
+
allow(posts).to receive(:__registry__).and_return(registry)
|
45
|
+
allow(associations).to receive(:try).and_yield(tags_assoc)
|
46
|
+
allow(tags_assoc).to receive(:join_keys).and_return({})
|
47
|
+
end
|
48
|
+
|
49
|
+
shared_context 'associates result' do
|
50
|
+
it 'inserts join tuples and returns child tuples with combine keys' do
|
51
|
+
expect(tags_assoc).to receive(:persist).with(registry, post_tuples, tag_tuples)
|
52
|
+
expect(tags_assoc).to receive(:parent_combine_keys).with(registry).and_return(%i[name tag])
|
53
|
+
|
54
|
+
result = command.associate(post_tuples, tag_tuples, assoc: tags_assoc, keys: {})
|
55
|
+
|
56
|
+
expect(result).
|
57
|
+
to match_array([
|
58
|
+
{ title: 'post 1', tag: 'red' }, { title: 'post 1', tag: 'green'},
|
59
|
+
{ title: 'post 2', tag: 'red' }, { title: 'post 2', tag: 'green'}
|
60
|
+
])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#associate' do
|
65
|
+
context 'with plain hash tuples' do
|
66
|
+
let(:post_tuples) do
|
67
|
+
[{ title: 'post 1' }, { title: 'post 2' }]
|
68
|
+
end
|
69
|
+
|
70
|
+
let(:tag_tuples) do
|
71
|
+
[{ name: 'red' }, { name: 'green' }]
|
72
|
+
end
|
73
|
+
|
74
|
+
include_context 'associates result'
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'with tuples coercible to a hash' do
|
78
|
+
before do
|
79
|
+
module Test
|
80
|
+
class Post < OpenStruct
|
81
|
+
def to_hash
|
82
|
+
{ title: title }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
let(:post_tuples) do
|
89
|
+
[Test::Post.new(title: 'post 1'), Test::Post.new(title: 'post 2')]
|
90
|
+
end
|
91
|
+
|
92
|
+
let(:tag_tuples) do
|
93
|
+
[{ name: 'red' }, { name: 'green' }]
|
94
|
+
end
|
95
|
+
|
96
|
+
include_context 'associates result'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|