arlj 0.0.2 → 0.1.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: a58db94fac67a5fa42539b8b1cc30beaf1134207
4
- data.tar.gz: 3ffd40c1670e27ed4e6eefe82b20df628dd7b361
3
+ metadata.gz: 8d66639a751ea47513133e0ce7d6bbde7e3ea305
4
+ data.tar.gz: 7063bd9f2a5e51647a644f0ca0b5de206195866a
5
5
  SHA512:
6
- metadata.gz: caf5c2c6a30881344c5f75074a5a1131263f68614574b28f3790a475008d638cd8745dbdbc3c0f0d7085d4bc863d112b310020b14ba06d3fa3613d133ef803d8
7
- data.tar.gz: 5276383d71fdf062b199cd3370dfac5258fccf7856f5a753e8444dd5a741ab8d64ec19ba2c34b79d005e14d0286502ee4f89108bb5e5d49a159267ab7e2237ff
6
+ metadata.gz: 148bcbd399bec222d45e3aa1abf55d1b71fc97d258237a23ee882f239d4b1f8c838fc950e01a27ed256c8aef1a18c30457a1e4acd86a827e5a59b34b8aa05a4d
7
+ data.tar.gz: c9f80b5128245a37778974f82966802cbbcbc9ae68a5eef7bf30b3b3850cb44c38481ff96648dd922bffff102837f00bce4416afef7603782750d2ce03993dd7
data/README.md CHANGED
@@ -45,30 +45,67 @@ puts Parent.left_joins(:children).group('records.id').select('COUNT(children.id)
45
45
  GROUP BY records.id
46
46
  ```
47
47
 
48
- `left_joins` is purposely low level to be extra chainable.
48
+ `left_joins` is purposely low level for maximum control.
49
49
 
50
- Arlj also adds an aggregation method:
50
+ Arlj has an aggregation method that is higher level and generally easier to use:
51
51
 
52
52
  ```ruby
53
- Parent.left_joins_aggregate(:children, 'COUNT(*)', 'SUM(col)' => 'total').select('children_count', 'total').to_sql
53
+ Parent.left_joins_aggregate(:children, 'COUNT(*)').to_sql
54
+ => SELECT "parents".*
55
+ FROM "parents"
56
+ LEFT OUTER JOIN (SELECT "children"."parent_id"
57
+ , COUNT("children"."id") AS children_count
58
+ FROM "children"
59
+ GROUP BY "children"."parent_id") arlj_aggregate_children
60
+ ON arlj_aggregate_children."parent_id" = "parents"."id"
61
+ ```
62
+
63
+ Supported aggregation functions are `COUNT()`, `SUM()`, `AVG()`, `MIN()`, and `MAX()`.
64
+
65
+ The aggregation column has a default name of `{table}_{function}_{column}` which
66
+ is easily renamed:
67
+
68
+ ```ruby
69
+ Parent.left_joins_aggregate(:children, 'SUM(age)' => 'ekkekkekkekkeptangya').to_sql
70
+ => SELECT "parents".*
71
+ FROM "parents"
72
+ LEFT OUTER JOIN (SELECT "children"."parent_id"
73
+ , SUM("children"."age") AS ekkekkekkekkeptangya
74
+ FROM "children"
75
+ GROUP BY "children"."parent_id") arlj_aggregate_children
76
+ ON arlj_aggregate_children."parent_id" = "parents"."id"
77
+ ```
78
+
79
+ Since Arlj uses a sub-select, you can easily chain additional queries:
80
+
81
+ ```ruby
82
+ Parent.left_joins_aggregate(:children, 'COUNT(*)').select('children_count').to_sql
54
83
  => SELECT children_count
55
- , total
56
84
  FROM "parents"
57
85
  LEFT OUTER JOIN (SELECT "children"."parent_id"
58
86
  , COUNT("children"."id") AS children_count
59
- , SUM("children"."col") AS total
60
87
  FROM "children"
61
88
  GROUP BY "children"."parent_id") arlj_aggregate_children
62
89
  ON arlj_aggregate_children."parent_id" = "parents"."id"
63
90
  ```
64
91
 
65
- `left_joins_aggregate` currently uses a subquery to hide its aggregation. It is
66
- not the most efficient implementation but it does offer a much better chaining
67
- experience than using `group` at the top level.
92
+ Arlj also supports some basic where clauses:
93
+
94
+ ```ruby
95
+ Parent.left_joins_aggregate(:children, 'COUNT(*)', where: {age: 1..5}).to_sql
96
+ => SELECT "parents".*
97
+ FROM "parents"
98
+ LEFT OUTER JOIN (SELECT "children"."parent_id"
99
+ , COUNT("children"."id") AS children_count
100
+ FROM "children"
101
+ WHERE ("children"."age" BETWEEN 1 AND 5)
102
+ GROUP BY "children"."parent_id") arlj_aggregate_children
103
+ ON arlj_aggregate_children."parent_id" = "parents"."id"
104
+ ```
68
105
 
69
106
  If you prefer, you may also use `arlj` and `arlj_aggregate` instead of
70
107
  `left_joins` and `left_joins_aggregate` respectively. To prevent potential
71
- naming conflicts, please use `Arlj::Base`:
108
+ naming conflicts, use `Arlj::Base` instead:
72
109
 
73
110
  ```ruby
74
111
  class Parent < ActiveRecord::Base
@@ -85,10 +122,18 @@ Arlj.memoize!
85
122
 
86
123
  This has not been proven to be faster.
87
124
 
125
+ ## Gotchas
126
+
127
+ * Since `left_joins_aggregate` uses a sub-select for its aggregation, it can
128
+ underperform a better optimized query.
129
+
130
+ * When `left_joins_aggregate` joins zero records, the aggregate column is NULL.
131
+ To operate correctly on these columns, please use `COALESCE(col, 0)`.
132
+
88
133
  ## TODO
89
134
 
90
- * Relations with conditions
91
- * `LEFT JOIN [...] ON`
135
+ * `left_joins(nested: :relations)`
136
+ * `left_joins_aggregate([...], merge: User.active)`
92
137
  * `has_and_belongs_to_many`
93
138
  * `has_many :through =>`
94
139
 
@@ -41,21 +41,26 @@ module Arlj
41
41
  refl = reflect_on_association(assoc)
42
42
  refl_arel = refl.klass.arel_table
43
43
 
44
- join_name = "arlj_aggregate_#{refl.table_name}"
44
+ subq_ar = refl.klass.group(refl_arel[refl.foreign_key])
45
45
 
46
46
  columns = [refl_arel[refl.foreign_key]]
47
- args.each do |thunk|
48
- columns << parse_thunk(refl, assoc, refl_arel, thunk)
47
+ args.each do |arg|
48
+ columns << parse_directive(refl, assoc, refl_arel, arg)
49
49
  end
50
- options.each do |thunk, name|
51
- columns << parse_thunk(refl, assoc, refl_arel, thunk, name)
50
+ options.each do |key, value|
51
+ if directive?(key)
52
+ columns << parse_directive(refl, assoc, refl_arel, key, value)
53
+ elsif key.to_s == 'where'
54
+ subq_ar = subq_ar.send(key, value)
55
+ else
56
+ raise "'#{key.inspect} => #{value.inspect}' not recognized"
57
+ end
52
58
  end
53
59
 
54
- subq_arel =
55
- refl_arel.project(columns).
56
- from(refl_arel).
57
- group(refl_arel[refl.foreign_key]).
58
- as(join_name)
60
+ subq_arel = subq_ar.arel
61
+ subq_arel.projections.clear
62
+ subq_arel = subq_arel.project(columns).
63
+ as("arlj_aggregate_#{refl.table_name}")
59
64
 
60
65
  arlj_left_join_arel(subq_arel, refl.foreign_key)
61
66
  end
@@ -66,7 +71,7 @@ module Arlj
66
71
 
67
72
  private
68
73
 
69
- THUNK_PATTERN = /^([a-zA-Z]*)\((.*)\)$/
74
+ DIRECTIVE_PATTERN = /^([a-zA-Z]*)\((.*)\)$/
70
75
  AGGREGATE_FUNCTIONS = {
71
76
  'sum' => 'sum',
72
77
  'average' => 'average',
@@ -77,10 +82,14 @@ module Arlj
77
82
  'min' => 'minimum',
78
83
  'count' => 'count',
79
84
  }.freeze
80
- def parse_thunk(refl, assoc, arel, thunk, name=nil)
81
- matchdata = THUNK_PATTERN.match(thunk)
85
+ def directive?(check)
86
+ DIRECTIVE_PATTERN =~ check
87
+ end
88
+
89
+ def parse_directive(refl, assoc, arel, directive, name=nil)
90
+ matchdata = DIRECTIVE_PATTERN.match(directive)
82
91
  if matchdata.nil?
83
- raise "'#{thunk}' not parsable - must be of format 'func(column)'"
92
+ raise "'#{directive}' not parsable - must be of format 'func(column)'"
84
93
  end
85
94
 
86
95
  func = AGGREGATE_FUNCTIONS[matchdata[1].downcase]
@@ -98,6 +107,10 @@ module Arlj
98
107
  arel[column].send(func).as(name)
99
108
  end
100
109
 
110
+ def arel_node(value)
111
+ Arel::Nodes::SqlLiteral.new(value)
112
+ end
113
+
101
114
  def arlj_left_join_arel(arel, foreign_key)
102
115
  arel_table.join(arel, Arel::Nodes::OuterJoin).
103
116
  on(arel[foreign_key].eq(arel_table[self.primary_key]))
@@ -1,3 +1,3 @@
1
1
  module Arlj
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -17,14 +17,14 @@ RSpec.describe Arlj::Base do
17
17
  Temping.create :child do
18
18
  with_columns do |t|
19
19
  t.integer :parent_id
20
- t.integer :col
20
+ t.integer :age
21
21
  end
22
22
  end
23
23
 
24
24
  before(:all) do
25
25
  @parent = Parent.create(name: 'John')
26
26
  (1..10).each do |n|
27
- @parent.children.create(col: n)
27
+ @parent.children.create(age: n)
28
28
  end
29
29
 
30
30
  @parent_no_child = Parent.create(name: 'Jane')
@@ -45,25 +45,42 @@ RSpec.describe Arlj::Base do
45
45
  assert{ children_count == @parent.children.size }
46
46
  end
47
47
 
48
- specify 'sum(col)' do
49
- children_sum_col = Parent.arlj_aggregate(:children, 'SUM(col)').pluck('children_sum_col').first
50
- assert{ children_sum_col == @parent.children.sum(:col) }
48
+ specify 'sum(age)' do
49
+ children_sum_age = Parent.arlj_aggregate(:children, 'SUM(age)').pluck('children_sum_age').first
50
+ assert{ children_sum_age == @parent.children.sum(:age) }
51
51
  end
52
52
 
53
- specify 'SUM(col) => name' do
54
- sum = Parent.arlj_aggregate(:children, 'sum(col)' => 'sum').pluck('sum').first
55
- assert{ sum == @parent.children.sum(:col) }
53
+ specify 'SUM(age) => name' do
54
+ sum = Parent.arlj_aggregate(:children, 'sum(age)' => 'sum').pluck('sum').first
55
+ assert{ sum == @parent.children.sum(:age) }
56
56
  end
57
57
 
58
- specify 'FAKE(col) raises error' do
59
- error = rescuing{ Parent.arlj_aggregate(:children, 'FAKE(col)') }
58
+ specify 'FAKE(age) raises error' do
59
+ error = rescuing{ Parent.arlj_aggregate(:children, 'FAKE(age)') }
60
60
  assert{ error }
61
61
  end
62
62
 
63
- specify 'COUNT(*) => count, SUM(col) => sum' do
64
- array = Parent.arlj_aggregate(:children, 'count(*)' => 'count', 'sum(col)' => 'sum').pluck('count', 'sum').first
63
+ specify 'COUNT(*) => count, SUM(age) => sum' do
64
+ array = Parent.arlj_aggregate(:children, 'count(*)' => 'count', 'sum(age)' => 'sum').pluck('count', 'sum').first
65
65
  assert{ array[0] == @parent.children.size }
66
- assert{ array[1] == @parent.children.sum(:col) }
66
+ assert{ array[1] == @parent.children.sum(:age) }
67
+ end
68
+
69
+ context 'where' do
70
+ specify 'COUNT(*), where: "age > 4"' do
71
+ count = Parent.arlj_aggregate(:children, 'COUNT(*)', where: 'age > 4').pluck('children_count').first
72
+ assert{ count == @parent.children.where('age > 4').count }
73
+ end
74
+
75
+ specify 'SUM(age), where: {age: 1..4}' do
76
+ count = Parent.arlj_aggregate(:children, 'SUM(age)', where: {age: 1..4}).pluck('children_sum_age').first
77
+ assert{ count == @parent.children.where(age: 1..4).sum(:age) }
78
+ end
79
+
80
+ specify 'COUNT(age), where: ["age = ?", 1]' do
81
+ count = Parent.arlj_aggregate(:children, 'SUM(age)', where: ['age = ?', 8]).pluck('children_sum_age').first
82
+ assert{ count == 8 }
83
+ end
67
84
  end
68
85
  end
69
86
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arlj
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Feng
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-05 00:00:00.000000000 Z
11
+ date: 2015-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -134,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
134
  version: '0'
135
135
  requirements: []
136
136
  rubyforge_project:
137
- rubygems_version: 2.2.2
137
+ rubygems_version: 2.4.5
138
138
  signing_key:
139
139
  specification_version: 4
140
140
  summary: ActiveRecord Left Join