pipejump 0.2.0

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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,25 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ spec/connection.yml
23
+ coverage
24
+ coverage.data
25
+ test*.rb
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/ruby
2
+ source "http://rubygems.org"
3
+
4
+ gem 'rspec', '1.3.0'
5
+ gem 'rake'
6
+ gem 'jeweler'
7
+ gem 'rcov'
data/Gemfile.lock ADDED
@@ -0,0 +1,25 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ gemcutter (0.5.0)
5
+ json_pure
6
+ git (1.2.5)
7
+ jeweler (1.4.0)
8
+ gemcutter (>= 0.1.0)
9
+ git (>= 1.2.5)
10
+ rubyforge (>= 2.0.0)
11
+ json_pure (1.4.6)
12
+ rake (0.8.7)
13
+ rcov (0.9.8)
14
+ rspec (1.3.0)
15
+ rubyforge (2.0.3)
16
+ json_pure (>= 1.1.7)
17
+
18
+ PLATFORMS
19
+ ruby
20
+
21
+ DEPENDENCIES
22
+ jeweler
23
+ rake
24
+ rcov
25
+ rspec (= 1.3.0)
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Marcin Bunsch
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,120 @@
1
+ *pipejump* gem is a Ruby-based client for the API of the Pipejump sales app.
2
+
3
+ == Requirements
4
+
5
+ There are no dependencies required to use this code.
6
+
7
+ == Installation
8
+
9
+ The gem available via Rubygems. To install it, use the following command:
10
+
11
+
12
+ sudo gem install pipejump
13
+
14
+
15
+ To get the latest version, clone the gem from github and run rake install:
16
+
17
+
18
+ git clone http://github.com/pipejump/pipejump
19
+ cd pipejump
20
+ rake install
21
+
22
+
23
+ == Usage
24
+
25
+ Before using this gem you must require it by calling:
26
+
27
+
28
+ require 'pipejump'
29
+
30
+
31
+ === Session
32
+
33
+ To use the client, you must first authenticate the client with the API by initializing an instance of the Pipejump::Session class. You will later use this instance for communication with the API.
34
+
35
+
36
+ @session = Pipejump::Session.new(:email => 'you@email.com', :password => 'your_password')
37
+
38
+
39
+ If the credentials are incorrect, this will raise a Pipejump::AuthenticationFailed error.
40
+
41
+ If authentication succeeds, this will return a new Pipejump::Session instance which you can use to communicate with the API.
42
+
43
+ As of version 0.1.1 you can use the token which is fetched when authenticating using the email and password. So once you get the token, you can use it for future initialization of the Session and not send the username and password, which is a more secure.
44
+
45
+ @session = Pipejump::Session.new(:token => 'your_token')
46
+
47
+ Also, as of version 0.1.1 connection is performed over SSL.
48
+
49
+ For more information on the Session, consult the wiki page at http://github.com/pipejump/pipejump/wiki/Session
50
+
51
+ To access any resources, you need a valid Session instance, referred to as @session in the following examples.
52
+
53
+ === Account
54
+
55
+ You can access your account by calling
56
+
57
+
58
+ @session.account
59
+
60
+
61
+ For more information, consult the wiki page at http://github.com/pipejump/pipejump/wiki/Account
62
+
63
+ === Deals
64
+
65
+ You can access your deals by calling
66
+
67
+
68
+ @session.deals
69
+
70
+
71
+ With Deals you get access to Notes, Reminders and Deal Contacts.
72
+
73
+ For more information, consult the wiki page at http://github.com/pipejump/pipejump/wiki/Deals
74
+
75
+ === Clients
76
+
77
+ You can access your clients by calling
78
+
79
+
80
+ @session.clients
81
+
82
+
83
+ For more information, consult the wiki page at http://github.com/pipejump/pipejump/wiki/Clients
84
+
85
+ === Contacts
86
+
87
+ You can access your contacts by calling
88
+
89
+
90
+ @session.contacts
91
+
92
+
93
+ For more information, consult the wiki page at http://github.com/pipejump/pipejump/wiki/Contacts
94
+
95
+ === Sources
96
+
97
+ You can access your sources by calling
98
+
99
+
100
+ @session.sources
101
+
102
+
103
+ For more information, consult the wiki page at http://github.com/pipejump/pipejump/wiki/Sources
104
+
105
+ == Contribution
106
+
107
+ You're more than welcome to fork and improve this gem. Usual rules:
108
+
109
+ * Fork the project.
110
+ * Make your feature addition or bug fix.
111
+ * Add tests for it. This is important so I don't break it in a
112
+ future version unintentionally.
113
+ * Commit, do not mess with rakefile, version, or history.
114
+ (if you want to have your own version, that is fine but
115
+ bump version in a commit by itself I can ignore when I pull)
116
+ * Send me a pull request.
117
+
118
+ == Copyright
119
+
120
+ Copyright (c) 2010 Pipejump. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,57 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+ require 'rake'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "pipejump"
9
+ gem.summary = %Q{Pipejump API Ruby client}
10
+ gem.description = %Q{Pipejump API Ruby client}
11
+ gem.email = "marcin@pipejump.com"
12
+ gem.homepage = "http://github.com/pipejump/pipejump"
13
+ gem.authors = ["Marcin Bunsch"]
14
+ gem.add_development_dependency "bundler", ">= 0"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+
24
+ begin
25
+ require 'rcov/rcovtask'
26
+
27
+ desc "Run rcov for rspec"
28
+ Spec::Rake::SpecTask.new(:rcov) do |t|
29
+ t.spec_files = FileList['spec/pipejump/**/*_spec.rb']
30
+ t.spec_opts = %w{--color}
31
+ t.rcov = true
32
+ t.rcov_opts = %w{--html --exclude osx\/objc,gems\/,spec\/}
33
+ t.rcov_opts << %[-o "coverage"]
34
+ end
35
+
36
+ rescue LoadError
37
+ task :rcov do
38
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
+ end
40
+ end
41
+
42
+ Spec::Rake::SpecTask.new(:spec) do |t|
43
+ t.spec_files = FileList['spec/pipejump/*_spec.rb', 'spec/pipejump/**/*_spec.rb']
44
+ t.spec_opts = %w{--color}
45
+ end
46
+
47
+ task :default => :spec
48
+
49
+ require 'rake/rdoctask'
50
+ Rake::RDocTask.new do |rdoc|
51
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
52
+
53
+ rdoc.rdoc_dir = 'rdoc'
54
+ rdoc.title = "pipejump-client #{version}"
55
+ rdoc.rdoc_files.include('README*')
56
+ rdoc.rdoc_files.include('lib/**/*.rb')
57
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,117 @@
1
+ module Pipejump
2
+
3
+ # Represents a collection Resources available in the Pipejump API
4
+ #
5
+ # ==== Available Collections:
6
+ # * Clients
7
+ # @session.clients
8
+ # * Contacts
9
+ # @session.contacts
10
+ # * Sources
11
+ # @session.sources
12
+ # * Notes in a Deal
13
+ # @deal.notes
14
+ # * Reminders in a Deal
15
+ # @deal.reminders
16
+ # * Contacts in a Deal
17
+ # @deal.contacts
18
+ # *Please* *note*: This Collection has the following methods disabled: find, create
19
+ #
20
+ # ==== Additional methods:
21
+ # Additionally, each collection allows shorthand calls to the Array returned via all for the following methods:
22
+ # * first
23
+ # * last
24
+ # * each
25
+ # * size
26
+ # * collect
27
+ # * reject
28
+ # ===== Example:
29
+ # @session.sources.size
30
+ # @session.sources.each { |source| }
31
+ class Collection
32
+
33
+ class << self
34
+ # Disable methods specified as arguments
35
+ def disable(*methods) #:nodoc:
36
+ methods.each do |method|
37
+ class_eval <<-STR
38
+ def #{method}; raise NotImplemented; end
39
+ STR
40
+ end
41
+ end
42
+ end
43
+
44
+ # Create a new Collection of Resource objects
45
+ # ==== Arguments
46
+ # * _session_ - Session object
47
+ # * _resource_ - class of the Resource for this collection
48
+ # * _owner_ - a Resource object which owns the collection, applies scope to calls
49
+ # ==== Usage
50
+ # Normally you do not call the constructor directly, instead you go via the Session or Deal instance, like this:
51
+ # @session.clients
52
+ # or
53
+ # @deal.notes
54
+ #
55
+ def initialize(session, resource, owner = nil)
56
+ @session = session
57
+ @resource = resource
58
+ @owner = owner
59
+ @prefix = owner ? owner.element_path : ''
60
+ end
61
+
62
+ # Returns a path to the collection of Resource objects
63
+ def collection_path
64
+ @prefix + '/' + @resource.collection_path.to_s
65
+ end
66
+
67
+ # Returns a path to a single Resource
68
+ def element_path(id)
69
+ @prefix + '/' + @resource.collection_path.to_s + '/' + id.to_s
70
+ end
71
+
72
+ # Returns a single Resource object, based on its _id_
73
+ # ==== Arguments
74
+ # * _id_ - id of Resource
75
+ def find(id)
76
+ code, data = @session.get(element_path(id) + '.json')
77
+ if code == 200
78
+ key = @resource.name.to_s.split('::').last.downcase
79
+ @resource.new(data[key].merge(:session =>@session))
80
+ elsif code == 404
81
+ raise ResourceNotFound
82
+ end
83
+ end
84
+
85
+ # Returns an Array of Resource objects
86
+ def all
87
+ code, data = @session.get(collection_path + '.json')
88
+ data.collect { |data|
89
+ key = @resource.name.to_s.split('::').last.downcase
90
+ @resource.new(data[key].merge(:session => @session, :prefix => @prefix))
91
+ }
92
+ end
93
+
94
+ # Creates and returns a Resource object
95
+ # ==== Arguments
96
+ # * _attrs_ - a Hash of attributes passed to the constructor of the Resource
97
+ def create(attrs)
98
+ resource = @resource.new(attrs.merge(:session => @session, :prefix => @prefix))
99
+ resource.save
100
+ resource
101
+ end
102
+
103
+ def inspect
104
+ all.inspect
105
+ end
106
+
107
+ ['first', 'last', 'each', 'size', 'collect', 'reject'].each do |method|
108
+ class_eval <<-STR
109
+ def #{method}(*args, &block)
110
+ all.#{method}(*args, &block)
111
+ end
112
+ STR
113
+ end
114
+
115
+ end
116
+
117
+ end
@@ -0,0 +1,54 @@
1
+ require 'uri'
2
+ module Pipejump
3
+
4
+ class Connection #:nodoc:
5
+
6
+ attr_accessor :session, :endpoint
7
+
8
+ def initialize(session, endpoint = nil)
9
+ @session = session
10
+ @endpoint = endpoint || 'https://api.pipejump.com'
11
+ end
12
+
13
+ def site
14
+ parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
15
+ parser.parse(self.endpoint)
16
+ end
17
+
18
+ def post(path, data)
19
+ http.post(path, data, headers)
20
+ end
21
+
22
+ def put(path, data)
23
+ http.put(path, data, headers)
24
+ end
25
+
26
+ def get(path)
27
+ http.get(path, headers)
28
+ end
29
+
30
+ def delete(path)
31
+ http.delete(path, headers)
32
+ end
33
+
34
+ def headers
35
+ { 'X-Pipejump-Auth' => session.token.to_s }
36
+ end
37
+
38
+ def http
39
+ instance ||= Net::HTTP.new(site.host, site.port)
40
+ if @endpoint.match(/^https:/)
41
+ instance.use_ssl = true
42
+ instance.verify_mode = OpenSSL::SSL::VERIFY_NONE
43
+ end
44
+ instance
45
+ end
46
+
47
+ def inspect
48
+ "#<#{self.class} endpoint: \"#{endpoint}\">"
49
+ end
50
+
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,11 @@
1
+ module Pipejump
2
+ # Error raised when authentication fails
3
+ class AuthenticationFailed < Exception; end
4
+
5
+ # Error raised when a resource is not found via find
6
+ class ResourceNotFound < Exception; end
7
+ # Error raised when a method is called but is not available for that object
8
+ #
9
+ # Example: Pipejump::Collection of Contact objects within a Deal has the following methods disabled: find, create
10
+ class NotImplemented < Exception; end
11
+ end
@@ -0,0 +1,182 @@
1
+ module Pipejump
2
+ #
3
+ # Represents a Resource available in the Pipejump API
4
+ #
5
+ # The following Resources are available:
6
+ # * Account via @session.account
7
+ # * Client via @session.clients
8
+ # * Contact via @session.contacts
9
+ # * Deal via @session.deals
10
+ # * Note via @deal.notes
11
+ # * Reminder via @deal.reminders
12
+ # * Source via @deal.sources
13
+ #
14
+ # === Retrieving Resources
15
+ # For information on retrieving resources, please consult the Collection class
16
+ #
17
+ # === Creating a Resource
18
+ # To create a resource, call the create method on the appropriate collection.
19
+ #
20
+ # For example, to create a client, you would do the following:
21
+ # @client = @session.clients.create(:name => 'Google')
22
+ # This will return an instance of Pipejump::Client
23
+ #
24
+ # You can access the attributes of the instance via the attributes method:
25
+ # @client.attributes # => { 'id' => 1, 'name' => 'Google' }
26
+ #
27
+ # Pipejump::Resource also provides shortcut accessors for these attributes, similarly to ActiveRecord:
28
+ # @client.name = 'Yahoo'
29
+ # @client.name # => 'Yahoo'
30
+ #
31
+ # If the create fails, errors are available via the errors method
32
+ # @client = @session.clients.create(:name => '')
33
+ # @client.errors # => {"client"=>[{"error"=>{"code"=>"E0001", "field"=>"name", "description"=>"Please enter a client name"}}]}
34
+ #
35
+ # For more information on errors, please consult the Pipejump API documentation
36
+ #
37
+ # === Updating a Resource
38
+ # To update a resource, change the desired attributes and call the save method
39
+ # @client.name = 'Yahoo'
40
+ # @client.save
41
+ #
42
+ # The save method returns the instance on successful save.
43
+ #
44
+ # If the update fails, save returns false and errors are available via the errors method
45
+ # @client.name = ''
46
+ # @client.save # => false
47
+ # @client.errors # => {"client"=>[{"error"=>{"code"=>"E0001", "field"=>"name", "description"=>"Please enter a client name"}}]}
48
+ #
49
+ # For more information on errors, please consult the Pipejump API documentation
50
+ #
51
+ # === Removing a resource
52
+ # To remove a resource, call the destroy method on the Pipejump::Resource instance
53
+ # @client.destroy # => true
54
+ #
55
+ class Resource
56
+
57
+ class << self
58
+ # Returns the pluralized name of the resource used for the collection
59
+ def collection_path(path = nil)
60
+ @collection_path = path if path
61
+ @collection_path || "#{self.to_s.split('::').last.downcase}s"
62
+ end
63
+
64
+ # Naive implementation of belongs_to association
65
+ def belongs_to(klass) #:nodoc:
66
+ class_eval <<-STR
67
+ def #{klass}
68
+ if @attributes['#{klass}'].is_a?(Hash)
69
+ Pipejump::#{klass.to_s.capitalize}.new(@attributes['#{klass}'].merge(:session => @session))
70
+ elsif @attributes['#{klass}_id']
71
+ @session.#{klass}s.find(@attributes['#{klass}_id'])
72
+ end
73
+ end
74
+ STR
75
+ end
76
+
77
+ # Naive implementation of has_many association
78
+ attr_accessor :has_many_blocks #:nodoc:
79
+ def has_many(collection, &block) #:nodoc:
80
+ (self.has_many_blocks ||= {})[collection] = block
81
+ class_eval <<-STR
82
+ def #{collection}
83
+ collection = Collection.new(@session, #{collection.to_s[0..-2].capitalize}, self)
84
+ if self.class.has_many_blocks[:#{collection}]
85
+ (class << collection; self; end).class_eval(&self.class.has_many_blocks[:#{collection}])
86
+ end
87
+ collection
88
+ end
89
+ STR
90
+
91
+ end
92
+
93
+ end
94
+
95
+ attr_accessor :attributes
96
+ # Constructor for the Resource
97
+ def initialize(attrs)
98
+ @session = attrs.delete(:session)
99
+ @prefix = attrs.delete(:prefix) || ''
100
+ @attributes = {}
101
+ load(attrs)
102
+ end
103
+
104
+ def id
105
+ @attributes['id']
106
+ end
107
+
108
+ def load(attrs = {}) #:nodoc:
109
+ attrs.each_pair do |key, value|
110
+ @attributes[key.to_s] = value
111
+ end
112
+ end
113
+
114
+ def method_missing(meth, *args) #:nodoc:
115
+ if meth.to_s[-1].chr == '=' and @attributes[meth.to_s[0..-2]]
116
+ @attributes[meth.to_s[0..-2]] = args.first
117
+ elsif @attributes.has_key?(meth.to_s)
118
+ @attributes[meth.to_s]
119
+ else
120
+ super(meth, args)
121
+ end
122
+ end
123
+
124
+ def klassname #:nodoc:
125
+ self.class.to_s.split('::').last.downcase
126
+ end
127
+
128
+ def to_query
129
+ @attributes.collect { |pair| pair[0] = "#{klassname}[#{pair[0]}]"; pair.join('=') }.join('&')
130
+ end
131
+
132
+ def save
133
+ before_save if respond_to?(:before_save)
134
+ @errors = {}
135
+ code, data = id ? update : create # @session.post('/' + self.class.collection_path.to_s + '.json', to_query)
136
+ if data['errors']
137
+ @errors = data['errors']
138
+ false
139
+ else
140
+ load(data[klassname])
141
+ true
142
+ end
143
+ end
144
+
145
+ def inspect
146
+ "#<#{self.class} #{@attributes.collect { |pair| pair[1] = "\"#{pair[1]}\""; pair.join(': ') }.join(', ')}>"
147
+ end
148
+
149
+ def element_path
150
+ @prefix + '/' + self.class.collection_path.to_s + '/' + id.to_s
151
+ end
152
+
153
+ def collection_path
154
+ @prefix + '/' + self.class.collection_path.to_s
155
+ end
156
+
157
+ def create #:nodoc:
158
+ @session.post(collection_path + '.json', to_query)
159
+ end
160
+
161
+ def update #:nodoc:
162
+ @session.put(element_path + '.json', to_query)
163
+ end
164
+
165
+ # Destroys the Resource
166
+ def destroy
167
+ code, data = @session.delete(element_path + '.json')
168
+ code.to_i == 200
169
+ end
170
+
171
+ # Returns a Hash of errors
172
+ def errors
173
+ @errors ||= {}
174
+ end
175
+
176
+ def created?
177
+ !!id
178
+ end
179
+
180
+ end
181
+
182
+ end