rom-sql 0.9.1 → 1.0.0.beta1
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 +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
@@ -1,31 +0,0 @@
|
|
1
|
-
module ROM
|
2
|
-
module Plugins
|
3
|
-
module Relation
|
4
|
-
module SQL
|
5
|
-
module BaseView
|
6
|
-
# @api private
|
7
|
-
def self.included(klass)
|
8
|
-
super
|
9
|
-
klass.extend(ClassInterface)
|
10
|
-
end
|
11
|
-
|
12
|
-
module ClassInterface
|
13
|
-
def inherited(klass)
|
14
|
-
super
|
15
|
-
klass.view(:base) do
|
16
|
-
header { dataset.columns }
|
17
|
-
relation { select(*attributes(:base)).order(primary_key) }
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
ROM.plugins do
|
28
|
-
adapter :sql do
|
29
|
-
register :base_view, ROM::Plugins::Relation::SQL::BaseView, type: :relation
|
30
|
-
end
|
31
|
-
end
|
data/lib/rom/sql/header.rb
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
module ROM
|
2
|
-
module SQL
|
3
|
-
# @private
|
4
|
-
class Header
|
5
|
-
include Dry::Equalizer(:columns, :table)
|
6
|
-
|
7
|
-
SEP_REGEX = /_{2,3}/.freeze
|
8
|
-
|
9
|
-
attr_reader :columns, :table
|
10
|
-
|
11
|
-
def initialize(columns, table)
|
12
|
-
@columns = columns
|
13
|
-
@table = table
|
14
|
-
end
|
15
|
-
|
16
|
-
def to_ary
|
17
|
-
columns
|
18
|
-
end
|
19
|
-
alias_method :to_a, :to_ary
|
20
|
-
|
21
|
-
def to_h
|
22
|
-
columns.each_with_object({}) do |col, h|
|
23
|
-
left, right = col.to_s.split('___')
|
24
|
-
h[left.to_sym] = (right || left).to_sym
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def names
|
29
|
-
columns.map { |col| :"#{col.to_s.split(SEP_REGEX).last}" }
|
30
|
-
end
|
31
|
-
|
32
|
-
def exclude(*names)
|
33
|
-
self.class.new(columns.find_all { |col| !names.include?(col) }, table)
|
34
|
-
end
|
35
|
-
|
36
|
-
def project(*names)
|
37
|
-
self.class.new(columns.find_all { |col| names.include?(col) }, table)
|
38
|
-
end
|
39
|
-
|
40
|
-
def qualified
|
41
|
-
self.class.new(columns.map { |col| :"#{table}__#{col}" }, table)
|
42
|
-
end
|
43
|
-
|
44
|
-
def rename(options)
|
45
|
-
self.class.new(columns.map { |col|
|
46
|
-
new_name = options[col]
|
47
|
-
|
48
|
-
if new_name
|
49
|
-
:"#{col}___#{new_name}"
|
50
|
-
else
|
51
|
-
col
|
52
|
-
end
|
53
|
-
}, table)
|
54
|
-
end
|
55
|
-
|
56
|
-
def prefix(col_prefix)
|
57
|
-
rename(Hash[columns.map { |col| [col, :"#{col_prefix}_#{col}"] }])
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
@@ -1,133 +0,0 @@
|
|
1
|
-
require 'rom/sql/plugin/assoc_macros/class_interface'
|
2
|
-
|
3
|
-
module ROM
|
4
|
-
module SQL
|
5
|
-
module Plugin
|
6
|
-
module AssocMacros
|
7
|
-
# Extends a relation class with assoc-macros and instance-level methods
|
8
|
-
#
|
9
|
-
# @api private
|
10
|
-
def self.included(relation)
|
11
|
-
super
|
12
|
-
relation.extend(ClassInterface)
|
13
|
-
end
|
14
|
-
|
15
|
-
# @api private
|
16
|
-
def model
|
17
|
-
self.class.model
|
18
|
-
end
|
19
|
-
|
20
|
-
# Join configured association.
|
21
|
-
#
|
22
|
-
# Uses INNER JOIN type.
|
23
|
-
#
|
24
|
-
# @example
|
25
|
-
#
|
26
|
-
# setup.relation(:tasks)
|
27
|
-
#
|
28
|
-
# setup.relations(:users) do
|
29
|
-
# one_to_many :tasks, key: :user_id
|
30
|
-
#
|
31
|
-
# def with_tasks
|
32
|
-
# association_join(:tasks, select: [:title])
|
33
|
-
# end
|
34
|
-
# end
|
35
|
-
#
|
36
|
-
# @api public
|
37
|
-
def association_join(name, options = {})
|
38
|
-
graph_join(name, :inner, options)
|
39
|
-
end
|
40
|
-
|
41
|
-
# Join configured association
|
42
|
-
#
|
43
|
-
# Uses LEFT JOIN type.
|
44
|
-
#
|
45
|
-
# @example
|
46
|
-
#
|
47
|
-
# setup.relation(:tasks)
|
48
|
-
#
|
49
|
-
# setup.relations(:users) do
|
50
|
-
# one_to_many :tasks, key: :user_id
|
51
|
-
#
|
52
|
-
# def with_tasks
|
53
|
-
# association_left_join(:tasks, select: [:title])
|
54
|
-
# end
|
55
|
-
# end
|
56
|
-
#
|
57
|
-
# @api public
|
58
|
-
def association_left_join(name, options = {})
|
59
|
-
graph_join(name, :left_outer, options)
|
60
|
-
end
|
61
|
-
|
62
|
-
# @api private
|
63
|
-
def graph_join(assoc_name, join_type, options = {})
|
64
|
-
assoc = model.association_reflection(assoc_name)
|
65
|
-
|
66
|
-
if assoc.nil?
|
67
|
-
raise NoAssociationError,
|
68
|
-
"Association #{assoc_name.inspect} has not been " \
|
69
|
-
"defined for relation #{name.relation.inspect}"
|
70
|
-
end
|
71
|
-
|
72
|
-
type = assoc[:type]
|
73
|
-
table_name = assoc[:class].table_name
|
74
|
-
|
75
|
-
graph_rel =
|
76
|
-
if type == :many_to_many
|
77
|
-
select = options[:select] || {}
|
78
|
-
graph_join_many_to_many(table_name, assoc, select)
|
79
|
-
else
|
80
|
-
graph_join_other(table_name, assoc, type, join_type, options)
|
81
|
-
end
|
82
|
-
|
83
|
-
graph_rel = graph_rel.where(assoc[:conditions]) if assoc[:conditions]
|
84
|
-
|
85
|
-
graph_rel
|
86
|
-
end
|
87
|
-
|
88
|
-
# @api private
|
89
|
-
def graph(*args)
|
90
|
-
__new__(dataset.__send__(__method__, *args))
|
91
|
-
end
|
92
|
-
|
93
|
-
private
|
94
|
-
|
95
|
-
def graph_join_many_to_many(name, assoc, select)
|
96
|
-
l_select, r_select =
|
97
|
-
if select.is_a?(Hash)
|
98
|
-
[select[assoc[:join_table]] || [], select[name]]
|
99
|
-
else
|
100
|
-
[[], select]
|
101
|
-
end
|
102
|
-
|
103
|
-
l_graph = graph(
|
104
|
-
assoc[:join_table],
|
105
|
-
{ assoc[:left_key] => primary_key },
|
106
|
-
select: l_select, implicit_qualifier: self.name.dataset
|
107
|
-
)
|
108
|
-
|
109
|
-
l_graph.graph(
|
110
|
-
name, { primary_key => assoc[:right_key] }, select: r_select
|
111
|
-
)
|
112
|
-
end
|
113
|
-
|
114
|
-
def graph_join_other(name, assoc, type, join_type, options)
|
115
|
-
key = assoc[:key]
|
116
|
-
on_conditions = assoc[:on] || {}
|
117
|
-
|
118
|
-
join_keys =
|
119
|
-
if type == :many_to_one
|
120
|
-
{ assoc[:class].primary_key => key }
|
121
|
-
else
|
122
|
-
{ key => primary_key }
|
123
|
-
end.merge(on_conditions)
|
124
|
-
|
125
|
-
graph(
|
126
|
-
name, join_keys,
|
127
|
-
options.merge(join_type: join_type, implicit_qualifier: self.name.dataset)
|
128
|
-
)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
@@ -1,128 +0,0 @@
|
|
1
|
-
module ROM
|
2
|
-
module SQL
|
3
|
-
module Plugin
|
4
|
-
module AssocMacros
|
5
|
-
# Class DSL for SQL relations
|
6
|
-
#
|
7
|
-
# @api private
|
8
|
-
module ClassInterface
|
9
|
-
# @api private
|
10
|
-
def self.prepare(klass)
|
11
|
-
klass.class_eval do
|
12
|
-
class << self
|
13
|
-
attr_reader :model, :associations
|
14
|
-
end
|
15
|
-
end
|
16
|
-
klass.instance_variable_set('@model', Class.new(Sequel::Model))
|
17
|
-
klass.instance_variable_set('@associations', [])
|
18
|
-
end
|
19
|
-
|
20
|
-
# @api private
|
21
|
-
def self.extended(klass)
|
22
|
-
prepare(klass)
|
23
|
-
end
|
24
|
-
|
25
|
-
# Set up model and association ivars for descendant class
|
26
|
-
#
|
27
|
-
# @api private
|
28
|
-
def inherited(klass)
|
29
|
-
super
|
30
|
-
ClassInterface.prepare(klass)
|
31
|
-
end
|
32
|
-
|
33
|
-
# Set up a one-to-many association
|
34
|
-
#
|
35
|
-
# @example
|
36
|
-
# class Users < ROM::Relation[:sql]
|
37
|
-
# one_to_many :tasks, key: :user_id
|
38
|
-
#
|
39
|
-
# def with_tasks
|
40
|
-
# association_join(:tasks)
|
41
|
-
# end
|
42
|
-
# end
|
43
|
-
#
|
44
|
-
# @param [Symbol] name The name of the association
|
45
|
-
# @param [Hash] options The options hash
|
46
|
-
# @option options [Symbol] :key Name of the key to join on
|
47
|
-
# @option options [Hash] :on Additional conditions for join
|
48
|
-
# @option options [Hash] :conditions Additional conditions for WHERE
|
49
|
-
#
|
50
|
-
# @api public
|
51
|
-
def one_to_many(name, options)
|
52
|
-
associations << [__method__, name, { relation: name }.merge(options)]
|
53
|
-
end
|
54
|
-
|
55
|
-
# Set up a many-to-many association
|
56
|
-
#
|
57
|
-
# @example
|
58
|
-
# class Tasks < ROM::Relation[:sql]
|
59
|
-
# many_to_many :tags,
|
60
|
-
# join_table: :task_tags,
|
61
|
-
# left_key: :task_id,
|
62
|
-
# right_key: :tag_id,
|
63
|
-
#
|
64
|
-
# def with_tags
|
65
|
-
# association_join(:tags)
|
66
|
-
# end
|
67
|
-
# end
|
68
|
-
#
|
69
|
-
# @param [Symbol] name The name of the association
|
70
|
-
# @param [Hash] options The options hash
|
71
|
-
# @option options [Symbol] :join_table Name of the join table
|
72
|
-
# @option options [Hash] :left_key Name of the left join key
|
73
|
-
# @option options [Hash] :right_key Name of the right join key
|
74
|
-
# @option options [Hash] :on Additional conditions for join
|
75
|
-
# @option options [Hash] :conditions Additional conditions for WHERE
|
76
|
-
#
|
77
|
-
# @api public
|
78
|
-
def many_to_many(name, options = {})
|
79
|
-
associations << [__method__, name, { relation: name }.merge(options)]
|
80
|
-
end
|
81
|
-
|
82
|
-
# Set up a many-to-one association
|
83
|
-
#
|
84
|
-
# @example
|
85
|
-
# class Tasks < ROM::Relation[:sql]
|
86
|
-
# many_to_one :users, key: :user_id
|
87
|
-
#
|
88
|
-
# def with_users
|
89
|
-
# association_join(:users)
|
90
|
-
# end
|
91
|
-
# end
|
92
|
-
#
|
93
|
-
# @param [Symbol] name The name of the association
|
94
|
-
# @param [Hash] options The options hash
|
95
|
-
# @option options [Symbol] :join_table Name of the join table
|
96
|
-
# @option options [Hash] :key Name of the join key
|
97
|
-
# @option options [Hash] :on Additional conditions for join
|
98
|
-
# @option options [Hash] :conditions Additional conditions for WHERE
|
99
|
-
#
|
100
|
-
# @api public
|
101
|
-
def many_to_one(name, options = {})
|
102
|
-
associations << [__method__, name, { relation: name }.merge(options)]
|
103
|
-
end
|
104
|
-
|
105
|
-
# Finalize the relation by setting up its associations (if any)
|
106
|
-
#
|
107
|
-
# @api private
|
108
|
-
def finalize(relations, relation)
|
109
|
-
return unless relation.dataset.db.table_exists?(dataset)
|
110
|
-
|
111
|
-
model.set_dataset(relation.dataset)
|
112
|
-
model.dataset.naked!
|
113
|
-
|
114
|
-
associations.each do |*args, assoc_opts|
|
115
|
-
options = Hash[assoc_opts]
|
116
|
-
other = relations[options.delete(:relation) || args[1]].model
|
117
|
-
model.public_send(*args, options.merge(class: other))
|
118
|
-
end
|
119
|
-
|
120
|
-
model.freeze
|
121
|
-
|
122
|
-
super
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
@@ -1,111 +0,0 @@
|
|
1
|
-
require 'dry-struct'
|
2
|
-
|
3
|
-
RSpec.describe 'Reading relations using custom mappers' do
|
4
|
-
include_context 'users and tasks'
|
5
|
-
|
6
|
-
with_adapters do
|
7
|
-
before :each do
|
8
|
-
module Test
|
9
|
-
class Goal < Dry::Struct
|
10
|
-
attribute :id, Types::Strict::Int
|
11
|
-
attribute :title, Types::Strict::String
|
12
|
-
end
|
13
|
-
|
14
|
-
class User < Dry::Struct
|
15
|
-
attribute :id, Types::Strict::Int
|
16
|
-
attribute :name, Types::Strict::String
|
17
|
-
attribute :goals, Types::Strict::Array.member(Goal)
|
18
|
-
end
|
19
|
-
|
20
|
-
class UserGoalCount < Dry::Struct
|
21
|
-
attribute :id, Types::Strict::Int
|
22
|
-
attribute :name, Types::Strict::String
|
23
|
-
attribute :goal_count, Types::Strict::Int
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
conf.relation(:goals) do
|
28
|
-
use :assoc_macros
|
29
|
-
|
30
|
-
register_as :goals
|
31
|
-
dataset :tasks
|
32
|
-
end
|
33
|
-
|
34
|
-
conf.relation(:users) do
|
35
|
-
use :assoc_macros
|
36
|
-
|
37
|
-
one_to_many :goals, key: :user_id
|
38
|
-
|
39
|
-
def by_name(name)
|
40
|
-
where(name: name)
|
41
|
-
end
|
42
|
-
|
43
|
-
def with_goals
|
44
|
-
association_left_join(:goals, select: [:id, :title])
|
45
|
-
end
|
46
|
-
|
47
|
-
def all
|
48
|
-
select(:id, :name)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
conf.relation(:user_goal_counts) do
|
53
|
-
use :assoc_macros
|
54
|
-
|
55
|
-
dataset :users
|
56
|
-
register_as :user_goal_counts
|
57
|
-
one_to_many :goals, key: :user_id
|
58
|
-
|
59
|
-
def all
|
60
|
-
with_goals.select_group(:users__id, :users__name).select_append {
|
61
|
-
count(:tasks).as(:goal_count)
|
62
|
-
}
|
63
|
-
end
|
64
|
-
|
65
|
-
def with_goals
|
66
|
-
association_left_join(:goals, select: [:id, :title])
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
conf.mappers do
|
71
|
-
define(:users) do
|
72
|
-
model Test::User
|
73
|
-
|
74
|
-
group :goals do
|
75
|
-
model Test::Goal
|
76
|
-
|
77
|
-
attribute :id, from: :tasks_id
|
78
|
-
attribute :title
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
define(:user_goal_counts) do
|
83
|
-
model Test::UserGoalCount
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
it 'loads domain objects' do
|
89
|
-
user = container.relation(:users).as(:users).with_goals.by_name('Jane').to_a.first
|
90
|
-
|
91
|
-
expect(user).to eql(
|
92
|
-
Test::User.new(
|
93
|
-
id: 1, name: 'Jane', goals: [Test::Goal.new(id: 2, title: "Jane's task")]
|
94
|
-
))
|
95
|
-
end
|
96
|
-
|
97
|
-
# FIXME: on mysql and sqlite
|
98
|
-
if metadata[:postgres]
|
99
|
-
it 'works with grouping and aggregates' do
|
100
|
-
container.relations[:goals].insert(id: 3, user_id: 1, title: 'Get Milk')
|
101
|
-
|
102
|
-
users_with_goal_count = container.relation(:user_goal_counts).as(:user_goal_counts).all
|
103
|
-
|
104
|
-
expect(users_with_goal_count.to_a).to eq([
|
105
|
-
Test::UserGoalCount.new(id: 1, name: "Jane", goal_count: 2),
|
106
|
-
Test::UserGoalCount.new(id: 2, name: "Joe", goal_count: 1)
|
107
|
-
])
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|