flounder 0.15.1 → 0.16.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f116f6520c4017a543634696a1d98c17cf3208d9
4
- data.tar.gz: 9471a11ee88f27827a3c6ac3ecdeea6218c5b0b2
3
+ metadata.gz: f1df582c917b24ed0beb74e4fdd6b15d0f7792ba
4
+ data.tar.gz: f5ef57ba73986e7ebb89e475289f177baf0c1602
5
5
  SHA512:
6
- metadata.gz: 2746df8286f4a3d5068e482321e556581778fb96acba83d1c89cae63dfa76846f8e7abcbadd43e2d59b6b5edc8a44e144a2b439176e4d8c484eb00e7c20e7f1f
7
- data.tar.gz: 45eb6fa78492db34ed608b1f45a9490a626dda10c4912366fd1dd1420f7f515d5c7bc4902e35c03432470d8ddc7607714c98e1ae188ba017239539fefeea5e51
6
+ metadata.gz: 964433279a3fcd0bb1035dc5062fb075560518ccf411462ad79af281264b1abc41f22b02019d753620cb39caf53583d2e3a56d67be3be39a5560c7f8056bdc1f
7
+ data.tar.gz: 08eded169bd82e3468af1e673fb199f14a0e59352707f068b76f7ed96d5d3af5ac5af7ccd673070bc3124eaf96e75c8713e0c48fcc42cbe02b6e4054beb4f982
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- flounder (0.12.1)
4
+ flounder (0.15.1)
5
5
  aggregate (~> 0.2, >= 0.2.2)
6
6
  arel (~> 5, > 5.0.1)
7
7
  connection_pool (~> 2)
data/HISTORY CHANGED
@@ -1,3 +1,5 @@
1
+ # 0.16
2
+ + DRY joins using #connect - one declaration of relationships, many uses.
1
3
 
2
4
  # 0.15
3
5
  + Condition piece generator - use this to create OR expressions: Entity#cond
data/README CHANGED
@@ -6,10 +6,16 @@ SYNOPSIS
6
6
 
7
7
  require 'flounder'
8
8
  d = Flounder.domain do |dom|
9
- dom.entity(:users, 'users')
9
+ dom.entity(:users, :user, 'users') do |user|
10
+ user.has_many :giraffes, :id => :user_id
11
+ end
12
+ dom.entity(:giraffes, :giraffe, 'all_giraffes') do |giraffe|
13
+ giraffe.belongs_to :user, :user_id => :id
14
+ end
10
15
  end
11
16
 
12
17
  d[:users].where(:id.lt => 10).to_a
18
+ d[:users].connect(:giraffes)
13
19
 
14
20
  STATUS
15
21
 
data/flounder.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "flounder"
5
- s.version = '0.15.1'
5
+ s.version = '0.16.0'
6
6
  s.summary = "Flounder is a way to write SQL simply in Ruby. It deals with everything BUT object relational mapping. "
7
7
  s.email = "kaspar.schiess@technologyastronauts.ch"
8
8
  s.homepage = "https://bitbucket.org/technologyastronauts/oss_flounder"
@@ -1,5 +1,7 @@
1
1
  require 'forwardable'
2
2
 
3
+ require 'flounder/relation'
4
+
3
5
  module Flounder
4
6
 
5
7
  # An entity corresponds to a table in the database. On top of its table
@@ -28,6 +30,7 @@ module Flounder
28
30
  @singular = singular
29
31
  @table_name = table_name
30
32
  @columns_hash = nil
33
+ @relations = {} # name -> relation
31
34
 
32
35
  @table = Arel::Table.new(table_name)
33
36
  end
@@ -49,6 +52,9 @@ module Flounder
49
52
  # @return [String] Name of the table that underlies this entity.
50
53
  attr_reader :table_name
51
54
 
55
+ # @return [Array] relations that this entity has to other entities
56
+ attr_accessor :relations
57
+
52
58
  extend Forwardable
53
59
  def_delegators :domain, :transaction, :with_connection
54
60
 
