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
@@ -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
|