mongoid-casino 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.gitignore +20 -0
  2. data/.ruby-gemset +1 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +93 -0
  7. data/Rakefile +9 -0
  8. data/casino.gemspec +30 -0
  9. data/lib/casino.rb +13 -0
  10. data/lib/casino/collection.rb +195 -0
  11. data/lib/casino/dimension.rb +11 -0
  12. data/lib/casino/focus.rb +26 -0
  13. data/lib/casino/intersection.rb +30 -0
  14. data/lib/casino/intersection/match/all.rb +29 -0
  15. data/lib/casino/intersection/match/base.rb +49 -0
  16. data/lib/casino/intersection/match/equivalence.rb +25 -0
  17. data/lib/casino/intersection/match/expression.rb +15 -0
  18. data/lib/casino/intersection/match/greater.rb +43 -0
  19. data/lib/casino/intersection/match/include.rb +15 -0
  20. data/lib/casino/intersection/match/lesser.rb +31 -0
  21. data/lib/casino/intersection/match/recurse.rb +19 -0
  22. data/lib/casino/lobby.rb +21 -0
  23. data/lib/casino/projection.rb +76 -0
  24. data/lib/casino/query.rb +23 -0
  25. data/lib/casino/question.rb +9 -0
  26. data/lib/casino/store.rb +49 -0
  27. data/lib/casino/version.rb +3 -0
  28. data/spec/fabricators/model_fabricator.rb +12 -0
  29. data/spec/fabricators/note_fabricator.rb +3 -0
  30. data/spec/fixtures/collections/collection.rb +3 -0
  31. data/spec/fixtures/collections/emails_by_day.rb +28 -0
  32. data/spec/fixtures/models/model.rb +8 -0
  33. data/spec/fixtures/models/note.rb +7 -0
  34. data/spec/lib/casino/collection_spec.rb +146 -0
  35. data/spec/lib/casino/dimension_spec.rb +27 -0
  36. data/spec/lib/casino/focus_spec.rb +53 -0
  37. data/spec/lib/casino/intersection/match/all_spec.rb +24 -0
  38. data/spec/lib/casino/intersection/match/base_spec.rb +159 -0
  39. data/spec/lib/casino/intersection/match/equivalence_spec.rb +26 -0
  40. data/spec/lib/casino/intersection/match/expression_spec.rb +26 -0
  41. data/spec/lib/casino/intersection/match/greater_spec.rb +72 -0
  42. data/spec/lib/casino/intersection/match/include_spec.rb +27 -0
  43. data/spec/lib/casino/intersection/match/lesser_spec.rb +74 -0
  44. data/spec/lib/casino/intersection/match/recurse_spec.rb +24 -0
  45. data/spec/lib/casino/intersection_spec.rb +33 -0
  46. data/spec/lib/casino/lobby_spec.rb +20 -0
  47. data/spec/lib/casino/projection_spec.rb +81 -0
  48. data/spec/lib/casino/query_spec.rb +42 -0
  49. data/spec/lib/casino/question_spec.rb +10 -0
  50. data/spec/lib/casino/store_spec.rb +60 -0
  51. data/spec/spec_helper.rb +26 -0
  52. data/spec/support/mongoid.yml +6 -0
  53. metadata +234 -0
@@ -0,0 +1,3 @@
1
+ class Collection
2
+ include Casino::Collection
3
+ end
@@ -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,8 @@
1
+ class Model
2
+ include Mongoid::Document
3
+
4
+ field :created_at
5
+ field :source
6
+
7
+ embeds_many :notes
8
+ end
@@ -0,0 +1,7 @@
1
+ class Note
2
+ include Mongoid::Document
3
+
4
+ field :views, type: Integer
5
+
6
+ embedded_in :model
7
+ 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