keen 0.3.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ *.bundle
2
+ Gemfile.lock
3
+ tmp
4
+ .env
5
+ log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format progress
2
+ --color
@@ -0,0 +1,17 @@
1
+ language: ruby
2
+ bundler_args: --without development
3
+
4
+ rvm:
5
+ - 1.8.7
6
+ - 1.9.3
7
+ - jruby-19mode
8
+ - rbx-19mode
9
+
10
+ env:
11
+ global:
12
+ - KEEN_API_KEY=f806128f31c349fda124b62d1f4cf4b2
13
+ - KEEN_PROJECT_ID=50e5ffa6897a2c319b000000
14
+
15
+ script:
16
+ - bundle exec rake spec
17
+ - bundle exec rake integration
data/Gemfile CHANGED
@@ -1,14 +1,32 @@
1
- source "http://rubygems.org"
2
- # Add dependencies required to use your gem here.
3
- gem "json", ">= 1.6.5"
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem 'rake'
7
+ gem 'rspec'
8
+ gem 'em-http-request'
9
+ gem 'webmock'
10
+ end
4
11
 
5
- # Add dependencies to develop your gem here.
6
- # Include everything needed to run rake, tests, features, etc.
7
12
  group :development do
8
- gem "shoulda", ">= 0"
9
- gem "rdoc", "~> 3.12"
10
- gem "bundler", ">= 1.1.4"
11
- gem "jeweler", "~> 1.8.3"
12
- gem "rspec", ">= 2.9.0"
13
- gem "cucumber", ">= 1.2.1"
13
+ # guard cross-platform listener trick
14
+ gem 'rb-inotify', :require => false
15
+ gem 'rb-fsevent', :require => false
16
+ gem 'rb-fchange', :require => false
17
+
18
+ # guard notifications
19
+ gem 'ruby_gntp'
20
+
21
+ # fix guard prompt
22
+ gem 'rb-readline' # or compile ruby w/ readline
23
+
24
+ # guard
25
+ gem 'guard'
26
+ gem 'guard-rspec'
27
+
28
+ # debuggers
29
+ gem 'ruby-debug', :platforms => :mri_18
30
+ gem 'debugger', :platforms => :mri_19
14
31
  end
