sleek 0.0.1 → 0.0.2

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.
Files changed (38) hide show
  1. data/README.md +95 -20
  2. data/lib/sleek.rb +4 -2
  3. data/lib/sleek/core_ext/range.rb +6 -7
  4. data/lib/sleek/event.rb +4 -1
  5. data/lib/sleek/filter.rb +11 -0
  6. data/lib/sleek/group_by_criteria.rb +144 -0
  7. data/lib/sleek/interval.rb +21 -20
  8. data/lib/sleek/{base.rb → namespace.rb} +13 -8
  9. data/lib/sleek/queries.rb +0 -1
  10. data/lib/sleek/queries/average.rb +1 -1
  11. data/lib/sleek/queries/count_unique.rb +1 -1
  12. data/lib/sleek/queries/maximum.rb +1 -1
  13. data/lib/sleek/queries/minimum.rb +1 -1
  14. data/lib/sleek/queries/query.rb +70 -37
  15. data/lib/sleek/queries/sum.rb +1 -1
  16. data/lib/sleek/query_collection.rb +3 -3
  17. data/lib/sleek/query_command.rb +71 -0
  18. data/lib/sleek/timeframe.rb +45 -52
  19. data/lib/sleek/version.rb +1 -1
  20. data/spec/lib/sleek/event_spec.rb +3 -3
  21. data/spec/lib/sleek/filter_spec.rb +3 -3
  22. data/spec/lib/sleek/group_by_criteria_spec.rb +139 -0
  23. data/spec/lib/sleek/interval_spec.rb +6 -5
  24. data/spec/lib/sleek/{base_spec.rb → namespace_spec.rb} +15 -8
  25. data/spec/lib/sleek/queries/average_spec.rb +1 -1
  26. data/spec/lib/sleek/queries/count_spec.rb +1 -1
  27. data/spec/lib/sleek/queries/count_unique_spec.rb +1 -1
  28. data/spec/lib/sleek/queries/maximum_spec.rb +1 -1
  29. data/spec/lib/sleek/queries/minimum_spec.rb +1 -1
  30. data/spec/lib/sleek/queries/query_spec.rb +58 -84
  31. data/spec/lib/sleek/queries/sum_spec.rb +1 -1
  32. data/spec/lib/sleek/query_collection_spec.rb +11 -9
  33. data/spec/lib/sleek/query_command_spec.rb +171 -0
  34. data/spec/lib/sleek/timeframe_spec.rb +70 -36
  35. data/spec/lib/sleek_spec.rb +2 -2
  36. metadata +11 -8
  37. data/lib/sleek/queries/targetable.rb +0 -13
  38. data/spec/lib/sleek/queries/targetable_spec.rb +0 -29
@@ -1,3 +1,3 @@
1
1
  module Sleek
2
- VERSION = "0.0.1"
2
+ VERSION = '0.0.2'
3
3
  end
@@ -3,18 +3,18 @@ require 'spec_helper'
3
3
  describe Sleek::Event do
4
4
  describe ".create_with_namespace" do
5
5
  it "creates event record" do
6
- expect { Sleek::Event.create_with_namespace(:default, "signups", { name: 'John Doe', email: 'john@doe.com' }) }.to change { Sleek::Event.count }.by(1)
6
+ expect { described_class.create_with_namespace(:default, "signups", { name: 'John Doe', email: 'john@doe.com' }) }.to change { Sleek::Event.count }.by(1)
7
7
  end
8
8
 
9
9
  it "sets namespace and bucket" do
10
- evt = Sleek::Event.create_with_namespace(:default, "signups", {})
10
+ evt = described_class.create_with_namespace(:default, "signups", {})
11
11
  expect(evt.namespace).to eq :default
12
12
  expect(evt.bucket).to eq "signups"
13
13
  end
14
14
 
15
15
  it "sets the data" do
16
16
  data = { name: 'John Doe', email: 'j@d.com' }
17
- evt = Sleek::Event.create_with_namespace(:ns, "buc", data)
17
+ evt = described_class.create_with_namespace(:ns, "buc", data)
18
18
  expect(evt.data).to eq data
