mixpannenkoek 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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