mixpannenkoek 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a7399793b312fda362a710cab02c13c0061f3181
4
- data.tar.gz: 3d4990a74dc58c7d011c83d7d9f0794ceb0eba76
3
+ metadata.gz: bc559cc6c5f4a55a318e78cfea0dd26fc5abcb8d
4
+ data.tar.gz: ba9b0af6737d9c8c4c7fbfdf7bd495ce53d0383d
5
5
  SHA512:
6
- metadata.gz: ebed70b8633f90ef5976f67defa8d8351e47e5ac9690fafd1ec192f0d55151c25f212aa0235d604892a3e419d7b090dcc39f3026d4f1d5a890fe20ecca329634
7
- data.tar.gz: a93ba52c09f8f521558305c7432f5115bc253f43d9f25933eeb6422c0df350984a5ced58048153356829fa565bf937fb77c291e75ddea36733c7c6e5451095f1
6
+ metadata.gz: a3bcaaef791d0848be8bf5bfd31e0434ecd51d3318037655bc2892ab2e56177707a1077c1fb75750cda43bf6065c2b7360d402200e4e21c1a60e3965fd840cd7
7
+ data.tar.gz: 90a9757a0c9db3d992688481ae9d0acd76d5e2e2176809851715d367668948b9cdc24ee7ef606d0a5b3c6d4a56d8f208a4202648ff7824299cb16e7bb3441a10
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Mixpannenkoek
2
2
 
