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