19
19
  end
20
20
  end
@@ -4,20 +4,20 @@ describe Sleek::Filter do
4
4
  describe "#initialize" do
5
5
  context "when valid operator is passed" do
6
6
  it "does not raise an exception" do
7
- expect { Sleek::Filter.new(:test, :eq, 1) }.to_not raise_exception ArgumentError
7
+ expect { described_class.new(:test, :eq, 1) }.to_not raise_exception ArgumentError
8
8
  end
9
9
  end
10
10
 
11
11
  context "when invalid operator is passed" do
12
12
  it "raises an exception" do
13
- expect { Sleek::Filter.new(:test, :lol, 1) }.to raise_exception ArgumentError, "unsupported operator - lol"
13
+ expect { described_class.new(:test, :lol, 1) }.to raise_exception ArgumentError, "unsupported operator - lol"
14
14
  end
15
15
  end
16
16
  end
17
17
 
18
18
  describe "#apply" do
19
19
  it "appleis the filter to the criteria" do
20
- filter = Sleek::Filter.new(:test, :eq, 1)
20
+ filter = described_class.new(:test, :eq, 1)
21
21
  criteria = stub('criteria')
22
22
  criteria.should_receive(:eq).with("d.test" => 1)
23
23
  filter.apply(criteria)
@@ -0,0 +1,139 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sleek::GroupByCriteria do
4
+ let(:collection) { mock('collection', aggregate: []) }
5
+ let(:criteria) { mock('criteria', collection: collection) }
6
+ let(:group_by) { "d.field" }
7
+ let(:db_group) { "$d.field" }
8
+ subject(:crit) { described_class.new(criteria, group_by) }
9
+
10
+ describe "#aggregates" do
11
+ it "makes up the pipeline" do
12
+ crit.should_receive(:aggregates_pipeline).with(:some_field, false)
13
+
14
+ crit.aggregates(:some_field, false)
15
+ end
16
+
17
+ it "aggregates on the collection" do
18
+ pipeline = double('pipeline')
19
+ result_a = double('result_a')
20
+ result = double('result', to_a: result_a)
21
+ crit.stub(:aggregates_pipeline).and_return(pipeline)
22
+ collection.should_receive(:aggregate).with(pipeline).and_return(result)
23
+
24
+ expect(crit.aggregates(:some_field, false)).to eq result_a
25
+ end
26
+ end
27
+
28
+ describe "#aggregates_prop" do
29
+ it "aggregates on the collection" do
30
+ crit.should_receive(:aggregates).with(:some_field, false).and_return([])
31
+ crit.aggregates_prop(:some_field, "count", false)
32
+ end
33
+
34
+ it "maps the result" do
35
+ crit.stub(:aggregates).and_return([{"_id" => :a, "count" => 1}, {"_id" => :b, "count" => 4}])
36
+ expect(crit.aggregates_prop(:some_field, "count", false)).to eq({ a: 1, b: 4 })
37
+ end
38
+ end
39
+
40
+ describe "#aggregates_pipeline" do
41
+ let(:new_criteria) { stub('new_criteria') }
42
+ let(:selector) { stub('selector') }
43
+
44
+ context "when not aggregating on specific field" do
45
+ let(:pipeline) { crit.aggregates_pipeline }
46
+
47
+ before do
48
+ criteria.should_receive(:ne).with(group_by => nil).and_return(new_criteria)
49
+ new_criteria.should_receive(:selector).and_return(selector)
50
+ end
51
+
52
+ it "adds group_by_field != nil to criteria and matches the criteria" do
53
+ expect(pipeline.first).to eq "$match" => selector
54
+ end
55
+
56
+ it "groups with count" do
57
+ expect(pipeline.last).to eq "$group" => { "_id" => db_group, "count" => { "$sum" => 1 } }
58
+ end
59
+
60
+ it "has only two operators" do
61
+ expect(pipeline).to have(2).items
62
+ end
63
+ end
64
+
65
+ context "when aggregating on specific field" do
66
+ let(:field) { "d.property" }
67
+ let(:db_field) { "$d.property" }
68
+ let(:new_criteria2) { stub('new_criteria2') }
69
+
70
+ before do
71
+ criteria.should_receive(:ne).with(field => nil).and_return(new_criteria)
72
+ new_criteria.should_receive(:ne).with(group_by => nil).and_return(new_criteria2)
73
+ new_criteria2.should_receive(:selector).and_return(selector)
74
+ end
75
+
76
+ context "when not including unique counter" do
77
+ let(:pipeline) { crit.aggregates_pipeline(field) }
78
+
79
+ it "adds group_by_field != nil and field != nil to criteria and matches the criteria" do
80
+ expect(pipeline.first).to eq "$match" => selector
81
+ end
82
+
83
+ it "groups with count, min, max, sum, avg" do
84
+ expect(pipeline.last).to eq "$group" => {
85
+ "_id" => db_group,
86
+ "count" => { "$sum" => 1 },
87
+ "max" => { "$max" => db_field },
88
+ "min" => { "$min" => db_field },
89
+ "sum" => { "$sum" => db_field },
90
+ "avg" => { "$avg" => db_field }
91
+ }
92
+ end
93
+
94
+ it "has 2 operators" do
95
+ expect(pipeline).to have(2).items
96
+ end
97
+ end
98
+
99
+ context "when including unique counter" do
100
+ let(:pipeline) { crit.aggregates_pipeline(field, true) }
101
+
102
+ it "adds group_by_field != nil and field != nil to criteria and matches the criteria" do
103
+ expect(pipeline.first).to eq "$match" => selector
104
+ end
105
+
106
+ it "groups with count, min, max, sum, avg" do
107
+ expect(pipeline[1]).to eq "$group" => {
108
+ "_id" => db_group,
109
+ "count" => { "$sum" => 1 },
110
+ "max" => { "$max" => db_field },
111
+ "min" => { "$min" => db_field },
112
+ "sum" => { "$sum" => db_field },
113
+ "avg" => { "$avg" => db_field },
114
+ "unique_set" => { "$addToSet" => db_field }
115
+ }
116
+ end
117
+
118
+ it "unwinds the set of unique values" do
119
+ expect(pipeline[2]).to eq "$unwind" => "$unique_set"
120
+ end
121
+
122
+ it "groups aggregates back and counts unique values" do
123
+ expect(pipeline.last).to eq "$group" => {
124
+ "_id" => "$_id",
125
+ "count_unique" => { "$sum" => 1 },
126
+ "count" => { "$first" => "count" },
127
+ "max" => { "$first" => "max" },
128
+ "min" => { "$first" => "min" },
129
+ "avg" => { "$first" => "avg" }
130
+ }
131
+ end
132
+
133
+ it "has 4 operators" do
134
+ expect(pipeline).to have(4).items
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -3,20 +3,21 @@ require 'spec_helper'
3
3
  describe Sleek::Interval do
