mongoid-casino 0.0.1
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.
- data/.gitignore +20 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +93 -0
- data/Rakefile +9 -0
- data/casino.gemspec +30 -0
- data/lib/casino.rb +13 -0
- data/lib/casino/collection.rb +195 -0
- data/lib/casino/dimension.rb +11 -0
- data/lib/casino/focus.rb +26 -0
- data/lib/casino/intersection.rb +30 -0
- data/lib/casino/intersection/match/all.rb +29 -0
- data/lib/casino/intersection/match/base.rb +49 -0
- data/lib/casino/intersection/match/equivalence.rb +25 -0
- data/lib/casino/intersection/match/expression.rb +15 -0
- data/lib/casino/intersection/match/greater.rb +43 -0
- data/lib/casino/intersection/match/include.rb +15 -0
- data/lib/casino/intersection/match/lesser.rb +31 -0
- data/lib/casino/intersection/match/recurse.rb +19 -0
- data/lib/casino/lobby.rb +21 -0
- data/lib/casino/projection.rb +76 -0
- data/lib/casino/query.rb +23 -0
- data/lib/casino/question.rb +9 -0
- data/lib/casino/store.rb +49 -0
- data/lib/casino/version.rb +3 -0
- data/spec/fabricators/model_fabricator.rb +12 -0
- data/spec/fabricators/note_fabricator.rb +3 -0
- data/spec/fixtures/collections/collection.rb +3 -0
- data/spec/fixtures/collections/emails_by_day.rb +28 -0
- data/spec/fixtures/models/model.rb +8 -0
- data/spec/fixtures/models/note.rb +7 -0
- data/spec/lib/casino/collection_spec.rb +146 -0
- data/spec/lib/casino/dimension_spec.rb +27 -0
- data/spec/lib/casino/focus_spec.rb +53 -0
- data/spec/lib/casino/intersection/match/all_spec.rb +24 -0
- data/spec/lib/casino/intersection/match/base_spec.rb +159 -0
- data/spec/lib/casino/intersection/match/equivalence_spec.rb +26 -0
- data/spec/lib/casino/intersection/match/expression_spec.rb +26 -0
- data/spec/lib/casino/intersection/match/greater_spec.rb +72 -0
- data/spec/lib/casino/intersection/match/include_spec.rb +27 -0
- data/spec/lib/casino/intersection/match/lesser_spec.rb +74 -0
- data/spec/lib/casino/intersection/match/recurse_spec.rb +24 -0
- data/spec/lib/casino/intersection_spec.rb +33 -0
- data/spec/lib/casino/lobby_spec.rb +20 -0
- data/spec/lib/casino/projection_spec.rb +81 -0
- data/spec/lib/casino/query_spec.rb +42 -0
- data/spec/lib/casino/question_spec.rb +10 -0
- data/spec/lib/casino/store_spec.rb +60 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/mongoid.yml +6 -0
- metadata +234 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
class EmailsByDay < Collection
|
2
|
+
|
3
|
+
focus Model
|
4
|
+
|
5
|
+
dimension "Date", :created_at, :last_seven_days
|
6
|
+
dimension "Source", :source, :primary_sources
|
7
|
+
|
8
|
+
question :unique_emails, :count_emails
|
9
|
+
|
10
|
+
def last_seven_days
|
11
|
+
(7.days.ago.to_date..Date.today).map do |date|
|
12
|
+
key = date.strftime("%m/%d/%Y")
|
13
|
+
range = date.beginning_of_day
|
14
|
+
query(key, range)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def primary_sources
|
19
|
+
%w(Facebook Google Twitter).map do |source|
|
20
|
+
query(source, source)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def count_emails
|
25
|
+
intersection.distinct(:email).count
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Casino::Collection do
|
4
|
+
describe "making a Casino collection" do
|
5
|
+
it { Collection.must_respond_to :dimension }
|
6
|
+
it { Collection.must_respond_to :focus }
|
7
|
+
it { Collection.must_respond_to :question }
|
8
|
+
it { Collection.must_respond_to :lobby }
|
9
|
+
it { Collection.must_respond_to :register }
|
10
|
+
it { Collection.new.must_respond_to :query }
|
11
|
+
it { Collection.new.must_respond_to :intersection }
|
12
|
+
it { Collection.new.must_respond_to :answer }
|
13
|
+
it { Collection.new.must_respond_to :projection }
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '.dimension' do
|
17
|
+
let(:field) { :created_at }
|
18
|
+
let(:queries) { [] }
|
19
|
+
let(:approach) { { operator: :and } }
|
20
|
+
subject { Collection.dimension(field, queries, approach) }
|
21
|
+
it { subject.must_be_instance_of Casino::Dimension }
|
22
|
+
it "registers the dimension with the Lobby" do
|
23
|
+
dimension = Collection.dimension(field, queries, approach)
|
24
|
+
Collection.lobby.registry[:dimension].must_include dimension
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '.focus' do
|
29
|
+
let(:model) { Model }
|
30
|
+
let(:focus) { Collection.focus(model) }
|
31
|
+
subject { focus }
|
32
|
+
it { subject.must_be_instance_of Casino::Focus }
|
33
|
+
it "registers the focus with the Lobby" do
|
34
|
+
focus = Collection.focus(Model)
|
35
|
+
Collection.lobby.registry[:focus].first.must_equal focus
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '.question' do
|
40
|
+
let(:name) { :emails }
|
41
|
+
let(:answer) { -> { distinct(:email) } }
|
42
|
+
subject { Collection.question(name, answer) }
|
43
|
+
it { subject.must_be_instance_of Casino::Question }
|
44
|
+
it "registers the question with the Lobby" do
|
45
|
+
question = Collection.question(name, answer)
|
46
|
+
Collection.lobby.registry[:question].must_include question
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'instance methods' do
|
51
|
+
|
52
|
+
let(:emails_by_day) { EmailsByDay.new }
|
53
|
+
|
54
|
+
describe 'registry accessors' do
|
55
|
+
|
56
|
+
let(:registry) { emails_by_day.class.lobby.registry }
|
57
|
+
let(:dimensions) { registry[:dimension] }
|
58
|
+
let(:questions) { registry[:question] }
|
59
|
+
let(:focus) { registry[:focus] }
|
60
|
+
|
61
|
+
subject { emails_by_day }
|
62
|
+
|
63
|
+
it { subject.dimensions.must_equal dimensions }
|
64
|
+
it { subject.focus.must_equal focus }
|
65
|
+
it { subject.questions.must_equal questions }
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '#query' do
|
70
|
+
subject { Collection.new.query('', '', '') }
|
71
|
+
it { subject.must_be_instance_of Casino::Query }
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '#store' do
|
75
|
+
subject { emails_by_day.store }
|
76
|
+
it { subject.must_be_instance_of Casino::Store }
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#results' do
|
80
|
+
subject { emails_by_day.results }
|
81
|
+
it { subject.must_be_instance_of Mongoid::Criteria }
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '#update' do
|
85
|
+
subject { emails_by_day.update }
|
86
|
+
it { subject.must_be_instance_of Mongoid::Criteria }
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#insert' do
|
90
|
+
subject { emails_by_day.insert(Model.new) }
|
91
|
+
it "returns a `Mongoid::Criteria`" do
|
92
|
+
subject.must_be_instance_of Mongoid::Criteria
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe '#projection' do
|
97
|
+
|
98
|
+
let(:selector) { Model.where(created_at: Time.now).selector }
|
99
|
+
let(:mock) { OpenStruct.new(selector: selector) }
|
100
|
+
|
101
|
+
subject { emails_by_day.projection }
|
102
|
+
|
103
|
+
it { subject.must_be_instance_of Casino::Projection }
|
104
|
+
|
105
|
+
it "is preloaded with the current criteria intersection" do
|
106
|
+
emails_by_day.stub(:intersection, mock) do
|
107
|
+
subject.pipeline.first['$match'].must_equal selector
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
describe '#intersection' do
|
114
|
+
subject { emails_by_day.intersection }
|
115
|
+
it { subject.must_be_instance_of Mongoid::Criteria }
|
116
|
+
it "defaults to the base model criteria" do
|
117
|
+
subject.must_equal Model.scoped
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe '#answer' do
|
122
|
+
subject { emails_by_day.answer(:count_emails) }
|
123
|
+
it "calls the target method" do
|
124
|
+
emails_by_day.stub(:count_emails, true) do
|
125
|
+
subject.must_equal true
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe '#key' do
|
131
|
+
|
132
|
+
let(:string) do
|
133
|
+
"emails_by_day_asks_model_about_unique_emails" +
|
134
|
+
"_by_created_at_and_source"
|
135
|
+
end
|
136
|
+
|
137
|
+
let(:key) { Digest::SHA1.hexdigest(string) }
|
138
|
+
|
139
|
+
subject { emails_by_day.key }
|
140
|
+
it { subject.must_equal key }
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Casino::Dimension do
|
4
|
+
|
5
|
+
let(:label) { "Date" }
|
6
|
+
let(:field) { :created_at }
|
7
|
+
let(:queries) { :method_name }
|
8
|
+
let(:and_approach) { { operator: :and } }
|
9
|
+
let(:where_approach) { { operator: :where } }
|
10
|
+
|
11
|
+
let(:dimension) do
|
12
|
+
Casino::Dimension.new(label, field, queries, and_approach)
|
13
|
+
end
|
14
|
+
|
15
|
+
subject { dimension }
|
16
|
+
|
17
|
+
it { subject.label.must_equal label }
|
18
|
+
it { subject.queries.must_equal queries }
|
19
|
+
it { subject.approach.must_equal and_approach }
|
20
|
+
it { subject.field.must_equal field }
|
21
|
+
|
22
|
+
it 'defaults #approach to operator: :where' do
|
23
|
+
dimension = Casino::Dimension.new(label, field, queries)
|
24
|
+
dimension.approach.must_equal where_approach
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Casino::Focus do
|
4
|
+
|
5
|
+
let(:model) { Model }
|
6
|
+
let(:focus) { Casino::Focus.new(model) }
|
7
|
+
|
8
|
+
subject { focus }
|
9
|
+
|
10
|
+
it { subject.model.must_equal model }
|
11
|
+
|
12
|
+
describe '#eql?' do
|
13
|
+
|
14
|
+
let(:match) { Casino::Focus.new(model) }
|
15
|
+
let(:no_match) { Casino::Focus.new(Class.new) }
|
16
|
+
|
17
|
+
it "is true when compared to another focus of the same model" do
|
18
|
+
subject.eql?(match).must_equal true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "is false when compared to a different model focus" do
|
22
|
+
subject.eql?(no_match).must_equal false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#hash' do
|
27
|
+
it "delegates to the model hash" do
|
28
|
+
subject.hash.must_equal model.hash
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#build_criteria' do
|
33
|
+
|
34
|
+
let(:conditions) do
|
35
|
+
[ [:where, { created_at: Time.now }],
|
36
|
+
[:and, { source: "Facebook" }] ]
|
37
|
+
end
|
38
|
+
|
39
|
+
let(:query) do
|
40
|
+
Casino::Query.new(:label, conditions)
|
41
|
+
end
|
42
|
+
|
43
|
+
subject { focus.build_criteria(query) }
|
44
|
+
|
45
|
+
it "produces a Mongoid::Criteria with the correct fields" do
|
46
|
+
[ "created_at", "$and" ].each do |field|
|
47
|
+
subject.selector.keys.must_include field
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
describe Casino::Intersection::Match::All do
|
2
|
+
|
3
|
+
let(:system) { Casino::Intersection::Match::All }
|
4
|
+
|
5
|
+
describe '#eligible?' do
|
6
|
+
let(:matcher) { system.new('$and', '', '', '') }
|
7
|
+
subject { matcher.eligible? }
|
8
|
+
it { subject.must_equal true }
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#evaluate' do
|
12
|
+
|
13
|
+
let(:matcher) { system.new('$and', '', '', [{}]) }
|
14
|
+
let(:base) { Casino::Intersection::Match::Base }
|
15
|
+
subject { matcher.evaluate }
|
16
|
+
it 'builds base matchers to evaluate' do
|
17
|
+
base.stub(:new, OpenStruct.new(evaluate: true)) do
|
18
|
+
subject.must_equal true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# If it's a regular expression, match with =~
|
2
|
+
# If it's a hash, recurse over the hash with the document
|
3
|
+
# If it is in the following directives, do the directive operation:
|
4
|
+
# * $gt evaluates with >
|
5
|
+
# * $gte evaluates with >=
|
6
|
+
# * $lt evalutes with <
|
7
|
+
# * $lte evaluates with <=
|
8
|
+
# * $in evaluates with Array#include?
|
9
|
+
# * $and evalutes as a list with each element of the list
|
10
|
+
# representing a new level of Match::Base with its own
|
11
|
+
# key
|
12
|
+
# If it's a string/integer, match with ==
|
13
|
+
|
14
|
+
describe Casino::Intersection::Match::Base do
|
15
|
+
|
16
|
+
let(:base_class) { Casino::Intersection::Match::Base }
|
17
|
+
let(:document) { Model.new(created_at: 3.days.ago) }
|
18
|
+
let(:key) { 'created_at' }
|
19
|
+
let(:criteria) { Model.gt(created_at: 4.days.ago) }
|
20
|
+
let(:selector) { criteria.selector }
|
21
|
+
let(:value) { document.created_at }
|
22
|
+
let(:base) { base_class.new(document, key, selector, value) }
|
23
|
+
let(:source) { "Facebook" }
|
24
|
+
let(:label) { [source] }
|
25
|
+
let(:intersection) { Casino::Intersection.new(label, criteria) }
|
26
|
+
|
27
|
+
describe 'when the document has an equivalent value' do
|
28
|
+
let(:model) { Model.new(source: source) }
|
29
|
+
let(:criteria) { Model.where(source: source) }
|
30
|
+
subject { intersection.match?(model) }
|
31
|
+
it { subject.must_equal true }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'when the document has a regular expression' do
|
35
|
+
let(:model) { Model.new(source: source) }
|
36
|
+
let(:criteria) { Model.where(source: /Face/i) }
|
37
|
+
subject { intersection.match?(model) }
|
38
|
+
it { subject.must_equal true }
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'directives' do
|
42
|
+
|
43
|
+
describe '$and' do
|
44
|
+
let(:model) { Model.new(source: "Facebook", created_at: 3.days.ago) }
|
45
|
+
|
46
|
+
let(:criteria) do
|
47
|
+
Model.and(:created_at.gt => 4.days.ago).
|
48
|
+
and(source: "Facebook")
|
49
|
+
end
|
50
|
+
|
51
|
+
subject { intersection.match?(model) }
|
52
|
+
it { subject.must_equal true }
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '$in' do
|
56
|
+
let(:model) { Model.new(source: "Facebook") }
|
57
|
+
let(:criteria) { Model.in(source: "Facebook") }
|
58
|
+
subject { intersection.match?(model) }
|
59
|
+
it { subject.must_equal true }
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '$gt' do
|
63
|
+
let(:model) { Model.new(created_at: 3.days.ago) }
|
64
|
+
let(:criteria) { Model.gt(created_at: 4.days.ago) }
|
65
|
+
subject { intersection.match?(model) }
|
66
|
+
it { subject.must_equal true }
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '$lt' do
|
70
|
+
let(:model) { Model.new(created_at: 4.days.ago) }
|
71
|
+
let(:criteria) { Model.lt(created_at: 3.days.ago) }
|
72
|
+
subject { intersection.match?(model) }
|
73
|
+
it { subject.must_equal true }
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '$gte' do
|
77
|
+
let(:model) { Model.new(created_at: 3.days.ago) }
|
78
|
+
let(:criteria) { Model.gte(created_at: 3.days.ago) }
|
79
|
+
subject { intersection.match?(model) }
|
80
|
+
it { subject.must_equal true }
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '$lte' do
|
84
|
+
let(:three_days_ago) { 3.days.ago }
|
85
|
+
let(:model) { Model.new(created_at: three_days_ago) }
|
86
|
+
let(:criteria) { Model.lte(created_at: three_days_ago) }
|
87
|
+
subject { intersection.match?(model) }
|
88
|
+
it { subject.must_equal true }
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
describe 'creating a new matcher base' do
|
94
|
+
|
95
|
+
describe '#document' do
|
96
|
+
subject { base.document }
|
97
|
+
it { subject.must_equal document }
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '#key' do
|
101
|
+
subject { base.key }
|
102
|
+
it { subject.must_equal key }
|
103
|
+
end
|
104
|
+
|
105
|
+
describe '#selector' do
|
106
|
+
subject { base.selector }
|
107
|
+
it { subject.must_equal selector }
|
108
|
+
end
|
109
|
+
|
110
|
+
describe '#field' do
|
111
|
+
subject { base.field }
|
112
|
+
it { subject.must_equal selector[key] }
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#value' do
|
116
|
+
subject { base.value }
|
117
|
+
it { subject.must_equal document.send(key) }
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
describe '#evaluate' do
|
123
|
+
it 'calls #evaluate on the given #matcher' do
|
124
|
+
matcher = MiniTest::Mock.new
|
125
|
+
matcher.expect :evaluate, nil
|
126
|
+
base.stub(:matcher, matcher) do
|
127
|
+
base.evaluate
|
128
|
+
end
|
129
|
+
matcher.verify
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe 'interfaces to matcher classes' do
|
134
|
+
|
135
|
+
describe '#matcher_for' do
|
136
|
+
it "creates a matcher for the given class name" do
|
137
|
+
matcher = base.matcher_for(:equivalence)
|
138
|
+
matcher.must_be_instance_of Casino::Intersection::Match::Equivalence
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe '#match_arguments' do
|
143
|
+
let(:arguments) { [base.key, base.value, base.document, base.field] }
|
144
|
+
subject { base.match_arguments }
|
145
|
+
it { subject.must_equal arguments }
|
146
|
+
end
|
147
|
+
|
148
|
+
describe '#matcher' do
|
149
|
+
it "finds the first eligible matcher" do
|
150
|
+
eligible_matcher = OpenStruct.new(eligible?: true)
|
151
|
+
base.stub(:matchers, [eligible_matcher]) do
|
152
|
+
base.matcher.must_equal eligible_matcher
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|