@@ -66,6 +72,42 @@ module Flounder
66
72
  end
67
73
  alias to_s inspect
68
74
 
75
+ # Relations to other entities
76
+
77
+ # Adds a relationship where many records on the other entity correspond to
78
+ # this one.
79
+ #
80
+ def has_many *a
81
+ add_relationship :has_many, *a
82
+ end
83
+
84
+ # Adds a relationship where one record on the other entity corresponds to
85
+ # possibly many on our side.
86
+ #
87
+ # Example:
88
+ # domain.entity :foos, :foo, 'foo' do |foo|
89
+ # foo.belongs_to :bar, :bar_id => :id
90
+ # end
91
+ #
92
+ # You can also explicitly name the relationship instead of going through
93
+ # the singular name of the other entity:
94
+ #
95
+ # Example:
96
+ # domain.entity :foos, :foo, 'foo' do |foo|
97
+ # foo.belongs_to :bar_stuff, :bar, :bar_id => :id
98
+ # end
99
+ #
100
+ def belongs_to *a
101
+ add_relationship :belongs_to, *a
102
+ end
103
+
104
+ def add_relationship type, name, entity_ref=nil, **join_conditions
105
+ raise ArgumentError, "Must have join conditions." if join_conditions.empty?
106
+
107
+ relations[name] = Relation.new(
108
+ domain, type, name, entity_ref||name, join_conditions)
109
+ end
110
+
69
111
  # Builds a condition part or a general SQL expression.
70
112
  #
71
113
  # entity.cond(a: 1)
@@ -123,7 +165,7 @@ module Flounder
123
165
  # Query initiators
124
166
  [:where, :join, :outer_join, :project,
125
167
  :order_by, :group_by,
126
- :limit, :offset].each do |name|
168
+ :limit, :offset, :connect].each do |name|
127
169
  define_method name do |*args|
128
170
  select { |q| q.send(name, *args) }
129
171
  end
@@ -0,0 +1,18 @@
1
+ module Flounder::Helpers
2
+ module Entity
3
+ def entity_like? something
4
+ something.kind_of?(Flounder::Entity) ||
5
+ something.kind_of?(Symbol) && domain.has_entity?(something)
6
+ end
7
+ def convert_to_entity something
8
+ case something
9
+ when Flounder::Entity, Flounder::EntityAlias
10
+ return something
11
+ when Symbol
12
+ return domain[something]
13
+ else
14
+ fail "Not entity-like - yet! (#{something.inspect})"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,6 +1,9 @@
1
1
 
2
2
  require 'pg_hstore'
3
3
  require 'benchmark'
4
+ require 'set'
5
+
6
+ require 'flounder/helpers/entity'
4
7
 
5
8
  module Flounder::Query
6
9
  class Base
@@ -211,6 +214,8 @@ module Flounder::Query
211
214
  # covers: :field => (1..100)
212
215
  when Range
213
216
  arel_field.in(value)
217
+ when Set
218
+ arel_field.in(value.to_a)
214
219
  else
215
220
  arel_field.send(kind, value)
216
221
  end
@@ -241,19 +246,6 @@ module Flounder::Query
241
246
  end
242
247
  end
243
248
 
244
- def entity_like? something
245
- something.kind_of?(Flounder::Entity) ||
246
- something.kind_of?(Symbol) && domain.has_entity?(something)
247
- end
248
- def convert_to_entity something
249
- case something
250
- when Flounder::Entity, Flounder::EntityAlias
251
- return something
252
- when Symbol
253
- return domain[something]
254
- else
255
- fail "Not entity-like - yet! (#{something.inspect})"
256
- end
257
- end
249
+ include Flounder::Helpers::Entity
258
250
  end
259
251
  end
@@ -153,8 +153,46 @@ module Flounder::Query
153
153
  all.first.count
154
154
  end
155
155
 
156
+ # Follows relationships on the currently anchored entity. This is like
157
+ # an explicit join, but allows to eliminate repetition.
158
+ #
159
+ def connect *connect_spec
160
+ connect_spec.each do |spec_part|
161
+ follow_relation_spec(join_entity, spec_part)
162
+ end
163
+
164
+ self
165
+ end
166
+
156
167
  private