4
4
  describe "#initialize" do
5
5
  it "transforms interval description into value" do
6
- Sleek::Interval.should_receive(:interval_value).with(:hourly)
7
- Sleek::Interval.new(:hourly, Sleek::Timeframe.new(Time.now.all_day))
6
+ described_class.should_receive(:interval_value).with(:hourly)
7
+ described_class.new(:hourly, Time.now.all_day)
8
8
  end
9
9
  end
10
10
 
11
11
  describe "#timeframes" do
12
12
  it "slices timeframe into interval-long timeframes" do
13
- bd = Time.now.beginning_of_day
14
- interval = Sleek::Interval.new(:hourly, Sleek::Timeframe.new(Time.now.all_day))
13
+ now = ActiveSupport::TimeZone.new('UTC').now
14
+ bd = now.beginning_of_day
15
+ interval = described_class.new(:hourly, now.all_day)
15
16
  expect(interval.timeframes.count).to eq 23
16
17
 
17
18
  23.times do |i|
18
19
  subtf = interval.timeframes[i]
19
- expect(subtf.start).to eq bd + 1.hour * i
20
+ expect(subtf.begin).to eq bd + 1.hour * i
20
21
  expect(subtf.end).to eq bd + 1.hour * (i + 1)
21
22
  end
22
23
  end