3
- This gem implements a fluent query interface for mixpanel_client.
3
+ This gem implements a fluent query interface for the [mixpanel_client](https://github.com/keolo/mixpanel_client) gem.
4
+
5
+ [![Code Climate](https://codeclimate.com/github/Springest/mixpannenkoek.png)](https://codeclimate.com/github/Springest/mixpannenkoek)
4
6
 
5
7
  ## Installation
6
8
 
@@ -18,46 +20,124 @@ Or install it yourself as:
18
20
 
19
21
  ## Usage
20
22
 
21
- Begin by configuring a model.
23
+ Begin by creating and configuring a model. In Rails you might place this in `app/models/conversion_funnel.rb`. You *must* set the api_key, api_secret, and endpoint. These properties are heritable, so in the following example, `OtherFunnel` will have the api_key, api_secret and endpoint already set.
22
24
 
23
25
  ```ruby
24
- class ConversionFunnel < Mixpannenkoek::Base
26
+ class Funnel::Base < Mixpannenkoek::Base
25
27
  set_api_key 'MY_API_KEY'
26
28
  set_api_secret 'MY_API_SECRET'
27
- set_endpoint 'funnels'
28
-
29
+ set_endpoint 'funnels' # or any other endpoint
30
+ end
31
+
32
+ class Funnel::Conversions < Funnel::Base
29
33
  default_scope { set(funnel_id: 123456) }
30
34
  end
31
35
  ```
32
36
 
33
- Build up a query with `where`, `group`, and `set`.
37
+ With mixpanel_client, you might run a query like this:
34
38
 
35
39
  ```ruby
36
- ConversionFunnel.where(date: 31.days.ago..1.day.ago)
37
-
38
- ConversionFunnel.where(date: 31.days.ago..1.day.ago).where(user_id: 123).set(interval: 50).group('traffic_source')
40
+ client = Mixpanel::Client.new(
41
+ api_key: 'MY_API_KEY',
42
+ api_secret: 'MY_API_SECRET'
43
+ )
44
+
45
+ data = client.request(
46
+ 'funnels',
47
+ funnel_id: 123456,
48
+ from_date: '2014-01-01',
49
+ to_date: '2014-01-31',
50
+ interval: 31,
51
+ on: 'properties["traffic_source"]',
52
+ where: 'properties["user_type"] = "visitor" AND properties["landing_page"] = "homepage"',
53
+ )
39
54
  ```
40
55
 
41
- Operate on the query results fluently
56
+ With mixpannenkoek, you would write it like this (making use of the models defined above):
42
57
 
43
58
  ```ruby
44
- ConversionFunnel.where(date: 31.days.ago..1.day.ago).map { |date,data| data['steps'].last['count'] }
45
- #=> [1, 4, 2]
59
+ Funnel::Conversions.where(date: Date.parse('2014-01-01')..Date.parse('2014-01-31')).set(interval: 31).group('traffic_source').where(user_type: 'visitor').where(landing_page: 'homepage')
46
60
  ```
47
61
 
48
- Organize your query models with default scopes. Default scopes are heritable, so they will be automatically be applied to subclasses.
62
+ `where` allows you to easily build the `where` parameter of the request.
63
+
64
+ `group` corresponds to the `on` parameter.
65
+
66
+ `set` sets any other parameters in the request (in this case, `funnel_id` is mandatory).
67
+
68
+ This gem also supports `default_scope`, which is also heritable. Some of the parameters above might instead be set in the model, to save time.
69
+
49
70
  ```ruby
50
- class ConversionFunnel < Mixpannenkoek::Base
51
- default_scope { set(interval: 50) }
71
+ class Funnel::Conversions < Funnel::Base
72
+ default_scope { set(funnel_id: 123456) }
73
+ default_scope { set(interval: 31) }
52
74
  default_scope { where(user_type: 'visitor') }
53
75
  end
76
+ ```
54
77
 
55
- # default scopes are heritable
56
- # (GroupedConversionFunnel will get the default scopes
57
- # of ConversionFunnel, in addition to its own)
58
- class GroupedConversionFunnel < ConversionFunnel
59
- default_scope { group('traffic_source') }
60
- end
78
+ Building up the query then becomes a little bit easier:
79
+
80
+ ```ruby
81
+ Funnel::Conversions.where(date: Date.parse('2014-01-01')..Date.parse('2014-01-31')).group('traffic_source').where(landing_page: 'homepage')
82
+ ```
83
+
84
+ Note: you are not required to set the `funnel_id` in the model itself. The following queries are possible:
85
+
86
+ ```ruby
87
+ Funnel::Base.set(funnel_id: 987654).where(date: range)
88
+ ```
89
+
90
+ Operating on the response data is also fluent. Just call a method (including `[]`). This will trigger a request to the mixpanel API and make the response data available:
91
+
92
+ ```ruby
93
+ Funnel::Conversions.where(date: 31.days.ago..1.day.ago).response_data
94
+ #=> {"2010-05-24"=>
95
+ {"analysis"=>
96
+ {"completion"=>0.0646793595800525,
97
+ "starting_amount"=>762,
98
+ "steps"=>3,
99
+ "worst"=>2},
100
+ "steps"=>
101
+ [{"count"=>762,
102
+ "goal"=>"pages",
103
+ "overall_conv_ratio"=>1.0,
104
+ "step_conv_ratio"=>1.0},
105
+ {"count"=>69,
106
+ "goal"=>"View signup",
107
+ "overall_conv_ratio"=>0.09055118110236221,
108
+ "step_conv_ratio"=>0.09055118110236221},
109
+ {"count"=>10,
110
+ "goal"=>"View docs",
111
+ "overall_conv_ratio"=>0.0646793595800525,
112
+ "step_conv_ratio"=>0.7142857142857143}]},
113
+ "2010-05-31"=>
114
+ {"analysis"=>
115
+ {"completion"=>0.12362030905077263,
116
+ "starting_amount"=>906,
117
+ "steps"=>2,
118
+ "worst"=>2},
119
+ "steps"=>
120
+ [{"count"=>906,
121
+ "goal"=>"homepage",
122
+ "overall_conv_ratio"=>1.0,
123
+ "step_conv_ratio"=>1.0},
124
+ {"count"=>112,
125
+ "goal"=>"View signup",
126
+ "overall_conv_ratio"=>0.12362030905077263,
127
+ "step_conv_ratio"=>0.12362030905077263}]}}
128
+
129
+ Funnel::Conversions.where(date: 31.days.ago..1.day.ago)['2010-05-24']['steps'][0]['count']
130
+ #=> 762
131
+
132
+ Funnel::Conversions.where(date: 31.days.ago..1.day.ago).map { |date,data| data['steps'].last['count'] }
133
+ #=> [10, 112]
134
+ ```
135
+
136
+ Query objects are also immutable. So it's possible to organize your code in the following manner:
137
+
138
+ ```ruby
139
+ query_1 = Funnel::Conversions.where(date: 31.days.ago..1.day.ago)
140
+ query_2 = query_1.where(traffic_source: 'google') # this leaves query_1 unchanged
61
141
  ```
62
142
 
63
143
  ## Contributing
@@ -5,33 +5,28 @@ module Mixpannenkoek
5
5
  extend ::Mixpannenkoek::ClassInheritableAttribute
6
6
  class_inheritable_attribute :_api_key, :_api_secret, :_endpoint, :_default_scope
7
7
 
8
- # Public: the mixpanel api key
9
8
  def self.set_api_key(api_key = nil, &block)
10
- raise ArgumentError if !api_key.nil? && !block.nil?
11
- self._api_key = api_key || block
9
+ self._api_key = value_or_block(api_key, &block)
12
10
  end
13
11
 
14
- def self.api_key
15
- self._api_key.respond_to?(:call) ? self._api_key.call : self._api_key
12
+ def self.set_api_secret(api_secret = nil, &block)
13
+ self._api_secret = value_or_block(api_secret, &block)
16
14
  end
17
15
 
18
- # Public: the mixpanel api secret
19
- def self.set_api_secret(api_secret = nil, &block)
20
- raise ArgumentError if !api_secret.nil? && !block.nil?
21
- self._api_secret = api_secret || block
16
+ def self.set_endpoint(endpoint = nil, &block)
17
+ self._endpoint = value_or_block(endpoint, &block)
22
18
  end
23
19
 
24
- def self.api_secret
25
- self._api_secret.respond_to?(:call) ? self._api_secret.call : self._api_secret
20
+ def self.api_key
21
+ value_from_block(self._api_key)
26
22
  end
27
23
 
28
- def self.set_endpoint(endpoint = nil, &block)
29
- raise ArgumentError if !endpoint.nil? && !block.nil?
30
- self._endpoint = endpoint || block
24
+ def self.api_secret
25
+ value_from_block(self._api_secret)
31
26
  end
32
27
 
33
28
  def self.endpoint
34
- self._endpoint.respond_to?(:call) ? self._endpoint.call : self._endpoint
29
+ value_from_block(self._endpoint)
35
30
  end
36
31
 
37
32
  ### Class methods (for convenience)
@@ -63,5 +58,15 @@ module Mixpannenkoek
63
58
  self._default_scope ||= []
64
59
  self._default_scope.map{ |p| p.call }
65
60
  end
61
+
62
+ private
63
+ def self.value_or_block(value, &block)
64
+ raise ArgumentError unless !!value ^ !!block
65
+ value || block
66
+ end
67
+
68
+ def self.value_from_block(value_or_proc)
69
+ value_or_proc.respond_to?(:call) ? value_or_proc.call : value_or_proc
70
+ end
66
71
  end
67
72
  end
@@ -2,20 +2,28 @@ module Mixpannenkoek
2
2
  module ClassInheritableAttribute
3
3
  def class_inheritable_attribute(*attributes)
4
4
  attributes.map(&:to_sym).each do |attribute|
5
- define_singleton_method("#{attribute}=") do |value|
6
- @@class_inheritable_attributes ||= {}
7
- @@class_inheritable_attributes[attribute] ||= {}
5
+ create_setter(attribute)
6
+ create_getter(attribute)
7
+ end
8
+ end
8
9
 
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
10
+ def create_setter(attribute)
11
+ define_singleton_method("#{attribute}=") do |value|
12
+ @@class_inheritable_attributes ||= {}
13
+ @@class_inheritable_attributes[attribute] ||= {}
14
+
15
+ @@class_inheritable_attributes[attribute][self.name] = value
16
+ end
17
+ end
18
+
19
+ def create_getter(attribute)
20
+ define_singleton_method(attribute) do
21
+ if @@class_inheritable_attributes[attribute] && @@class_inheritable_attributes[attribute].has_key?(self.name)
22
+ @@class_inheritable_attributes[attribute][self.name]
23
+ elsif superclass.respond_to?(attribute)
24
+ superclass.send(attribute)
25
+ else
26
+ nil
19
27
  end
20
28
  end
21
29
  end
@@ -57,10 +57,7 @@ module Mixpannenkoek
57
57
  query = @vars
58
58
 
59
59
  if @where && @where != {}
60
- if @where[:date]
61
- query[:from_date] = @where[:date].first.strftime('%Y-%m-%d')
62
- query[:to_date] = @where[:date].last.strftime('%Y-%m-%d')
63
- end
60
+ extract_dates(query, @where)
64
61
 
65
62
  query[:where] = @where.map do |key,value|
66
63
  next if key == :date
@@ -118,5 +115,15 @@ module Mixpannenkoek
118
115
  time = ::Benchmark.ms(&block)
119
116
  Rails.logger.info " Mixpanel (#{time.round(1)}ms) #{request_parameters.inspect}" if defined? Rails
120
117
  end
118
+
119
+ def extract_dates(query, where)
120
+ return unless where[:date]
121
+
122
+ query[:from_date] = where[:date].first
123
+ query[:to_date] = where[:date].last
124
+
125
+ query[:from_date] = query[:from_date].strftime('%Y-%m-%d') if query[:from_date].respond_to? :strftime
126
+ query[:to_date] = query[:to_date].strftime('%Y-%m-%d') if query[:to_date].respond_to? :strftime
127
+ end
121
128
  end
122
129
  end
@@ -1,3 +1,3 @@
1
1
  module Mixpannenkoek
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -43,6 +43,17 @@ describe Mixpannenkoek::Base do
43
43
  it 'sets :to_date' do
44
44
  expect(subject).to include({ to_date: '2014-01-05' })
45
45
  end
46
+
47
+ context 'date strings' do
48
+ let(:date_range) { '2014-01-01'..'2014-01-31' }
49
+ it 'sets :from_date' do
50
+ expect(subject).to include({ from_date: '2014-01-01' })
51
+ end
52
+
53
+ it 'sets :to_date' do
54
+ expect(subject).to include({ to_date: '2014-01-31' })
55
+ end
56
+ end
46
57
  end
47
58
  end
48
59
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixpannenkoek
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Kraan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-27 00:00:00.000000000 Z
11
+ date: 2014-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mixpanel_client