32
+
@@ -0,0 +1,16 @@
1
+ group :unit do
2
+ guard 'rspec', :spec_paths => "spec/keen" do
3
+ watch('spec/spec_helper.rb') { "spec" }
4
+ watch('spec/keen/spec_helper.rb') { "spec" }
5
+ watch(%r{^spec/keen/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/keen/#{m[1]}_spec.rb" }
7
+ end
8
+ end
9
+
10
+ group :integration do
11
+ guard 'rspec', :spec_paths => "spec/integration" do
12
+ watch('spec/spec_helper.rb') { "spec" }
13
+ watch('spec/integration/spec_helper.rb') { "spec" }
14
+ watch(%r{^spec/integration/.+_spec\.rb$})
15
+ end
16
+ end
File without changes
data/README.md CHANGED
@@ -1,33 +1,105 @@
1
- Keen Ruby Client Library
2
- ========================
3
-
4
- Usage
5
- -----
6
-
7
- `gem install keen`
8
-
9
- Ruby Client Usage Guide
10
- http://keen.io/static/docs/clients/ruby/usage_guide.html
11
-
12
- see Also the Cucumber and Rspec stuff in /features/ for usage details)
13
-
14
- Contributing to keen
15
- --------------------
16
-
17
- * If you have gem release access, use Jeweler to increase version & release:
18
- * rake version:bump:major
19
- * rake release
20
- * Make sure all tests pass in 1.9.2 and 1.9.3
21
- * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
22
- * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
23
- * Fork the project.
24
- * Start a feature/bugfix branch.
25
- * Commit and push until you are happy with your contribution.
26
- * Make sure to add tests for it. This is important so we don't break it in a future version unintentionally. If at all possible, please use BDD principles to drive your feature development with Cucumber.
27
- * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
28
-
29
- Copyright
30
- ---------
31
-
32
- Copyright (c) 2012 Keen Labs, Inc. See LICENSE.txt for further details.
1
+ # Keen IO Official Ruby Client Library
2
+
3
+ [![Build Status](https://secure.travis-ci.org/keenlabs/keen-gem.png?branch=master)](http://travis-ci.org/keenlabs/keen-gem)
4
+
5
+ keen-gem is the official Ruby Client for the [Keen IO](https://keen.io/) API. The
6
+ Keen IO API lets developers build analytics features directly into their apps.
7
+
8
+ ### Installation
9
+
10
+ Add to your Gemfile:
11
+
12
+ gem 'keen'
13
+
14
+ or install from Rubygems:
15
+
16
+ gem install keen
17
+
18
+ keen is tested with Ruby 1.8 and 1.9 on:
19
+
20
+ * MRI
21
+ * Rubinius
22
+ * jRuby (except for asynchronous methods - no TLS support for EM on jRuby)
23
+
24
+ ### Usage
25
+
26
+ Before making any API calls, you must supply the keen gem with a Project ID and an API Key.
27
+ (If you need a Keen IO account, [sign up here](https://keen.io/) - it's free.)
28
+
29
+ The recommended way to do this is to set `KEEN_PROJECT_ID` and `KEEN_API_KEY` in your
30
+ environment. If you're using [foreman](http://ddollar.github.com/foreman/), add this to your `.env` file:
31
+
32
+ KEEN_PROJECT_ID=your-project-id
33
+ KEEN_API_KEY=your-api-key
34
+
35
+ When you deploy, make sure your production environment variables are also set. For example,
36
+ set [config vars](https://devcenter.heroku.com/articles/config-vars) on Heroku. (We recommend this
37
+ environment-based approach because it keeps sensitive credentials out of the codebase. If you can't do this, see the alternatives below.)
38
+
39
+ If your environment is set up property, `Keen` is ready go immediately. Publish an event like this:
40
+
41
+ Keen.publish("sign_ups", { :username => "lloyd", :referred_by => "harry" })
42
+
43
+ This will publish an event to the 'sign_ups' collection with the `username` and `referred_by` properties set.
44
+
45
+ The event properties are arbitrary JSON, and the event collection need not exist in advance.
46
+ If it doesn't exist, Keen IO will create it on the first request.
47
+
48
+ You can learn more about data modeling with Keen IO with the [Data Modeling Guide](https://keen.io/docs/event-data-modeling/event-data-intro/).
49
+
50
+ ### Asynchronous publishing
51
+
52
+ Publishing events shouldn't slow your application down. It shouldn't make your
53
+ users wait longer for their request to finish.
54
+
55
+ The Keen IO API is fast, but any synchronous network call you make will
56
+ negatively impact response times. For this reason, we recommend you use the `publish_async`
57
+ method to send events.
58
+
59
+ To compare asychronous vs. synchronous performance, check out the [keen-gem-example](http://keen-gem-example.herokuapp.com/) app.
60
+
61
+ To publish asynchronously, first add
62
+ [em-http-request](https://github.com/igrigorik/em-http-request) to your Gemfile.
63
+
64
+ Next, run an instance of EventMachine. If you're using an EventMachine-based web server like
65
+ thin or goliath you're already doing this. Otherwise, you'll need to start an EventMachine loop manually as follows:
66
+
67
+ Thread.new { EventMachine.run }
68
+
69
+ The best place for this is in an initializer, or anywhere that runs when your app boots up.
70
+ Here's a good blog article that explains more about this approach - [EventMachine and Passenger](http://railstips.org/blog/archives/2011/05/04/eventmachine-and-passenger/).
71
+
72
+ Now, in your code, replace `publish` with `publish_async`. Bind callbacks if you require them.
73
+
74
+ http = Keen.publish_async("sign_ups", { :username => "lloyd", :referred_by => "harry" })
75
+ http.callback { |response| puts "Success: #{response}"}
76
+ http.errback { puts "was a failurrr :,(" }
77
+
78
+ This will schedule the network call into the event loop and allow your request thread
79
+ to resume processing immediately.
80
+
81
+ ### Other code examples
82
+
83
+ ##### Authentication
84
+
85
+ To configure the keen gem credentials in code, do as follows:
86
+
87
+ Keen.project_id = 'your-project-id'
88
+ Keen.api_key = 'your-api-key'
89
+
90
+ You can also configure individual client instances as follows:
91
+
92
+ keen = new Keen::Client.new(:project_id => 'your-project-id',
93
+ :api_key => 'your-api'key)
94
+
95
+ ##### On keen.io
96
+
97
+ For more information and examples visit the
98
+ Keen IO [Ruby Usage Guide](https://keen.io/docs/clients/ruby/usage-guide/).
99
+
100
+ ### Questions & Support
101
+
102
+ If you have any questions, bugs, or suggestions, please
103
+ report them via Github Issues. Or, come chat with us anytime
104
+ at [users.keen.io](http://users.keen.io). We'd love to hear your feedback and ideas!
33
105
 
data/Rakefile CHANGED
@@ -1,48 +1,15 @@
1
- # encoding: utf-8
2
-
3
- require 'rubygems'
4
1
  require 'bundler'
5
- begin
6
- Bundler.setup(:default, :development)
7
- rescue Bundler::BundlerError => e
8
- $stderr.puts e.message
9
- $stderr.puts "Run `bundle install` to install missing gems"
10
- exit e.status_code
11
- end
12
- require 'rake'
2
+ require 'rspec/core/rake_task'
13
3
 
14
- require 'jeweler'
15
- Jeweler::Tasks.new do |gem|
16
- # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
- gem.name = "keen"
18
- gem.homepage = "http://github.com/dorkitude/keen"
19
- gem.license = "MIT"
20
- gem.summary = %Q{A library for batching and sending arbitrary events to the Keen API at http://keen.io}
21
- gem.description = %Q{See the github repo or examples.rb for usage information.}
22
- gem.email = "kyle@keen.io"
23
- gem.authors = ["dorkitude"]
24
- # dependencies defined in Gemfile
4
+ desc "Run Rspec unit tests"
5
+ RSpec::Core::RakeTask.new(:spec) do |t|
6
+ t.pattern = "spec/keen/**/*_spec.rb"
25
7
  end
26
- Jeweler::RubygemsDotOrgTasks.new
27
8
 
28
- require 'rake/testtask'
29
- Rake::TestTask.new(:test) do |test|
30
- test.libs << 'lib' << 'test'
31
- test.pattern = 'test/**/test_*.rb'
32
- test.verbose = true
9
+ desc "Run Rspec integration tests"
10
+ RSpec::Core::RakeTask.new(:integration) do |t|
11
+ t.pattern = "spec/integration/**/*_spec.rb"
33
12
  end
34
13
 
35
- require 'cucumber/rake/task'
36
- Cucumber::Rake::Task.new(:features)
37
-
38
- task :default => :test
39
-
40
- require 'rdoc/task'
41
- Rake::RDocTask.new do |rdoc|
42
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
43
-
44
- rdoc.rdoc_dir = 'rdoc'
45
- rdoc.title = "keen #{version}"
46
- rdoc.rdoc_files.include('README*')
47
- rdoc.rdoc_files.include('lib/**/*.rb')
48
- end
14
+ task :default => :spec
15
+ task :test => [:spec]
File without changes
@@ -1,77 +1,22 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
1
  # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "keen/version"
5
4
 
6
5
  Gem::Specification.new do |s|
7
- s.name = "keen"
8
- s.version = "0.3.0"
6
+ s.name = "keen"
7
+ s.version = Keen::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Kyle Wild", "Josh Dzielak"]
10
+ s.email = "josh@keen.io"
11
+ s.homepage = "https://github.com/keenlabs/keen-gem"
12
+ s.summary = "Keen IO API Client"
13
+ s.description = "Batch and send events to the Keen IO API. Supports asychronous requests."
9
14
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["dorkitude"]
12
- s.date = "2012-11-26"
13
- s.description = "See the github repo or examples.rb for usage information."
14
- s.email = "kyle@keen.io"
15
- s.extra_rdoc_files = [
16
- "LICENSE.txt",
17
- "README.md"
18
- ]
19
- s.files = [
20
- ".rvmrc",
21
- "Gemfile",
22
- "LICENSE.txt",
23
- "README.md",
24
- "Rakefile",
25
- "VERSION.yml",
26
- "conf/cacert.pem",
27
- "examples.rb",
28
- "features/add_event.feature",
29
- "features/step_definitions/keen_steps.rb",
30
- "features/support/before_and_after.rb",
31
- "features/support/env.rb",
32
- "keen.gemspec",
33
- "keen.gemspec.old",
34
- "lib/keen.rb",
35
- "lib/keen/client.rb",
36
- "lib/keen/event.rb",
37
- "lib/keen/keys.rb",
38
- "lib/keen/utils.rb",
39
- "lib/keen/version.rb"
40
- ]
41
- s.homepage = "http://github.com/dorkitude/keen"
42
- s.licenses = ["MIT"]
43
- s.require_paths = ["lib"]
44
- s.rubygems_version = "1.8.19"
45
- s.summary = "A library for batching and sending arbitrary events to the Keen API at http://keen.io"
46
-
47
- if s.respond_to? :specification_version then
48
- s.specification_version = 3
15
+ s.add_dependency "multi_json", "~> 1.0"
16
+ s.add_dependency "jruby-openssl" if defined?(JRUBY_VERSION)
49
17
 
50
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
51
- s.add_runtime_dependency(%q<json>, [">= 1.6.5"])
52
- s.add_development_dependency(%q<shoulda>, [">= 0"])
53
- s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
54
- s.add_development_dependency(%q<bundler>, [">= 1.1.4"])
55
- s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
56
- s.add_development_dependency(%q<rspec>, [">= 2.9.0"])
57
- s.add_development_dependency(%q<cucumber>, [">= 1.2.1"])
58
- else
59
- s.add_dependency(%q<json>, [">= 1.6.5"])
60
- s.add_dependency(%q<shoulda>, [">= 0"])
61
- s.add_dependency(%q<rdoc>, ["~> 3.12"])
62
- s.add_dependency(%q<bundler>, [">= 1.1.4"])
63
- s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
64
- s.add_dependency(%q<rspec>, [">= 2.9.0"])
65
- s.add_dependency(%q<cucumber>, [">= 1.2.1"])
66
- end
67
- else
68
- s.add_dependency(%q<json>, [">= 1.6.5"])
69
- s.add_dependency(%q<shoulda>, [">= 0"])
70
- s.add_dependency(%q<rdoc>, ["~> 3.12"])
71
- s.add_dependency(%q<bundler>, [">= 1.1.4"])
72
- s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
73
- s.add_dependency(%q<rspec>, [">= 2.9.0"])
74
- s.add_dependency(%q<cucumber>, [">= 1.2.1"])
75
- end
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
76
22
  end
77
-
@@ -1,5 +1,47 @@
1
+ require 'logger'
2
+ require 'forwardable'
3
+ require 'multi_json'
4
+
1
5
  require 'keen/client'
2
- require 'keen/event'
3
- require 'keen/keys'
4
- require 'keen/utils'
5
- require 'keen/version'
6
+
7
+ module Keen
8
+ class Error < RuntimeError
9
+ attr_accessor :original_error
10
+ def initialize(message, _original_error=nil)
11
+ self.original_error = _original_error
12
+ super(message)
13
+ end
14
+ end
15
+
16
+ class ConfigurationError < Error; end
17
+ class HttpError < Error; end
18
+ class BadRequestError < HttpError; end
19
+ class AuthenticationError < HttpError; end
20
+ class NotFoundError < HttpError; end
21
+
22
+ class << self
23
+ extend Forwardable
24
+
25
+ def_delegators :default_client, :project_id, :api_key,
26
+ :project_id=, :api_key=, :publish, :publish_async
27
+
28
+ attr_writer :logger
29
+
30
+ def logger
31
+ @logger ||= lambda {
32
+ logger = Logger.new($stdout)
33
+ logger.level = Logger::INFO
34
+ logger
35
+ }.call
36
+ end
37
+
38
+ private
39
+
40
+ def default_client
41
+ @default_client || Keen::Client.new(
42
+ :project_id => ENV['KEEN_PROJECT_ID'],
43
+ :api_key => ENV['KEEN_API_KEY']
44
+ )
45
+ end
46
+ end
47
+ end
@@ -1,94 +1,118 @@
1
- require "json"
2
- require "uri"
3
- require "time"
4
- require "net/http"
5
- require "net/https"
6
-
1
+ require 'keen/version'
2
+ require 'keen/http'
7
3
 
8
4
  module Keen
9
5
  class Client
10
- attr_accessor :project_id, :api_key, :options, :logging
11
-
12
- def base_url
13
- if @options[:base_url]
14
- @options[:base_url]
15
- else
16
- "https://api.keen.io/3.0"
6
+ attr_accessor :project_id, :api_key
7
+
8
+ CONFIG = {
9
+ :api_host => "api.keen.io",
10
+ :api_port => 443,
11
+ :api_sync_http_options => {
12
+ :use_ssl => true,
13
+ :verify_mode => OpenSSL::SSL::VERIFY_PEER,
14
+ :verify_depth => 5,
15
+ :ca_file => File.expand_path("../../../config/cacert.pem", __FILE__) },
16
+ :api_async_http_options => {},
17
+ :api_headers => {
18
+ "Content-Type" => "application/json",
19
+ "User-Agent" => "keen-gem v#{Keen::VERSION}"
20
+ }
21
+ }
22
+
23
+ def initialize(*args)
24
+ options = args[0]
25
+ unless options.is_a?(Hash)
26
+ # deprecated, pass a hash of options instead
27
+ options = {
28
+ :project_id => args[0],
29
+ :api_key => args[1],
30
+ }.merge(args[2] || {})
17
31
  end
18
- end
19
32
 
20
- def ssl_cert_file
21
- File.expand_path("../../../conf/cacert.pem", __FILE__)
33
+ @project_id, @api_key = options.values_at(
34
+ :project_id, :api_key)
22
35
  end
23
36
 
24
- def initialize(project_id, api_key, options = {})
25
- raise "project_id must be string" unless project_id.kind_of? String
26
- raise "api_key must be string" unless api_key.kind_of? String
37
+ def publish(event_name, properties)
38
+ check_configuration!
39
+ begin
40
+ response = Keen::HTTP::Sync.new(
41
+ api_host, api_port, api_sync_http_options).post(
42
+ :path => api_path(event_name),
43
+ :headers => api_headers_with_auth,
44
+ :body => MultiJson.encode(properties))
45
+ rescue Exception => http_error
46
+ raise HttpError.new("Couldn't connect to Keen IO: #{http_error.message}", http_error)
47
+ end
48
+ process_response(response.code, response.body.chomp)
49
+ end
27
50
 
28
- default_options = {
29
- :logging => true,
51
+ def publish_async(event_name, properties)
52
+ check_configuration!
53
+
54
+ deferrable = EventMachine::DefaultDeferrable.new
55
+
56
+ http_client = Keen::HTTP::Async.new(api_host, api_port, api_async_http_options)
57
+ http = http_client.post({
58
+ :path => api_path(event_name),
59
+ :headers => api_headers_with_auth,
60
+ :body => MultiJson.encode(properties)
61
+ })
62
+ http.callback {
63
+ begin
64
+ response = process_response(http.response_header.status, http.response.chomp)
65
+ deferrable.succeed(response)
66
+ rescue Exception => e
67
+ deferrable.fail(e)
68
+ end
69
+ }
70
+ http.errback {
71
+ Keen.logger.warn("Couldn't connect to Keen IO: #{http.error}")
72
+ deferrable.fail(Error.new("Couldn't connect to Keen IO: #{http.error}"))
30
73
  }
31
74
 
32
- options = default_options.update(options)
33
-
34
- @project_id = project_id
35
- @api_key = api_key
36
-
37
- @logging = options[:logging]
38
- @options = options
75
+ deferrable
39
76
  end
40
77
 
41
- def add_event(event_collection, event_properties, timestamp=nil)
42
- #
43
- # `event_collection` should be a string
44
- #
45
- # `event` should be a JSON-serializable hash
46
- #
47
- # `timestamp` is optional. If sent, it should be a Time instance.
48
- # If it's not sent, we'll use the current time.
49
-
50
- validate_event_collection(event_collection)
51
-
52
- if timestamp
53
- timestamp = timestamp.utc.iso8601
54
- end
55
-
56
- event = Keen::Event.new(event_collection, event_properties)
78
+ # deprecated
79
+ def add_event(event_name, properties, options={})
80
+ self.publish(event_name, properties, options)
81
+ end
57
82
 
58
- # build the request:
59
- if event.properties.is_a?(Array)
60
- url = "#{base_url}/projects/#{project_id}/events"
61
- body = {event_collection => event.properties}
83
+ private
84
+
85
+ def process_response(status_code, response_body)
86
+ body = MultiJson.decode(response_body)
87
+ case status_code.to_i
88
+ when 200..201
89
+ return body
90
+ when 400
91
+ raise BadRequestError.new(body)
92
+ when 401
93
+ raise AuthenticationError.new(body)
94
+ when 404
95
+ raise NotFoundError.new(body)
62
96
  else
63
- url = "#{base_url}/projects/#{project_id}/events/#{event_collection}"
64
- body = event.properties
65
- end
66
- uri = URI.parse(url)
67
- http = Net::HTTP.new(uri.host, uri.port)
68
- http.use_ssl = true
69
- http.ca_file = ssl_cert_file
70
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
71
- http.verify_depth = 5
72
-
73
- request = Net::HTTP::Post.new(uri.path)
74
-
75
- if timestamp
76
- request.body[:keen] = {
77
- :timestamp => timestamp
78
- }
97
+ raise HttpError.new(body)
79
98
  end
99
+ end
80
100
 
81
- request.body = JSON.generate(body)
101
+ def api_path(collection)
102
+ "/3.0/projects/#{project_id}/events/#{collection}"
103
+ end
82
104
 
83
- request["Content-Type"] = "application/json"
84
- request["Authorization"] = @api_key
105
+ def api_headers_with_auth
106
+ api_headers.merge("Authorization" => api_key)
107
+ end
85
108
 
86
- response = http.request(request)
87
- JSON.parse response.body
109
+ def check_configuration!
110
+ raise ConfigurationError, "Project ID must be set" unless project_id
111
+ raise ConfigurationError, "API Key must be set" unless api_key
88
112
  end
89
113
 
90
- def validate_event_collection(event_collection)
91
- # TODO
114
+ def method_missing(_method, *args, &block)
115
+ CONFIG[_method.to_sym] || super
92
116
  end
93
117
  end
94
118
  end