@@ -1,30 +1,37 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Sleek::Base do
4
- subject(:sleek) { Sleek::Base.new(:default) }
3
+ describe Sleek::Namespace do
4
+ subject(:sleek) { described_class.new(:default) }
5
5
 
6
6
  describe "#initialize" do
7
7
  it "sets the namespace" do
8
- sleek = Sleek::Base.new(:my_namespace)
9
- expect(sleek.namespace).to eq :my_namespace
8
+ sleek = described_class.new(:my_namespace)
9
+ expect(sleek.name).to eq :my_namespace
10
10
  end
11
11
  end
12
12
 
13
13
  describe "#record" do
14
14
  it "creates an event record" do
15
15
  data = { name: 'John Doe', email: 'j@d.com' }
16
-
17
16
  Sleek::Event.should_receive(:create_with_namespace).with(:default, "signups", data)
18
-
19
17
  sleek.record("signups", data)
20
18
  end
21
19
  end
22
20
 
23
21
  describe "#queries" do
24
22
  it "returns QueryCollection for current namespace" do
25
- Sleek::QueryCollection.should_receive(:new).with(:default).and_call_original
23
+ Sleek::QueryCollection.should_receive(:new).with(sleek).and_call_original
26
24
  qc = sleek.queries
27
- expect(qc.namespace).to eq :default
25
+ expect(qc.namespace).to eq sleek
26
+ end
27
+ end
28
+
29
+ describe "#delete!" do
30
+ it "deletes everything from the namespace" do
31
+ events = stub('events')
32
+ sleek.should_receive(:events).and_return(events)
33
+ events.should_receive(:delete_all)
34
+ sleek.delete!
28
35
  end
29
36
  end
30
37
 
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Sleek::Queries::Average do
4
- subject(:query) { Sleek::Queries::Average.new(:default, :purchases, target_property: "total") }
4
+ subject(:query) { described_class.new(:default, :purchases, target_property: "total") }
5
5
 
6
6
  describe "#perform" do
7
7
  it "counts the events" do
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Sleek::Queries::Count do
4
- subject(:query) { Sleek::Queries::Count.new(:default, :purchases) }
4
+ subject(:query) { described_class.new(:default, :purchases) }
5
5
 
6
6
  describe "#perform" do
7
7
  it "counts the events" do
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Sleek::Queries::CountUnique do
4
- subject(:query) { Sleek::Queries::CountUnique.new(:default, :purchases, target_property: "customer.id") }
4
+ subject(:query) { described_class.new(:default, :purchases, target_property: "customer.id") }
5
5
 
6
6
  describe "#perform" do
7
7
  it "counts the events" do
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Sleek::Queries::Maximum do
4
- subject(:query) { Sleek::Queries::Maximum.new(:default, :purchases, target_property: "total") }
4
+ subject(:query) { described_class.new(:default, :purchases, target_property: "total") }
5
5
 
6
6
  describe "#perform" do
7
7
  it "counts the events" do
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Sleek::Queries::Minimum do
4
- subject(:query) { Sleek::Queries::Minimum.new(:default, :purchases, target_property: "total") }
4
+ subject(:query) { described_class.new(:default, :purchases, target_property: "total") }
5
5
 
6
6
  describe "#perform" do
7
7
  it "counts the events" do
@@ -1,13 +1,22 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Sleek::Queries::Query do
4
- let(:query_class) { Sleek::Queries::Query }
5
- subject(:query) { query_class.new(:default, :purchases) }
4
+ let(:query_class) { described_class }
5
+ let(:namespace) { mock('namespace', name: :default) }
6
+ subject(:query) { query_class.new(namespace, :purchases) }
7
+
8
+ describe ".require_target_property!" do
9
+ it "sets a flag indicating target property is required by the query" do
10
+ klass = Class.new(query_class)
11
+ expect { klass.require_target_property! }.to change { klass.require_target_property? }.from(false).to(true)
12
+ end
13
+ end
6
14
 
