mixpannenkoek 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6086d33df79e9b37754ec4834a758f08f84097ab
4
+ data.tar.gz: 5b14c50436929af2868ca126a10cab450175ef0e
5
+ SHA512:
6
+ metadata.gz: 4cf1c431e9737f3235f9aba98c9fb976357e220bb40eded73654f5365bc6102776af5bb5297b62baa70a37ab19df9309d612d1f0f8660ceb1a30fe1e09d4b736
7
+ data.tar.gz: f267b2d654372851d60d078da1da63c39cb4c0802e54331ae7afcf1045192af733f3a8cdab8cb3d5786d98dd80565c05e70e29cfdd416a0d38edbff034c908bc
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mixpannenkoek.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Derek Kraan
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Mixpannenkoek
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'mixpannenkoek'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install mixpannenkoek
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,65 @@
1
+ module Mixpannenkoek
2
+ class Base
3
+ extend ::Mixpannenkoek::ClassInheritableAttribute
4
+ class_inheritable_attribute :_api_key, :_api_secret, :_endpoint, :_default_scope
5
+
6
+ # Public: the mixpanel api key
7
+ def self.set_api_key(api_key = nil, &block)
8
+ raise ArgumentError if !api_key.nil? && !block.nil?
9
+ self._api_key = api_key || block
10
+ end
11
+
12
+ def self.api_key
13
+ self._api_key.respond_to?(:call) ? self._api_key.call : self._api_key
14
+ end
15
+
16
+ # Public: the mixpanel api secret
17
+ def self.set_api_secret(api_secret = nil, &block)
18
+ raise ArgumentError if !api_secret.nil? && !block.nil?
19
+ self._api_secret = api_secret || block
20
+ end
21
+
22
+ def self.api_secret
23
+ self._api_secret.respond_to?(:call) ? self._api_secret.call : self._api_secret
24
+ end
25
+
26
+ def self.set_endpoint(endpoint = nil, &block)
27
+ raise ArgumentError if !endpoint.nil? && !block.nil?
28
+ self._endpoint = endpoint || block
29
+ end
30
+
31
+ def self.endpoint
32
+ self._endpoint.respond_to?(:call) ? self._endpoint.call : self._endpoint
33
+ end
34
+
35
+ ### Class methods (for convenience)
36
+ #
37
+ # these methods enable this type of usage:
38
+ # Mixpanel::Query.where(training_name: 'Training XYZ').group('subject_name').results
39
+ #
40
+ ###
41
+ def self.where(condition)
42
+ Mixpannenkoek::Query.new(self).where(condition)
43
+ end
44
+
45
+ def self.set(variable)
46
+ Mixpannenkoek::Query.new(self).set(variable)
47
+ end
48
+
49
+ def self.group(field)
50
+ Mixpannenkoek::Query.new(self).group(field)
51
+ end
52
+ ###
53
+ ### End class methods
54
+
55
+ def self.default_scope(&proc_or_lambda)
56
+ self._default_scope ||= []
57
+ self._default_scope += [proc_or_lambda]
58
+ end
59
+
60
+ def self.default_scopes
61
+ self._default_scope ||= []
62
+ self._default_scope.map{ |p| p.call }
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,23 @@
1
+ module Mixpannenkoek
2
+ module ClassInheritableAttribute
3
+ def class_inheritable_attribute(*attributes)
4
+ attributes.map(&:to_sym).each do |attribute|
5
+ define_singleton_method("#{attribute}=") do |value|
6
+ @@class_inheritable_attributes ||= {}
7
+ @@class_inheritable_attributes[attribute] ||= {}
8
+
9
+ @@class_inheritable_attributes[attribute][self.name] = value
10
+ end
11
+ define_singleton_method(attribute) do
12
+ if @@class_inheritable_attributes[attribute] && @@class_inheritable_attributes[attribute].has_key?(self.name)
13
+ @@class_inheritable_attributes[attribute][self.name]
14
+ elsif superclass.respond_to?(attribute)
15
+ superclass.send(attribute)
16
+ else
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,124 @@
1
+ require 'rubygems'
2
+ require 'mixpanel_client'
3
+
4
+ module Mixpannenkoek
5
+ class Benchmark
6
+ def self.ms(&block)
7
+ return block.call unless defined?(::Benchmark)
8
+ ::Benchmark.ms(&block)
9
+ end
10
+ end
11
+
12
+ class Query
13
+ class MissingRange < Exception; end
14
+ class MissingConfiguration < Exception; end
15
+
16
+ attr_accessor :where, :group, :klass
17
+
18
+ def initialize(klass, where = {}, vars = {}, group = nil)
19
+ @where = where
20
+ @vars = vars
21
+ @group = group
22
+ @klass = klass
23
+ end
24
+
25
+ def where(condition)
26
+ chain(where: @where.merge(condition))
27
+ end
28
+
29
+ def set(variable)
30
+ chain(vars: @vars.merge(variable))
31
+ end
32
+
33
+ def group(field)
34
+ chain(group: field)
35
+ end
36
+
37
+ def results
38
+ Mixpannenkoek::Results.new(@klass.endpoint, response_data)
39
+ end
40
+
41
+ def request_parameters
42
+ [@klass.endpoint, query_with_default_scopes]
43
+ end
44
+
45
+ def method_missing(*args, &block)
46
+ results.send(*args, &block)
47
+ end
48
+
49
+ def query_with_default_scopes
50
+ (@klass.default_scopes.map(&:query) + [query]).inject({}) do |final_query,query|
51
+ where = [final_query[:where], query.delete(:where)].compact.reject(&:empty?).compact
52
+ final_query[:where] =
53
+ if where.count > 1
54
+ "(#{where.join(' and ')})"
55
+ else
56
+ where.first
57
+ end
58
+
59
+ final_query.merge(query)
60
+ end
61
+ end
62
+
63
+ def query
64
+ query = @vars
65
+
66
+ if @where && @where != {}
67
+ if @where[:date]
68
+ query[:from_date] = @where[:date].first.strftime('%Y-%m-%d')
69
+ query[:to_date] = @where[:date].last.strftime('%Y-%m-%d')
70
+ end
71
+
72
+ query[:where] = @where.map do |key,value|
73
+ next if key == :date
74
+
75
+ case value
76
+ when Array
77
+ %Q((#{value.map { |val| %Q(properties["#{key}"] == "#{val}") }.join(' or ')}))
78
+ else
79
+ %Q(properties["#{key}"] == "#{value}")
80
+ end
81
+ end.compact
82
+
83
+ query[:where] =
84
+ if query[:where]
85
+ query[:where].join(' and ')
86
+ else
87
+ nil
88
+ end
89
+ end
90
+
91
+ query[:on] = %Q(properties["#{@group}"]) if @group
92
+
93
+ query
94
+ end
95
+
96
+ private
97
+
98
+ def chain(klass: @klass, where: @where, vars: @vars, group: @group)
99
+ self.class.new(klass, where, vars, group)
100
+ end
101
+
102
+ def mixpanel_client
103
+ Mixpanel::Client.new(
104
+ api_key: @klass.api_key,
105
+ api_secret: @klass.api_secret
106
+ )
107
+ end
108
+
109
+ def check_parameters
110
+ raise MissingRange if query_with_default_scopes[:from_date].nil? && query_with_default_scopes[:to_date].nil?
111
+ raise MissingConfiguration.new('The mixpanel api_key has not been configured') if @klass.api_key.nil?
112
+ raise MissingConfiguration.new('The mixpanel api_secret has not been configured') if @klass.api_secret.nil?
113
+ end
114
+
115
+ def response_data
116
+ check_parameters
117
+ time = Benchmark.ms do
118
+ @mixpanel_request ||= mixpanel_client.request(*request_parameters)
119
+ end
120
+ Rails.logger.info " Mixpanel (#{time.round(1)}ms) #{request_parameters.inspect}" if defined? Rails
121
+ @mixpanel_request
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,21 @@
1
+ module Mixpannenkoek
2
+ module Results
3
+ class Base
4
+ def initialize(response_data)
5
+ @response_data = response_data
6
+ end
7
+
8
+ def response_data
9
+ @response_data
10
+ end
11
+
12
+ def to_hash
13
+ @response_data
14
+ end
15
+
16
+ def method_missing(*args, &block)
17
+ to_hash.send(*args, &block)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ require 'mixpannenkoek/results/base'
2
+
3
+ module Mixpannenkoek
4
+ module Results
5
+ class Funnels < Base
6
+ def initialize(response_data)
7
+ @response_data = response_data
8
+ end
9
+
10
+ def to_hash
11
+ super['data']
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module Mixpannenkoek
2
+ module Results
3
+ def self.new(endpoint, response_data)
4
+ klass =
5
+ case endpoint
6
+ when 'funnels'
7
+ Mixpannenkoek::Results::Funnels
8
+ else
9
+ Mixpannenkoek::Results::Base
10
+ end
11
+ klass.new(response_data)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Mixpannenkoek
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,5 @@
1
+ Dir["#{File.dirname(__FILE__)}/**/*.rb"].each { |file| require file }
2
+
3
+ module Mixpannenkoek
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mixpannenkoek/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mixpannenkoek"
8
+ spec.version = Mixpannenkoek::VERSION
9
+ spec.authors = ["Derek Kraan"]
10
+ spec.email = ["derek.kraan@gmail.com"]
11
+ spec.summary = %q{Sugar for the mixpanel-client gem}
12
+ spec.description = %q{Implements a fluent interface for writing queries for the mixpanel API. Also includes ActiveRecord-like features like default scoping.}
13
+ spec.homepage = "https://github.com/Springest/mixpannenkoek"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency 'mixpanel_client', "~> 4.1.0"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.4"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ end
@@ -0,0 +1,29 @@
1
+ {"Signup flow": {"data": {"2010-05-24": {"analysis": {"completion": 0.064679359580052493,
2
+ "starting_amount": 762,
3
+ "steps": 3,
4
+ "worst": 2},
5
+ "steps": [{"count": 762,
6
+ "goal": "pages",
7
+ "overall_conv_ratio": 1.0,
8
+ "step_conv_ratio": 1.0},
9
+ {"count": 69,
10
+ "goal": "View signup",
11
+ "overall_conv_ratio": 0.09055118110236221,
12
+ "step_conv_ratio": 0.09055118110236221},
13
+ {"count": 10,
14
+ "goal": "View docs",
15
+ "overall_conv_ratio": 0.064679359580052493,
16
+ "step_conv_ratio": 0.7142857142857143}]},
17
+ "2010-05-31": {"analysis": {"completion": 0.12362030905077263,
18
+ "starting_amount": 906,
19
+ "steps": 2,
20
+ "worst": 2},
21
+ "steps": [{"count": 906,
22
+ "goal": "homepage",
23
+ "overall_conv_ratio": 1.0,
24
+ "step_conv_ratio": 1.0},
25
+ {"count": 112,
26
+ "goal": "View signup",
27
+ "overall_conv_ratio": 0.12362030905077263,
28
+ "step_conv_ratio": 0.12362030905077263}]}},
29
+ "meta": {"dates": ["2010-05-24", "2010-05-31"]}}}
File without changes
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mixpannenkoek::ClassInheritableAttribute do
4
+ class TestClass
5
+ extend Mixpannenkoek::ClassInheritableAttribute
6
+ class_inheritable_attribute :test_var
7
+ end
8
+
9
+ class TestSubClass < TestClass
10
+ end
11
+
12
+ it 'is heritable' do
13
+ TestClass.test_var = 'foo'
14
+ expect(TestSubClass.test_var).to eq 'foo'
15
+ end
16
+
17
+ it 'is overwritable' do
18
+ TestClass.test_var = 'foo'
19
+ TestSubClass.test_var = 'bar'
20
+ expect(TestSubClass.test_var).to eq 'bar'
21
+ end
22
+
23
+ it 'does not overwrite the superclass variable' do
24
+ TestClass.test_var = 'foo'
25
+ TestSubClass.test_var = 'bar'
26
+ expect(TestClass.test_var).to eq 'foo'
27
+ end
28
+ end
@@ -0,0 +1,130 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mixpannenkoek::Base do
4
+ class Mixpannenkoek::TestQuery < Mixpannenkoek::Base
5
+ set_api_key 'an_api_key'
6
+ set_api_secret 'an_api_secret'
7
+ set_endpoint 'funnels'
8
+ end
9
+
10
+ let(:date_range) { Date.parse('01-01-2014')..Date.parse('05-01-2014') }
11
+
12
+ describe '#where' do
13
+ subject { Mixpannenkoek::TestQuery.where(date: date_range).where(subject_name: 'Subject ABC').request_parameters[1] }
14
+ it 'sets :where' do
15
+ expect(subject).to include({ where: 'properties["subject_name"] == "Subject ABC"' })
16
+ end
17
+
18
+ context 'called twice' do
19
+ subject { Mixpannenkoek::TestQuery.where(date: date_range).where(subject_name: 'Subject ABC').where(training_name: 'Training XYZ').request_parameters[1] }
20
+ it 'sets multiple :where conditions' do
21
+ expect(subject).to include({ where: 'properties["subject_name"] == "Subject ABC" and properties["training_name"] == "Training XYZ"' })
22
+ end
23
+ end
24
+
25
+ context 'called with value: []' do
26
+ subject { Mixpannenkoek::TestQuery.where(date: date_range).where(subject_name: ['Subject ABC', 'Subject XYZ']).request_parameters[1] }
27
+ it 'sets multiple :where conditions' do
28
+ expect(subject).to include({ where: '(properties["subject_name"] == "Subject ABC" or properties["subject_name"] == "Subject XYZ")' })
29
+ end
30
+ end
31
+
32
+ context 'where(date: range)' do
33
+ subject { Mixpannenkoek::TestQuery.where(date: date_range).request_parameters[1] }
34
+
35
+ it 'does not automatically set the interval' do
36
+ expect(subject.keys).not_to include(:interval)
37
+ end
38
+
39
+ it 'sets :from_date' do
40
+ expect(subject).to include({ from_date: '2014-01-01' })
41
+ end
42
+
43
+ it 'sets :to_date' do
44
+ expect(subject).to include({ to_date: '2014-01-05' })
45
+ end
46
+ end
47
+ end
48
+
49
+ describe '#group' do
50
+ subject { Mixpannenkoek::TestQuery.where(date: date_range).group('subject_name').request_parameters[1] }
51
+ it 'sets :on' do
52
+ expect(subject).to include({ on: 'properties["subject_name"]' })
53
+ end
54
+
55
+ context 'called twice' do
56
+ subject { Mixpannenkoek::TestQuery.where(date: date_range).group('subject_name').group('training_name').request_parameters[1] }
57
+ it 'takes the value from the last call' do
58
+ expect(subject).to include({ on: 'properties["training_name"]' })
59
+ end
60
+ end
61
+ end
62
+
63
+ describe '#set' do
64
+ subject { Mixpannenkoek::TestQuery.set(funnel_id: 12345).where(date: date_range).request_parameters[1] }
65
+ it { should include({ funnel_id: 12345 }) }
66
+
67
+ context 'called twice' do
68
+ subject { Mixpannenkoek::TestQuery.set(funnel_id: 12345).set(event: 'click').where(date: date_range).request_parameters[1] }
69
+ it { should include({ funnel_id: 12345 }) }
70
+ it { should include({ event: 'click' }) }
71
+ end
72
+ end
73
+
74
+ describe '#query' do
75
+ context 'without date range' do
76
+ subject { Mixpannenkoek::TestQuery.group('subject_name').to_hash }
77
+ it 'raises Mixpannenkoek::Query::MissingRange' do
78
+ expect { subject }.to raise_error Mixpannenkoek::Query::MissingRange
79
+ end
80
+ end
81
+
82
+ context 'missing api_key' do
83
+ subject { Mixpannenkoek::TestQuery.where(date: date_range).to_hash }
84
+ before { Mixpannenkoek::TestQuery.stub(:api_key) { nil } }
85
+ it 'raises Mixpannenkoek::Query::MissingConfiguration' do
86
+ expect { subject }.to raise_error Mixpannenkoek::Query::MissingConfiguration, 'The mixpanel api_key has not been configured'
87
+ end
88
+ end
89
+
90
+ context 'missing api_secret' do
91
+ subject { Mixpannenkoek::TestQuery.where(date: date_range).to_hash }
92
+ before { Mixpannenkoek::TestQuery.stub(:api_secret) { nil } }
93
+ it 'raises Mixpannenkoek::Query::MissingConfiguration' do
94
+ expect { subject }.to raise_error Mixpannenkoek::Query::MissingConfiguration, 'The mixpanel api_secret has not been configured'
95
+ end
96
+ end
97
+ end
98
+
99
+ describe '#to_hash' do
100
+ subject { Mixpannenkoek::TestQuery.where(date: date_range).to_hash }
101
+
102
+ let(:response_data) { JSON.parse(File.open("#{File.dirname(__FILE__)}/../../fixtures/funnel_response_data.json").read) }
103
+ before { Mixpanel::Client.any_instance.stub(:request).and_return(response_data) }
104
+
105
+ it 'returns the response data' do
106
+ expect(subject).to eq response_data
107
+ end
108
+ end
109
+
110
+ describe '#method_missing' do
111
+ before { Mixpannenkoek::Query.any_instance.stub(:mixpanel_request).and_return({ "funnel" => { "data" => { "2014-01-01" => [{ 'count' => '1' }, { 'count' => '4' }] } } }) }
112
+ it 'delegates all calls to #to_hash.send(*args)' do
113
+ expect(Mixpannenkoek::TestQuery.where(date: date_range).keys).to eq ['funnel']
114
+ end
115
+ it 'delegates blocks' do
116
+ expect(Mixpannenkoek::TestQuery.where(date: date_range)['funnel']['data'].values[0].map { |hash| hash['count'] }).to eq ['1','4']
117
+ end
118
+ end
119
+
120
+ describe '.default_scope' do
121
+ class Mixpannenkoek::DefaultScopeQuery < Mixpannenkoek::TestQuery
122
+ default_scope { where(subject_name: 'Subject XYZ') }
123
+ end
124
+
125
+ subject { Mixpannenkoek::DefaultScopeQuery.where(date: date_range).request_parameters[1] }
126
+ it 'applies the default scope' do
127
+ expect(subject).to include({ where: 'properties["subject_name"] == "Subject XYZ"' })
128
+ end
129
+ end
130
+ end
File without changes
@@ -0,0 +1,3 @@
1
+ require 'mixpannenkoek'
2
+ require 'date'
3
+ require 'json'
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mixpannenkoek
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Derek Kraan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mixpanel_client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 4.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 4.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.4'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Implements a fluent interface for writing queries for the mixpanel API.
70
+ Also includes ActiveRecord-like features like default scoping.
71
+ email:
72
+ - derek.kraan@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - .gitignore
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - lib/mixpannenkoek.rb
83
+ - lib/mixpannenkoek/base.rb
84
+ - lib/mixpannenkoek/class_inheritable_attribute.rb
85
+ - lib/mixpannenkoek/query.rb
86
+ - lib/mixpannenkoek/results.rb
87
+ - lib/mixpannenkoek/results/base.rb
88
+ - lib/mixpannenkoek/results/funnels.rb
89
+ - lib/mixpannenkoek/version.rb
90
+ - mixpannenkoek.gemspec
91
+ - spec/fixtures/funnel_response_data.json
92
+ - spec/lib/mixpannenkoek/base_spec.rb
93
+ - spec/lib/mixpannenkoek/class_inheritable_attributes_spec.rb
94
+ - spec/lib/mixpannenkoek/query_spec.rb
95
+ - spec/lib/mixpannenkoek_spec.rb
96
+ - spec/spec_helper.rb
97
+ homepage: https://github.com/Springest/mixpannenkoek
98
+ licenses:
99
+ - MIT
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubyforge_project:
117
+ rubygems_version: 2.0.3
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: Sugar for the mixpanel-client gem
121
+ test_files:
122
+ - spec/fixtures/funnel_response_data.json
123
+ - spec/lib/mixpannenkoek/base_spec.rb
124
+ - spec/lib/mixpannenkoek/class_inheritable_attributes_spec.rb
125
+ - spec/lib/mixpannenkoek/query_spec.rb
126
+ - spec/lib/mixpannenkoek_spec.rb
127
+ - spec/spec_helper.rb