157
168
 
169
+ # Follows a given relationship spec. Does NOT change anchoring.
170
+ #
171
+ def follow_relation_spec entity, spec
172
+ old_join_entity = join_entity
173
+
174
+ case spec
175
+ when Symbol
176
+ relation = entity.relations[spec]
177
+ relation.apply(self)
178
+ when Hash
179
+ spec.each do |k, v|
180
+ follow_relation_spec(join_entity, k)
181
+ self.anchor
182
+
183
+ follow_relation_spec(join_entity, v)
184
+ end
185
+ when Array
186
+ spec.each do |v|
187
+ follow_relation_spec(join_entity, v)
188
+ end
189
+ else
190
+ raise ArgumentError, "#{spec.inspect} not allowed in #connect."
191
+ end
192
+
193
+ @join_entity = old_join_entity
194
+ end
195
+
158
196
  def column_name_to_entity name
159
197
  unless default_projection.empty?
160
198
  extract_source_info_from_name(name)
@@ -0,0 +1,30 @@
1
+ require 'flounder/helpers/entity'
2
+
3
+ module Flounder
4
+ class Relation
5
+ attr_accessor :domain, :type
6
+ attr_accessor :name, :entity_ref, :join_conditions
7
+
8
+ def initialize domain, type, name, entity_ref, join_conditions
9
+ @domain, @type, @name, @entity_ref, @join_conditions =
10
+ domain, type, name, entity_ref, join_conditions
11
+ end
12
+
13
+ def apply query
14
+ entity = convert_to_entity(entity_ref)
15
+
16
+ if name != entity_ref
17
+ # TODO maybe using name for both singular and plural is not a good idea.
18
+ # Maybe it is.
19
+ entity = entity.as(name, name)
20
+ end
21
+
22
+ query.
23
+ join(entity).
24
+ on(join_conditions)
25
+ end
26
+
27
+ private
28
+ include Flounder::Helpers::Entity
29
+ end
30
+ end
@@ -4,9 +4,21 @@ def domain
4
4
  @domain ||= begin
5
5
  connection = Flounder.connect(dbname: 'flounder')
6
6
  Flounder.domain(connection) do |dom|
7
- dom.entity(:users, :user, 'users')
8
- dom.entity(:posts, :post, 'posts')
9
- dom.entity(:comments, :comment, 'comments')
7
+ dom.entity(:users, :user, 'users') do |user|
8
+ user.has_many :posts, :id => :author_id
9
+ user.has_many :approved_posts, :posts, :id => :author_id
10
+ user.has_many :comments, :id => :author_id
11
+ end
12
+ dom.entity(:posts, :post, 'posts') do |post|
13
+ post.belongs_to :user, :author_id => :id
14
+ post.belongs_to :approver, :users, :author_id => :id
15
+
16
+ post.has_many :comments, :id => :post_id
17
+ end
18
+ dom.entity(:comments, :comment, 'comments') do |comment|
19
+ comment.belongs_to :post, :post_id => :id
20
+ comment.belongs_to :author, :author_id => :id
21
+ end
10
22
  end
11
23
  end
12
24
  end
data/qed/expressions.md CHANGED
@@ -21,4 +21,4 @@ These condition pieces can be used in a WHERE-clause directly.
21
21
  ~~~ruby
22
22
  users.where(users.cond(id: 1)).
23
23
  assert generates_sql("SELECT [fields] FROM \"users\" WHERE (\"users\".\"id\" = 1)")
24
- ~~~
24
+ ~~~
data/qed/index.md CHANGED
@@ -72,5 +72,16 @@ By default, symbols are interpreted as field names in the entity that you start
72
72
  assert generates_sql("SELECT [fields] FROM \"users\" WHERE \"posts\".\"id\" = 1")
73
73
  ~~~
74
74
 