7
15
  describe "#initialize" do
8
16
  it "sets the namespace and bucket" do
9
- query = Sleek::Queries::Query.new(:my_namespace, :purchases)
10
- expect(query.namespace).to eq :my_namespace
17
+ my_namespace = double('my_namespace', name: :my_namespace)
18
+ query = described_class.new(my_namespace, :purchases)
19
+ expect(query.namespace).to eq my_namespace
11
20
  expect(query.bucket).to eq :purchases
12
21
  end
13
22
 
@@ -29,10 +38,34 @@ describe Sleek::Queries::Query do
29
38
  end
30
39
 
31
40
  describe "#events" do
41
+ let(:evts) { double('events') }
42
+
43
+ context "when group_by is specified" do
44
+ before { query.stub(options: { group_by: "group" }) }
45
+
46
+ it "creates a group_by criteria from Mongoid::Criteria" do
47
+ namespace.should_receive(:events).and_return(evts)
48
+ Sleek::GroupByCriteria.should_receive(:new).with(evts, "d.group")
49
+
50
+ query.events
51
+ end
52
+
53
+ it "returns a group_by criteria" do
54
+ namespace.stub(events: evts)
55
+
56
+ crit = query.events
57
+
58
+ expect(crit.class).to eq Sleek::GroupByCriteria
59
+ expect(crit.criteria).to eq evts
60
+ expect(crit.group_by).to eq "d.group"
61
+ end
62
+ end
63
+
32
64
  context "when no timeframe is specifies" do
33
65
  context "when no filter is specified" do
34
66
  it "returns events in current namespace and bucket" do
35
- Sleek::Event.should_receive(:where).with(namespace: :default, bucket: :purchases)
67
+ namespace.stub(events: evts)
68
+ namespace.should_receive(:events).with(:purchases)
36
69
  query.events
37
70
  end
38
71
  end
@@ -42,6 +75,7 @@ describe Sleek::Queries::Query do
42
75
 
43
76
  it "applies filters" do
44
77
  final = stub('final_criteria')
78
+ namespace.stub(events: evts)
45
79
  query.should_receive(:apply_filters).and_return(final)
46
80
  expect(query.events).to eq final
47
81
  end
@@ -51,12 +85,12 @@ describe Sleek::Queries::Query do
51
85
  context "when timeframe is specified" do
52
86
  let(:start) { 1.day.ago }
53
87
  let(:finish) { Time.now }
54
- before { query.stub(:time_range).and_return(start..finish) }
88
+ before { query.stub(:timeframe).and_return(start..finish) }
55
89
 
56
90
  context "when no filter is specified" do
57
91
  it "gets only events between timeframe ends" do
58
92
  pre_evts = stub('pre_events')
59
- Sleek::Event.should_receive(:where).with(namespace: :default, bucket: :purchases).and_return(pre_evts)
93
+ namespace.should_receive(:events).with(:purchases).and_return(pre_evts)
60
94
  pre_evts.should_receive(:between).with("s.t" => start..finish)
61
95
  query.events
62
96
  end
@@ -69,7 +103,7 @@ describe Sleek::Queries::Query do
69
103
  pre_evts = stub('pre_events')
70
104
  criteria = stub('criteria')
71
105
  final = stub('final_criteria')
72
- Sleek::Event.should_receive(:where).with(namespace: :default, bucket: :purchases).and_return(pre_evts)
106
+ namespace.should_receive(:events).with(:purchases).and_return(pre_evts)
73
107
  pre_evts.should_receive(:between).and_return(criteria)
74
108
  query.should_receive(:apply_filters).with(criteria).and_return(final)
75
109
  expect(query.events).to eq final
@@ -120,35 +154,9 @@ describe Sleek::Queries::Query do
120
154
  end
121
155
  end
122
156
 
