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.
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