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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG.md +32 -0
  4. data/Gemfile +4 -1
  5. data/lib/rom/plugins/relation/sql/auto_wrap.rb +1 -3
  6. data/lib/rom/sql/association.rb +33 -14
  7. data/lib/rom/sql/association/many_to_many.rb +17 -10
  8. data/lib/rom/sql/association/many_to_one.rb +29 -13
  9. data/lib/rom/sql/association/name.rb +12 -4
  10. data/lib/rom/sql/association/one_to_many.rb +21 -10
  11. data/lib/rom/sql/commands/create.rb +0 -1
  12. data/lib/rom/sql/commands/update.rb +1 -49
  13. data/lib/rom/sql/dsl.rb +29 -0
  14. data/lib/rom/sql/expression.rb +26 -0
  15. data/lib/rom/sql/function.rb +23 -0
  16. data/lib/rom/sql/gateway.rb +24 -9
  17. data/lib/rom/sql/migration.rb +6 -7
  18. data/lib/rom/sql/migration/migrator.rb +7 -8
  19. data/lib/rom/sql/order_dsl.rb +20 -0
  20. data/lib/rom/sql/plugin/associates.rb +58 -45
  21. data/lib/rom/sql/plugin/pagination.rb +8 -11
  22. data/lib/rom/sql/plugins.rb +0 -2
  23. data/lib/rom/sql/projection_dsl.rb +41 -0
  24. data/lib/rom/sql/qualified_attribute.rb +2 -2
  25. data/lib/rom/sql/relation.rb +35 -67
  26. data/lib/rom/sql/relation/reading.rb +77 -25
  27. data/lib/rom/sql/restriction_dsl.rb +24 -0
  28. data/lib/rom/sql/schema.rb +73 -7
  29. data/lib/rom/sql/schema/associations_dsl.rb +4 -3
  30. data/lib/rom/sql/schema/dsl.rb +5 -2
  31. data/lib/rom/sql/schema/inferrer.rb +21 -11
  32. data/lib/rom/sql/transaction.rb +19 -0
  33. data/lib/rom/sql/type.rb +76 -0
  34. data/lib/rom/sql/version.rb +1 -1
  35. data/rom-sql.gemspec +3 -4
  36. data/spec/extensions/postgres/inferrer_spec.rb +19 -9
  37. data/spec/integration/association/many_to_many/custom_fks_spec.rb +73 -0
  38. data/spec/integration/association/many_to_many/from_view_spec.rb +81 -0
  39. data/spec/integration/association/many_to_many_spec.rb +2 -2
  40. data/spec/integration/association/many_to_one/custom_fks_spec.rb +59 -0
  41. data/spec/integration/association/many_to_one/from_view_spec.rb +74 -0
  42. data/spec/integration/association/many_to_one/self_ref_spec.rb +51 -0
  43. data/spec/integration/association/many_to_one_spec.rb +4 -2
  44. data/spec/integration/association/one_to_many/custom_fks_spec.rb +48 -0
  45. data/spec/integration/association/one_to_many/from_view_spec.rb +57 -0
  46. data/spec/integration/association/one_to_many/self_ref_spec.rb +52 -0
  47. data/spec/integration/association/one_to_many_spec.rb +1 -1
  48. data/spec/integration/association/one_to_one_spec.rb +1 -1
  49. data/spec/integration/association/one_to_one_through_spec.rb +2 -2
  50. data/spec/integration/commands/create_spec.rb +11 -27
  51. data/spec/integration/commands/update_spec.rb +54 -109
  52. data/spec/integration/gateway_spec.rb +31 -17
  53. data/spec/integration/plugins/associates_spec.rb +27 -0
  54. data/spec/integration/plugins/auto_wrap_spec.rb +8 -8
  55. data/spec/integration/schema/call_spec.rb +24 -0
  56. data/spec/integration/schema/prefix_spec.rb +18 -0
  57. data/spec/integration/schema/qualified_spec.rb +18 -0
  58. data/spec/integration/schema/rename_spec.rb +23 -0
  59. data/spec/integration/schema/view_spec.rb +29 -0
  60. data/spec/integration/schema_inference_spec.rb +31 -14
  61. data/spec/spec_helper.rb +2 -2
  62. data/spec/support/helpers.rb +7 -0
  63. data/spec/unit/gateway_spec.rb +5 -4
  64. data/spec/unit/projection_dsl_spec.rb +54 -0
  65. data/spec/unit/relation/dataset_spec.rb +3 -3
  66. data/spec/unit/relation/distinct_spec.rb +8 -7
  67. data/spec/unit/relation/exclude_spec.rb +2 -4
  68. data/spec/unit/relation/having_spec.rb +6 -4
  69. data/spec/unit/relation/inner_join_spec.rb +47 -2
  70. data/spec/unit/relation/invert_spec.rb +2 -3
  71. data/spec/unit/relation/left_join_spec.rb +44 -3
  72. data/spec/unit/relation/order_spec.rb +40 -0
  73. data/spec/unit/relation/prefix_spec.rb +2 -0
  74. data/spec/unit/relation/project_spec.rb +3 -1
  75. data/spec/unit/relation/qualified_columns_spec.rb +2 -0
  76. data/spec/unit/relation/rename_spec.rb +2 -0
  77. data/spec/unit/relation/right_join_spec.rb +59 -0
  78. data/spec/unit/relation/select_append_spec.rb +21 -0
  79. data/spec/unit/relation/select_spec.rb +41 -0
  80. data/spec/unit/relation/where_spec.rb +28 -0
  81. data/spec/unit/restriction_dsl_spec.rb +34 -0
  82. metadata +62 -40
  83. data/lib/rom/plugins/relation/sql/base_view.rb +0 -31
  84. data/lib/rom/sql/header.rb +0 -61
  85. data/lib/rom/sql/plugin/assoc_macros.rb +0 -133
  86. data/lib/rom/sql/plugin/assoc_macros/class_interface.rb +0 -128
  87. data/spec/integration/read_spec.rb +0 -111
  88. data/spec/unit/association_errors_spec.rb +0 -19
  89. data/spec/unit/plugin/assoc_macros/combined_associations_spec.rb +0 -73
  90. data/spec/unit/plugin/assoc_macros/many_to_many_spec.rb +0 -53
  91. data/spec/unit/plugin/assoc_macros/many_to_one_spec.rb +0 -61
  92. data/spec/unit/plugin/assoc_macros/one_to_many_spec.rb +0 -78
  93. 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
@@ -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