123
- describe "#timeframe" do
124
- context "when timeframe is specified" do
125
- let(:tf) { stub('timeframe') }
126
- before { query.stub(options: { timeframe: tf }) }
127
-
128
- it "creates new timeframe instance" do
129
- Sleek::Timeframe.should_receive(:new).with(tf)
130
- query.timeframe
131
- end
132
- end
133
- end
134
-
135
- describe "#series" do
136
- context "when timeframe and interval are specified" do
137
- let(:tf) { stub('timeframe') }
138
- before { query.stub(options: { timeframe: 'this_day', interval: :hourly }, timeframe: tf) }
139
-
140
- it "splits timeframe into intervals of sub-timeframes" do
141
- interval = stub('interval')
142
- Sleek::Interval.should_receive(:new).with(:hourly, tf).and_return(interval)
143
- interval.should_receive(:timeframes)
144
- query.series
145
- end
146
- end
147
- end
148
-
149
157
  describe "#valid_options?" do
150
158
  context "when options is a hash" do
151
- context "when no interval is passed" do
159
+ context "when target property is not required" do
152
160
  before { query.stub(options: {}) }
153
161
 
154
162
  it "is true" do
@@ -156,25 +164,22 @@ describe Sleek::Queries::Query do
156
164
  end
157
165
  end
158
166
 
159
- context "when interval is passed" do
160
- context "when timeframe is passed" do
161
- before { query.stub(options: { interval: :hourly, timeframe: Time.now.all_day }) }
167
+ context "when target property is required" do
168
+ before { query.stub(require_target_property?: true) }
162
169
 
163
- it "is true" do
164
- expect(query.valid_options?).to be_true
165
- end
170
+ it "is true if target property is specified" do
171
+ query.stub(options: { target_property: "total" })
172
+ expect(query.valid_options?).to be_true
166
173
  end
167
174
 
168
- context "when timeframe is not passed" do
169
- before { query.stub(options: { interval: :hourly }) }
170
- it "is false" do
171
- expect(query.valid_options?).to be_false
172
- end
175
+ it "is false if target property is not specified" do
176
+ query.stub(options: {})
177
+ expect(query.valid_options?).to be_false
173
178
  end
174
179
  end
175
180
  end
176
181
 
177
- context "when options isn't a hash" do
182
+ context "when options is not a hash" do
178
183
  before { query.stub(options: 1) }
179
184
 
180
185
  it "is false" do
@@ -184,43 +189,12 @@ describe Sleek::Queries::Query do
184
189
  end
185
190
 
186
191
  describe "#run" do
187
- context "when no series were requested" do
188
- it "performs query on events" do
189
- events = stub
190
- result = stub
191
- query.should_receive(:perform).with(events).and_return(result)
192
- query.stub(events: events)
193
- query.run
194
- end
195
- end
196
-
197
- context "when series were requested" do
198
- let(:series) { (0..23).to_a.map { |i| stub(to_time_range: (i..(i+1))) } }
199
- before { query.stub(options: { timeframe: 'this_day', interval: :hourly }, series: series) }
200
-
201
- it "performs query on each of sub-timeframes" do
202
- 24.times do |i|
203
- evts = stub('events')
204
- query.should_receive(:events).with(i..(i+1)).and_return(evts)
205
- query.should_receive(:perform).with(evts)
206
- end
207
-
208
- query.run
209
- end
210
-
211
- it "returns the array of results" do
212
- results = []
213
-
214
- 24.times do |i|
215
- evts = stub('events')
216
- value = stub('value')
217
- results << { timeframe: series[i], value: value }
218
- query.should_receive(:events).with(i..(i+1)).and_return(evts)
219
- query.should_receive(:perform).with(evts).and_return(value)
220
- end
221
-
222
- expect(query.run).to eq results
223
- end
192
+ it "performs query on events" do
193
+ events = double('events')
194
+ result = double('result')
195
+ query.should_receive(:perform).with(events).and_return(result)
196
+ query.stub(events: events)
197
+ query.run
224
198
  end
225
199
  end
226
200
  end