75
+ Sets are queried for using the `IN` operator.
76
+
77
+ ~~~ruby
78
+ require 'set'
79
+ s = Set.new([1,2,3])
80
+ users.where(:posts, :id.in => s).
81
+ assert generates_sql("SELECT [fields] FROM \"users\" WHERE \"posts\".\"id\" IN (1, 2, 3)")
82
+ users.where(:posts, :id => s).
83
+ assert generates_sql("SELECT [fields] FROM \"users\" WHERE \"posts\".\"id\" IN (1, 2, 3)")
84
+ ~~~
85
+
75
86
  TODO links to other documentation
76
87
 
data/qed/joins.md CHANGED
@@ -1,4 +1,5 @@
1
1
 
2
+ # Basic JOINs
2
3
  Joins are correctly created.
3
4
 
4
5
  ~~~ruby
@@ -83,3 +84,32 @@ Results should also be correct:
83
84
  row = query.first
84
85
  row.other_comment.assert == row.comment
85
86
  ~~~
87
+
88
+
89
+ # Joining using `#connect`
90
+
91
+ You can also store joins in the entity definition (see `#belongs_to` and `#has_many` on the Entity) and then navigate through your models using those names.
92
+
93
+ A simple example for a connect:
94
+
95
+ ~~~ruby
96
+ # Query for users, joining posts and joining comments from posts (anchor)
97
+ query = users.connect(:posts).where(id: 1)
98
+
99
+ users.
100
+ join(:posts).on(:id => :author_id).
101
+ where(id: 1).to_sql.assert == query.to_sql
102
+ ~~~
103
+
104
+ Or a more complicated example, using hashes and arrays in the join specification.
105
+
106
+ ~~~ruby
107
+ # Query for users, joining posts and joining comments from posts (anchor)
108
+ query = users.connect(:posts => [:comments, :approver])
109
+
110
+ users.
111
+ join(:posts).on(:id => :author_id).anchor.
112
+ join(:comments).on(:id => :post_id).
113
+ join(users.as(:approver, :approver)).on(:author_id => :id).to_sql.assert == query.to_sql
114
+ ~~~
115
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flounder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.1
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaspar Schiess
@@ -9,114 +9,114 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-10-13 00:00:00.000000000 Z
12
+ date: 2014-10-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: arel
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - ~>
18
+ - - "~>"
19
19
  - !ruby/object:Gem::Version
20
20
  version: '5'
21
- - - '>'
21
+ - - ">"
22
22
  - !ruby/object:Gem::Version
23
23
  version: 5.0.1
24
24
  type: :runtime
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
27
27
  requirements:
28
- - - ~>
28
+ - - "~>"
29
29
  - !ruby/object:Gem::Version
30
30
  version: '5'
31
- - - '>'
31
+ - - ">"
32
32
  - !ruby/object:Gem::Version
33
33
  version: 5.0.1
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: pg
36
36
  requirement: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.17'
41
41
  type: :runtime
42
42
  prerelease: false
43
43
  version_requirements: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0.17'
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: hashie
50
50
  requirement: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3'
55
- - - '>='
55
+ - - ">="
56
56
  - !ruby/object:Gem::Version
57
57
  version: '3.2'
58
58
  type: :runtime
59
59
  prerelease: false
60
60
  version_requirements: !ruby/object:Gem::Requirement
61
61
  requirements:
62
- - - ~>
62
+ - - "~>"
63
63
  - !ruby/object:Gem::Version
64
64
  version: '3'
65
- - - '>='
65
+ - - ">="
66
66
  - !ruby/object:Gem::Version
67
67
  version: '3.2'
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: connection_pool
70
70
  requirement: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - ~>
72
+ - - "~>"
73
73
  - !ruby/object:Gem::Version
74
74
  version: '2'
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
- - - ~>
79
+ - - "~>"
80
80
  - !ruby/object:Gem::Version
81
81
  version: '2'
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: pg-hstore
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
- - - ~>
86
+ - - "~>"
87
87
  - !ruby/object:Gem::Version
88
88
  version: '1.2'
