rom-sql 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c088db5ced8d4a5297ceb909e77dba86cd80ed37
4
- data.tar.gz: 9d7f52c636a1418b966d7c609f57e766a1a4b224
3
+ metadata.gz: 2f9129b51494f9c6e4e78dd37a3700e103012ee7
4
+ data.tar.gz: 72e8eb116c43a411fb250ee866cdeeb9e5edb0a9
5
5
  SHA512:
6
- metadata.gz: 2603a384db038ddf194df6827b4d29585f48b5db8cd4791117149e292c55d3c0e8ddcec364e516b046fab29a28c32af22e964f4788c4f44082b808239b1127b3
7
- data.tar.gz: 67d0846c7bea76d5c9365d35874a9755aa1c0306cbf09de7ce51b4964c16784c740f2b8713bb632dca0d54ac3ba480fc931d3a477abd602791ed28be4ba81183
6
+ metadata.gz: 468a0785cb4d9d132bcdd344cf0bb72b12ece60b5e118d8ae7a5ac095d1be5adb7eca861ae0bf70167c79bf911c2fca30e4d814cb514c44cfc8e53b421266dcb
7
+ data.tar.gz: f081da3b8fe5f27c6b9c40f80f5b374a230ffd3e30d71b8ce70d82edeac09c176bce328bf8015d50e7f2dd51f88ea54aeb36d7661f90e5f1a6ff23807436b500
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## v0.3.0 2014-12-19
2
+
3
+ ### Changed
4
+
5
+ * `association_join` now uses Sequel's `graph` interface which qualifies columns automatically (solnic)
6
+ * Delete command returns deleted tuples (solnic)
7
+
8
+ [Compare v0.2.0...v0.3.0](https://github.com/rom-rb/rom-sql/compare/v0.2.0...v0.3.0)
9
+
1
10
  ## v0.2.0 2014-12-06
2
11
 
3
12
  ### Added
data/README.md CHANGED
@@ -58,28 +58,14 @@ end
58
58
 
59
59
  ## Relations
60
60
 
61
- ROM doesn't have a relationship concept like in ActiveRecord or Sequel. Instead
62
- it provides a convenient interface for building joined relations that can be
63
- mapped to [aggregate objects](http://martinfowler.com/bliki/Aggregate.html).
64
-
65
- There's no lazy-loading, eager-loading or any other magic happening behind the
66
- scenes. You're in full control of how data are fetched from the database and it's
67
- an explicit operation.
68
-
69
61
  ``` ruby
70
62
 
71
63
  setup.relation(:tasks)
72
64
 
73
65
  setup.relation(:users) do
74
- one_to_many :tasks, key: :user_id
75
-
76
66
  def by_name(name)
77
67
  where(name: name)
78
68
  end
79
-
80
- def with_tasks
81
- association_join(:tasks)
82
- end
83
69
  end
84
70
 
85
71
  rom = setup.finalize
@@ -94,10 +80,19 @@ puts users.by_name("Piotr").with_tasks.to_a.inspect
94
80
  # => [{:id=>1, :name=>"Piotr", :user_id=>1, :title=>"Be happy"}]
95
81
  ```
96
82
 
97
- ## Mapping
83
+ ## Mapping joins to aggregates
84
+
85
+ ROM doesn't have a relationship concept like in ActiveRecord or Sequel. Instead
86
+ it provides a convenient interface for building joined relations that can be
87
+ mapped to [aggregate objects](http://martinfowler.com/bliki/Aggregate.html).
88
+
89
+ There's no lazy-loading, eager-loading or any other magic happening behind the
90
+ scenes. You're in full control of how data are fetched from the database and it's
91
+ an explicit operation.
98
92
 
99
- Mapping joined relations can be simplified using `wrap` and `group` in-memory
100
- operations:
93
+ Sequel's association DSL is available in relation definitions which enables
94
+ `association_join` interface inside relations. To map joined results to
95
+ aggregate objects `wrap` and `group` mapping transformation can be used
101
96
 
102
97
  ``` ruby
103
98
  setup.relation(:tasks)
@@ -110,7 +105,7 @@ setup.relation(:users) do
110
105
  end
111
106
 
112
107
  def with_tasks
113
- in_memory { group(association_join(:tasks), tasks: [:title]) }
108
+ association_join(:tasks, select: [:title])
114
109
  end
115
110
  end
116
111
 
data/lib/rom/sql.rb CHANGED
@@ -9,7 +9,6 @@ end
9
9
 
10
10
  require "rom/sql/version"
11
11
  require "rom/sql/header"
12
- require "rom/sql/relation_extension"
13
12
  require "rom/sql/relation_inclusion"
14
13
  require "rom/sql/adapter"
15
14
 
@@ -41,7 +41,9 @@ module ROM
41
41
  end
42
42
 
43
43
  def extend_relation_instance(relation)
44
- relation.extend(RelationExtension)
44
+ model = relation.model
45
+ model.set_dataset(relation.dataset)
46
+ model.dataset.naked!
45
47
  end
46
48
 
47
49
  def command_namespace
@@ -38,8 +38,9 @@ module ROM
38
38
  class Delete < ROM::Commands::Delete
39
39
 
40
40
  def execute
41
+ deleted = target.to_a
41
42
  target.delete
42
- relation
43
+ deleted
43
44
  end
44
45
 
45
46
  end
@@ -25,6 +25,10 @@ module ROM
25
25
  }
26
26
  end
27
27
 
28
+ def names
29
+ map { |col| :"#{col.to_s.split('___').last}" }
30
+ end
31
+
28
32
  def project(*names)
29
33
  find_all { |col| names.include?(col) }
30
34
  end
@@ -9,6 +9,7 @@ module ROM
9
9
  klass.extend(AssociationDSL)
10
10
 
11
11
  klass.send(:undef_method, :select)
12
+ klass.send(:attr_reader, :model)
12
13
 
13
14
  klass.class_eval {
14
15
  class << self
@@ -42,8 +43,8 @@ module ROM
42
43
  # end
43
44
  #
44
45
  # @api public
45
- def association_join(*args)
46
- send(:append_association, __method__, *args)
46
+ def association_join(name, options = {})
47
+ graph_join(name, :inner, options)
47
48
  end
48
49
 
49
50
  # Join configured association
@@ -63,36 +64,53 @@ module ROM
63
64
  # end
64
65
  #
65
66
  # @api public
66
- def association_left_join(*args)
67
- send(:append_association, __method__, *args)
67
+ def association_left_join(name, options = {})
68
+ graph_join(name, :left_outer, options)
68
69
  end
69
70
 
70
- private
71
-
72
71
  # @api private
73
- def append_association(type, name, options = {})
74
- self.class.new(
75
- dataset.public_send(type, name).
76
- select_append(*columns_for_association(name, options))
77
- )
72
+ def primary_key
73
+ model.primary_key
78
74
  end
79
75
 
80
76
  # @api private
81
- def columns_for_association(name, options)
82
- col_names = options[:select]
83
-
84
- return send(Inflecto.pluralize(name)).qualified_columns unless col_names
85
-
86
- relations = col_names.is_a?(Hash) ? col_names.keys : [name]
87
-
88
- columns = relations.each_with_object([]) do |rel_name, a|
89
- relation = send(Inflecto.pluralize(rel_name))
90
- names = col_names.is_a?(Hash) ? col_names[rel_name] : col_names
91
-
92
- a.concat(relation.select(*names).prefix.qualified_columns)
77
+ def graph_join(name, join_type, options = {})
78
+ assoc = model.association_reflection(name)
79
+
80
+ key = assoc[:key]
81
+ type = assoc[:type]
82
+
83
+ if type == :many_to_many
84
+ select = options[:select] || {}
85
+
86
+ l_select, r_select =
87
+ if select.is_a?(Hash)
88
+ [select[assoc[:join_table]] || [], select[name]]
89
+ else
90
+ [[], select]
91
+ end
92
+
93
+ l_graph = graph(
94
+ assoc[:join_table],
95
+ { assoc[:left_key] => primary_key },
96
+ select: l_select, implicit_qualifier: self.name
97
+ )
98
+
99
+ l_graph.graph(name, { primary_key => assoc[:right_key] }, select: r_select)
100
+ else
101
+ join_keys =
102
+ if type == :many_to_one
103
+ { primary_key => key }
104
+ else
105
+ { key => primary_key }
106
+ end
107
+
108
+ graph(
109
+ name,
110
+ join_keys,
111
+ options.merge(join_type: join_type, implicit_qualifier: self.name)
112
+ )
93
113
  end
94
-
95
- columns
96
114
  end
97
115
 
98
116
  module AssociationDSL
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  module SQL
3
- VERSION = "0.2.0".freeze
3
+ VERSION = "0.3.0".freeze
4
4
  end
5
5
  end
@@ -8,7 +8,7 @@ describe 'Commands / Delete' do
8
8
  before do
9
9
  setup.relation(:users) do
10
10
  def by_name(name)
11
- where(name: 'Piotr')
11
+ where(name: name)
12
12
  end
13
13
  end
14
14
 
@@ -22,12 +22,12 @@ describe 'Commands / Delete' do
22
22
  it 'deletes all tuples' do
23
23
  result = users.try { delete }
24
24
 
25
- expect(result.value.to_a).to match_array([])
25
+ expect(result.value.to_a).to match_array([{ id: 1, name: 'Piotr' }, { id: 2, name: 'Jane' }])
26
26
  end
27
27
 
28
28
  it 'deletes all tuples in a restricted relation' do
29
29
  result = users.try { delete(:by_name, 'Jane').execute }
30
30
 
31
- expect(result.value.to_a).to match_array([{ id: 2, name: 'Jane' }])
31
+ expect(result.value).to match_array([{ id: 2, name: 'Jane' }])
32
32
  end
33
33
  end
@@ -21,11 +21,11 @@ describe 'Defining multiple associations' do
21
21
  right_key: :tag_id
22
22
 
23
23
  def with_user_and_tags
24
- all.with_tags.with_user
24
+ all.with_user.with_tags
25
25
  end
26
26
 
27
27
  def all
28
- select(:id, :title).qualified
28
+ select(:id, :title)
29
29
  end
30
30
 
31
31
  def by_tag(name)
@@ -33,39 +33,41 @@ describe 'Defining multiple associations' do
33
33
  end
34
34
 
35
35
  def with_tags
36
- association_left_join(:tags, select: :name)
36
+ association_left_join(:tags, select: [:name])
37
37
  end
38
38
 
39
39
  def with_user
40
- association_join(:users, select: :name)
40
+ association_join(:users, select: [:name])
41
41
  end
42
42
 
43
- def sorted_by_tag_name
44
- order(Sequel.desc(:tasks__title))
43
+ def sorted_by_tags_name
44
+ order(Sequel.desc(:tags__name))
45
45
  end
46
46
 
47
47
  end
48
48
 
49
- expect(rom.relations.tasks.with_user_and_tags.to_a).to eql([
50
- { id: 1, title: 'Finish ROM', user_name: 'Piotr', tag_name: 'important' },
51
- { id: 2, title: 'Go to sleep', user_name: 'Piotr', tag_name: nil }
49
+ tasks = rom.relations.tasks
50
+
51
+ expect(tasks.with_user_and_tags.to_a).to eql([
52
+ { id: 1, title: 'Finish ROM', name: 'Piotr', tags_name: 'important' },
53
+ { id: 2, title: 'Go to sleep', name: 'Piotr', tags_name: nil }
52
54
  ])
53
55
 
54
- expect(rom.relations.tasks.with_user_and_tags.sorted_by_tag_name.to_a).to eql([
55
- { id: 2, title: 'Go to sleep', user_name: 'Piotr', tag_name: nil },
56
- { id: 1, title: 'Finish ROM', user_name: 'Piotr', tag_name: 'important' }
56
+ expect(tasks.with_user_and_tags.sorted_by_tags_name.to_a).to eql([
57
+ { id: 2, title: 'Go to sleep', name: 'Piotr', tags_name: nil },
58
+ { id: 1, title: 'Finish ROM', name: 'Piotr', tags_name: 'important' }
57
59
  ])
58
60
 
59
- expect(rom.relations.tasks.with_user_and_tags.by_tag('important').to_a).to eql([
60
- { id: 1, title: 'Finish ROM', user_name: 'Piotr', tag_name: 'important' }
61
+ expect(tasks.with_user_and_tags.by_tag('important').to_a).to eql([
62
+ { id: 1, title: 'Finish ROM', name: 'Piotr', tags_name: 'important' }
61
63
  ])
62
64
 
63
- expect(rom.relations.tasks.all.with_user.to_a).to eql([
64
- { id: 1, title: 'Finish ROM', user_name: 'Piotr' },
65
- { id: 2, title: 'Go to sleep', user_name: 'Piotr' }
65
+ expect(tasks.all.with_user.to_a).to eql([
66
+ { id: 1, title: 'Finish ROM', name: 'Piotr' },
67
+ { id: 2, title: 'Go to sleep', name: 'Piotr' }
66
68
  ])
67
69
 
68
- expect(rom.relations.tasks.where(title: 'Go to sleep').to_a).to eql(
70
+ expect(tasks.where(title: 'Go to sleep').to_a).to eql(
69
71
  [{ id: 2, user_id: 1, title: 'Go to sleep'}]
70
72
  )
71
73
  end
@@ -16,11 +16,21 @@ describe 'Defining many-to-one association' do
16
16
  right_key: :tag_id
17
17
 
18
18
  def with_tags
19
- association_left_join(:tags).select(:tasks__id, :tasks__title, :tags__name)
19
+ association_left_join(:tags, select: [:name])
20
+ end
21
+
22
+ def with_tags_and_tag_id
23
+ association_left_join(:tags, select: {
24
+ tags: [:name], task_tags: [:tag_id]
25
+ })
20
26
  end
21
27
 
22
28
  def by_tag(name)
23
- with_tags.where(tags__name: name)
29
+ with_tags.where(name: name)
30
+ end
31
+
32
+ def all
33
+ select(:id, :title)
24
34
  end
25
35
  end
26
36
 
@@ -28,12 +38,17 @@ describe 'Defining many-to-one association' do
28
38
 
29
39
  tasks = rom.relations.tasks
30
40
 
31
- expect(tasks.with_tags.to_a).to eql([
41
+ expect(tasks.all.with_tags.to_a).to eql([
32
42
  { id: 1, title: 'Finish ROM', name: 'important' },
33
43
  { id: 2, title: 'Go to sleep', name: nil }
34
44
  ])
35
45
 
36
- expect(tasks.by_tag("important").to_a).to eql([
46
+ expect(tasks.all.with_tags_and_tag_id.to_a).to eql([
47
+ { id: 1, title: 'Finish ROM', tag_id: 1, name: 'important' },
48
+ { id: 2, title: 'Go to sleep', tag_id: nil, name: nil }
49
+ ])
50
+
51
+ expect(tasks.all.by_tag("important").to_a).to eql([
37
52
  { id: 1, title: 'Finish ROM', name: 'important' }
38
53
  ])
39
54
 
@@ -8,7 +8,7 @@ describe 'Defining many-to-one association' do
8
8
  many_to_one :users, key: :user_id
9
9
 
10
10
  def all
11
- select(:id, :title).rename(title: :task_title).qualified
11
+ select(:id, :title)
12
12
  end
13
13
 
14
14
  def with_user
@@ -16,12 +16,26 @@ describe 'Defining many-to-one association' do
16
16
  end
17
17
  end
18
18
 
19
+ setup.mappers do
20
+ define(:tasks)
21
+
22
+ define(:with_user, parent: :tasks) do
23
+ wrap :user do
24
+ attribute :name
25
+ end
26
+ end
27
+ end
28
+
19
29
  setup.relation(:users)
20
30
 
21
31
  tasks = rom.relations.tasks
22
32
 
23
33
  expect(tasks.all.with_user.to_a).to eql(
24
- [{ id: 1, user_name: 'Piotr', task_title: 'Finish ROM' }]
34
+ [{ id: 1, name: 'Piotr', title: 'Finish ROM' }]
35
+ )
36
+
37
+ expect(rom.read(:tasks).all.with_user.to_a).to eql(
38
+ [{ id: 1, title: 'Finish ROM', user: { name: 'Piotr' } }]
25
39
  )
26
40
  end
27
41
  end
@@ -14,14 +14,30 @@ describe 'Defining one-to-many association' do
14
14
  end
15
15
 
16
16
  def with_tasks
17
- association_join(:tasks)
17
+ association_left_join(:tasks, select: [:id, :title])
18
+ end
19
+
20
+ def all
21
+ select(:id, :name)
22
+ end
23
+ end
24
+
25
+ setup.mappers do
26
+ define(:users)
27
+
28
+ define(:with_tasks, parent: :users) do
29
+ group tasks: [:tasks_id, :title]
18
30
  end
19
31
  end
20
32
 
21
33
  users = rom.relations.users
22
34
 
23
35
  expect(users.with_tasks.by_name("Piotr").to_a).to eql(
24
- [{ id: 1, user_id: 1, name: 'Piotr', title: 'Finish ROM' }]
36
+ [{ id: 1, name: 'Piotr', tasks_id: 1, title: 'Finish ROM' }]
37
+ )
38
+
39
+ expect(rom.read(:users).all.with_tasks.by_name("Piotr").to_a).to eql(
40
+ [{ id: 1, name: 'Piotr', tasks: [{ tasks_id: 1, title: 'Finish ROM' }] }]
25
41
  )
26
42
  end
27
43
  end
@@ -26,7 +26,7 @@ describe 'Inferring schema from database' do
26
26
  rom = setup.finalize
27
27
  schema = rom.schema
28
28
 
29
- expect(schema.postgres).to be(nil)
29
+ expect { schema.postgres }.to raise_error(NoMethodError)
30
30
  end
31
31
  end
32
32
  end
metadata CHANGED
@@ -1,95 +1,95 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rom-sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-06 00:00:00.000000000 Z
11
+ date: 2014-12-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ~>
18
18
  - !ruby/object:Gem::Version
19
19
  version: '4.17'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ~>
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4.17'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: equalizer
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ~>
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0.0'
34
- - - ">="
34
+ - - '>='
35
35
  - !ruby/object:Gem::Version
36
36
  version: 0.0.9
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
- - - "~>"
41
+ - - ~>
42
42
  - !ruby/object:Gem::Version
43
43
  version: '0.0'
44
- - - ">="
44
+ - - '>='
45
45
  - !ruby/object:Gem::Version
46
46
  version: 0.0.9
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rom
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - "~>"
51
+ - - ~>
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0.4'
54
- - - ">="
54
+ - - '>='
55
55
  - !ruby/object:Gem::Version
56
56
  version: 0.4.0
57
57
  type: :runtime
58
58
  prerelease: false
59
59
  version_requirements: !ruby/object:Gem::Requirement
60
60
  requirements:
61
- - - "~>"
61
+ - - ~>
62
62
  - !ruby/object:Gem::Version
63
63
  version: '0.4'
64
- - - ">="
64
+ - - '>='
65
65
  - !ruby/object:Gem::Version
66
66
  version: 0.4.0
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: bundler
69
69
  requirement: !ruby/object:Gem::Requirement
70
70
  requirements:
71
- - - ">="
71
+ - - '>='
72
72
  - !ruby/object:Gem::Version
73
73
  version: '0'
74
74
  type: :development
75
75
  prerelease: false
76
76
  version_requirements: !ruby/object:Gem::Requirement
77
77
  requirements:
78
- - - ">="
78
+ - - '>='
79
79
  - !ruby/object:Gem::Version
80
80
  version: '0'
81
81
  - !ruby/object:Gem::Dependency
82
82
  name: rake
83
83
  requirement: !ruby/object:Gem::Requirement
84
84
  requirements:
85
- - - "~>"
85
+ - - ~>
86
86
  - !ruby/object:Gem::Version
87
87
  version: '10.0'
88
88
  type: :development
89
89
  prerelease: false
90
90
  version_requirements: !ruby/object:Gem::Requirement
91
91
  requirements:
92
- - - "~>"
92
+ - - ~>
93
93
  - !ruby/object:Gem::Version
94
94
  version: '10.0'
95
95
  description: RDBMS support for ROM
@@ -99,9 +99,9 @@ executables: []
99
99
  extensions: []
100
100
  extra_rdoc_files: []
101
101
  files:
102
- - ".gitignore"
103
- - ".rspec"
104
- - ".travis.yml"
102
+ - .gitignore
103
+ - .rspec
104
+ - .travis.yml
105
105
  - CHANGELOG.md
106
106
  - Gemfile
107
107
  - LICENSE.txt
@@ -112,7 +112,6 @@ files:
112
112
  - lib/rom/sql/adapter.rb
113
113
  - lib/rom/sql/commands.rb
114
114
  - lib/rom/sql/header.rb
115
- - lib/rom/sql/relation_extension.rb
116
115
  - lib/rom/sql/relation_inclusion.rb
117
116
  - lib/rom/sql/spec/support.rb
118
117
  - lib/rom/sql/support/active_support_notifications.rb
@@ -147,17 +146,17 @@ require_paths:
147
146
  - lib
148
147
  required_ruby_version: !ruby/object:Gem::Requirement
149
148
  requirements:
150
- - - ">="
149
+ - - '>='
151
150
  - !ruby/object:Gem::Version
152
151
  version: '0'
153
152
  required_rubygems_version: !ruby/object:Gem::Requirement
154
153
  requirements:
155
- - - ">="
154
+ - - '>='
156
155
  - !ruby/object:Gem::Version
157
156
  version: '0'
158
157
  requirements: []
159
158
  rubyforge_project:
160
- rubygems_version: 2.2.2
159
+ rubygems_version: 2.0.14
161
160
  signing_key:
162
161
  specification_version: 4
163
162
  summary: RDBMS support for ROM
@@ -1,15 +0,0 @@
1
- module ROM
2
- module SQL
3
-
4
- module RelationExtension
5
- attr_reader :model
6
-
7
- def self.extended(relation)
8
- relation.model.set_dataset(relation.dataset)
9
- relation.model.dataset.naked!
10
- end
11
-
12
- end
13
-
14
- end
15
- end