flounder 0.15.1 → 0.16.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: 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