89
- - - '>='
89
+ - - ">="
90
90
  - !ruby/object:Gem::Version
91
91
  version: 1.2.0
92
92
  type: :runtime
93
93
  prerelease: false
94
94
  version_requirements: !ruby/object:Gem::Requirement
95
95
  requirements:
96
- - - ~>
96
+ - - "~>"
97
97
  - !ruby/object:Gem::Version
98
98
  version: '1.2'
99
- - - '>='
99
+ - - ">="
100
100
  - !ruby/object:Gem::Version
101
101
  version: 1.2.0
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: aggregate
104
104
  requirement: !ruby/object:Gem::Requirement
105
105
  requirements:
106
- - - ~>
106
+ - - "~>"
107
107
  - !ruby/object:Gem::Version
108
108
  version: '0.2'
109
- - - '>='
109
+ - - ">="
110
110
  - !ruby/object:Gem::Version
111
111
  version: 0.2.2
112
112
  type: :runtime
113
113
  prerelease: false
114
114
  version_requirements: !ruby/object:Gem::Requirement
115
115
  requirements:
116
- - - ~>
116
+ - - "~>"
117
117
  - !ruby/object:Gem::Version
118
118
  version: '0.2'
119
- - - '>='
119
+ - - ">="
120
120
  - !ruby/object:Gem::Version
121
121
  version: 0.2.2
122
122
  description: " Flounder is the missing piece between the database and your Ruby
@@ -127,11 +127,14 @@ executables: []
127
127
  extensions: []
128
128
  extra_rdoc_files: []
129
129
  files:
130
- - flounder.gemspec
131
130
  - Gemfile
132
131
  - Gemfile.lock
133
132
  - HACKING
134
133
  - HISTORY
134
+ - LICENSE
135
+ - README
136
+ - flounder.gemspec
137
+ - lib/flounder.rb
135
138
  - lib/flounder/connection.rb
136
139
  - lib/flounder/connection_pool.rb
137
140
  - lib/flounder/domain.rb
@@ -141,6 +144,7 @@ files:
141
144
  - lib/flounder/exceptions.rb
142
145
  - lib/flounder/expression.rb
143
146
  - lib/flounder/field.rb
147
+ - lib/flounder/helpers/entity.rb
144
148
  - lib/flounder/immediate.rb
145
149
  - lib/flounder/postgres_utils.rb
146
150
  - lib/flounder/query/base.rb
@@ -149,9 +153,8 @@ files:
149
153
  - lib/flounder/query/returning.rb
150
154
  - lib/flounder/query/select.rb
151
155
  - lib/flounder/query/update.rb
156
+ - lib/flounder/relation.rb
152
157
  - lib/flounder/symbol_extensions.rb
153
- - lib/flounder.rb
154
- - LICENSE
155
158
  - qed/applique/ae.rb
156
159
  - qed/applique/flounder.rb
157
160
  - qed/applique/setup_domain.rb
@@ -170,7 +173,6 @@ files:
170
173
  - qed/projection.md
171
174
  - qed/selects.md
172
175
  - qed/updates.md
173
- - README
174
176
  homepage: https://bitbucket.org/technologyastronauts/oss_flounder
175
177
  licenses:
176
178
  - MIT
@@ -181,17 +183,17 @@ require_paths:
181
183
  - lib
182
184
  required_ruby_version: !ruby/object:Gem::Requirement
183
185
  requirements:
184
- - - '>='
186
+ - - ">="
185
187
  - !ruby/object:Gem::Version
186
188
  version: '0'
187
189
  required_rubygems_version: !ruby/object:Gem::Requirement
188
190
  requirements:
189
- - - '>='
191
+ - - ">="
190
192
  - !ruby/object:Gem::Version
191
193
  version: '0'
192
194
  requirements: []
193
195
  rubyforge_project:
194
- rubygems_version: 2.0.14
196
+ rubygems_version: 2.2.2
195
197
  signing_key:
196
198
  specification_version: 4
197
199
  summary: Flounder is a way to write SQL simply in Ruby. It deals with everything BUT