soleone-highrise 0.13.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/CHANGELOG +91 -0
- data/MIT-LICENSE +20 -0
- data/README.mkdn +86 -0
- data/Rakefile +46 -0
- data/VERSION.yml +4 -0
- data/autotest/discover.rb +3 -0
- data/examples/config_initializers_highrise.rb +12 -0
- data/examples/extending.rb +31 -0
- data/examples/sample.rb +10 -0
- data/highrise.gemspec +131 -0
- data/install.rb +1 -0
- data/lib/cachable.rb +77 -0
- data/lib/highrise.rb +22 -0
- data/lib/highrise/base.rb +9 -0
- data/lib/highrise/comment.rb +4 -0
- data/lib/highrise/company.rb +14 -0
- data/lib/highrise/curlhelper.rb +27 -0
- data/lib/highrise/curly.rb +101 -0
- data/lib/highrise/deal.rb +35 -0
- data/lib/highrise/email.rb +9 -0
- data/lib/highrise/group.rb +7 -0
- data/lib/highrise/kase.rb +8 -0
- data/lib/highrise/membership.rb +4 -0
- data/lib/highrise/note.rb +9 -0
- data/lib/highrise/pagination.rb +29 -0
- data/lib/highrise/person.rb +43 -0
- data/lib/highrise/subject.rb +50 -0
- data/lib/highrise/tag.rb +18 -0
- data/lib/highrise/taggable.rb +33 -0
- data/lib/highrise/task.rb +26 -0
- data/lib/highrise/user.rb +11 -0
- data/spec/cachable_spec.rb +68 -0
- data/spec/highrise/base_spec.rb +13 -0
- data/spec/highrise/comment_spec.rb +14 -0
- data/spec/highrise/companies/16883216.html +1723 -0
- data/spec/highrise/company_spec.rb +70 -0
- data/spec/highrise/curlhelper_spec.rb +35 -0
- data/spec/highrise/curly_spec.rb +36 -0
- data/spec/highrise/email_spec.rb +25 -0
- data/spec/highrise/group_spec.rb +14 -0
- data/spec/highrise/kase_spec.rb +25 -0
- data/spec/highrise/membership_spec.rb +14 -0
- data/spec/highrise/note_spec.rb +23 -0
- data/spec/highrise/pagination_spec.rb +10 -0
- data/spec/highrise/people/16887003.html +1733 -0
- data/spec/highrise/person_spec.rb +83 -0
- data/spec/highrise/subject_spec.rb +43 -0
- data/spec/highrise/tag_spec.rb +24 -0
- data/spec/highrise/task_spec.rb +23 -0
- data/spec/highrise/user_spec.rb +30 -0
- data/spec/spec.opts +7 -0
- data/spec/spec_helper.rb +20 -0
- data/uninstall.rb +1 -0
- metadata +176 -0
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
data/lib/cachable.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# Caching is a way to speed up slow ActiveResource queries by keeping the result of
|
2
|
+
# a request around to be reused by subsequent requests.
|
3
|
+
#
|
4
|
+
# Caching is turned OFF by default.
|
5
|
+
#
|
6
|
+
# == Usage
|
7
|
+
#
|
8
|
+
# require 'cachable'
|
9
|
+
#
|
10
|
+
# module CachedResource
|
11
|
+
# class Base < ActiveResource::Base
|
12
|
+
# end
|
13
|
+
# class ActiveResource::Connection
|
14
|
+
# include Cachable
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# == Caching stores
|
20
|
+
#
|
21
|
+
# All the caching stores from ActiveSupport::Cache are available
|
22
|
+
# as backends for caching. See the Rails rdoc for more information on
|
23
|
+
# these stores
|
24
|
+
#
|
25
|
+
# === Configuration examples ('off' is the default):
|
26
|
+
# CachedResource.connection.cache_store = ActiveSupport::Cache.lookup_store :memory_store
|
27
|
+
# CachedResource.connection.cache_store = ActiveSupport::Cache.lookup_store :file_store, "/path/to/cache/directory"
|
28
|
+
# CachedResource.connection.cache_store = ActiveSupport::Cache.lookup_store :drb_store, "druby://localhost:9192"
|
29
|
+
# CachedResource.connection.cache_store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost"
|
30
|
+
# CachedResource.connection.cache_store = MyOwnStore.new("parameter")
|
31
|
+
#
|
32
|
+
# === If you are using a store that has write options, you can set them
|
33
|
+
# CachedResource.connection.store_options = { :expires_in => 60.seconds }
|
34
|
+
#
|
35
|
+
# Note: To ensure that caching is turned off, set CachedResource.connection.cache_store = nil
|
36
|
+
#
|
37
|
+
# FYI: You can use this with *any* active resource interface, not just Highrise.
|
38
|
+
|
39
|
+
module Cachable
|
40
|
+
def self.included(base)
|
41
|
+
base.class_eval do
|
42
|
+
include InstanceMethods
|
43
|
+
alias_method_chain :get, :cache
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module InstanceMethods
|
48
|
+
attr_writer :cache_store, :store_options
|
49
|
+
|
50
|
+
def cache_store
|
51
|
+
@cache_store ||= nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def store_options
|
55
|
+
@store_options ||= {}
|
56
|
+
end
|
57
|
+
|
58
|
+
def is_caching?
|
59
|
+
!@cache_store.nil?
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def get_with_cache(path, headers = {})
|
65
|
+
return get_without_cache(path, headers) unless is_caching?
|
66
|
+
fetch(path) { get_without_cache(path, headers) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def cache_key(*args)
|
70
|
+
args.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
def fetch(args, &block)
|
74
|
+
cache_store.fetch(cache_key(args), store_options, &block).dup
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/highrise.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'active_resource'
|
2
|
+
require 'activesupport'
|
3
|
+
require 'active_support/basic_object'
|
4
|
+
|
5
|
+
require File.dirname(__FILE__) + '/highrise/base'
|
6
|
+
require File.dirname(__FILE__) + '/highrise/pagination'
|
7
|
+
require File.dirname(__FILE__) + '/highrise/curly'
|
8
|
+
require File.dirname(__FILE__) + '/highrise/curlhelper'
|
9
|
+
require File.dirname(__FILE__) + '/highrise/taggable'
|
10
|
+
require File.dirname(__FILE__) + '/highrise/subject'
|
11
|
+
require File.dirname(__FILE__) + '/highrise/comment'
|
12
|
+
require File.dirname(__FILE__) + '/highrise/company'
|
13
|
+
require File.dirname(__FILE__) + '/highrise/email'
|
14
|
+
require File.dirname(__FILE__) + '/highrise/group'
|
15
|
+
require File.dirname(__FILE__) + '/highrise/kase'
|
16
|
+
require File.dirname(__FILE__) + '/highrise/membership'
|
17
|
+
require File.dirname(__FILE__) + '/highrise/note'
|
18
|
+
require File.dirname(__FILE__) + '/highrise/person'
|
19
|
+
require File.dirname(__FILE__) + '/highrise/task'
|
20
|
+
require File.dirname(__FILE__) + '/highrise/user'
|
21
|
+
require File.dirname(__FILE__) + '/highrise/tag'
|
22
|
+
require File.dirname(__FILE__) + '/highrise/deal'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Highrise
|
2
|
+
class Company < Subject
|
3
|
+
include Pagination
|
4
|
+
include Taggable
|
5
|
+
|
6
|
+
def self.find_all_across_pages_since(time)
|
7
|
+
find_all_across_pages(:params => { :since => time.utc.to_s(:db).gsub(/[^\d]/, '') })
|
8
|
+
end
|
9
|
+
|
10
|
+
def people
|
11
|
+
Person.find(:all, :from => "/companies/#{id}/people.xml")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Highrise
|
2
|
+
class CurlHelper
|
3
|
+
|
4
|
+
# thanks mislav ;) http://github.com/mislav/curly/tree/master
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@curly = Curly.new
|
8
|
+
@curly.http_auth_types = Curl::CURLAUTH_BASIC
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_userpwd_from_url(url)
|
12
|
+
userpwd_from_url_regex = /(http|https):\/\/((?:[\w\.\-\+%!$&'\(\)*\+,;=]+:)*[\w\.\-\+%!$&'\(\)*\+,;=]+)?/
|
13
|
+
match = userpwd_from_url_regex.match(url)
|
14
|
+
match.captures[1] unless match.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_document(url)
|
18
|
+
@curly.userpwd = get_userpwd_from_url(url)
|
19
|
+
@curly.get(url).doc
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_document_from_id(collection_name_and_id)
|
23
|
+
get_document("#{Highrise::Base.site.to_s}#{collection_name_and_id}")
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'hpricot'
|
2
|
+
require 'iconv'
|
3
|
+
require 'curb'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
class Curly < ActiveSupport::BasicObject
|
7
|
+
attr_reader :uri
|
8
|
+
|
9
|
+
def initialize(uri = nil)
|
10
|
+
@curl = Curl::Easy.new
|
11
|
+
self.uri = uri
|
12
|
+
self.follow_location = true
|
13
|
+
yield self if block_given?
|
14
|
+
end
|
15
|
+
|
16
|
+
def uri=(obj)
|
17
|
+
case obj
|
18
|
+
when String
|
19
|
+
unless @uri
|
20
|
+
@uri = URI.parse(obj)
|
21
|
+
else
|
22
|
+
@uri += obj
|
23
|
+
end
|
24
|
+
when URI::HTTP
|
25
|
+
@uri = obj
|
26
|
+
when nil
|
27
|
+
return
|
28
|
+
else
|
29
|
+
raise "unsupported URI type (#{obj.class.name} given)"
|
30
|
+
end
|
31
|
+
|
32
|
+
self.url = @uri.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def method_missing(method, *args, &block)
|
36
|
+
@curl.send(method, *args, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def cookiejar=(filename)
|
40
|
+
self.enable_cookies = true
|
41
|
+
@curl.cookiejar = filename
|
42
|
+
end
|
43
|
+
|
44
|
+
def get(uri = nil)
|
45
|
+
self.uri = uri
|
46
|
+
http_get
|
47
|
+
raise "expected 2xx, got #{response_code} (GET #{url})" unless success?
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def success?
|
52
|
+
response_code >= 200 and response_code < 300
|
53
|
+
end
|
54
|
+
|
55
|
+
def doc
|
56
|
+
Hpricot body_unicode
|
57
|
+
end
|
58
|
+
|
59
|
+
def encoding
|
60
|
+
return @encoding unless @encoding == false
|
61
|
+
@encoding = if body_str =~ /;\s*charset=([\w-]+)\s*['"]/
|
62
|
+
$1.downcase
|
63
|
+
else
|
64
|
+
false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def post(params)
|
69
|
+
fields = params.map do |key, value|
|
70
|
+
Curl::PostField.content(key.to_s, value.to_s)
|
71
|
+
end
|
72
|
+
http_post *fields
|
73
|
+
end
|
74
|
+
|
75
|
+
class Form
|
76
|
+
def initialize(element)
|
77
|
+
@node = element
|
78
|
+
end
|
79
|
+
|
80
|
+
def elements
|
81
|
+
@node.search('input, button, select, textarea')
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def body_unicode
|
88
|
+
body = body_str
|
89
|
+
if encoding and encoding != 'utf-8'
|
90
|
+
body = Iconv.conv('UTF-8', encoding, body)
|
91
|
+
end
|
92
|
+
body
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
Hpricot::Doc.class_eval do
|
98
|
+
def forms
|
99
|
+
search('form').map { |f| Curly::Form.new(f) }
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Highrise
|
2
|
+
# name
|
3
|
+
# background
|
4
|
+
# status (pending, won, lost)
|
5
|
+
# category-id
|
6
|
+
# ---------------------------
|
7
|
+
# account-id
|
8
|
+
# author-id
|
9
|
+
# created-at
|
10
|
+
# currency
|
11
|
+
# duration
|
12
|
+
# group-id
|
13
|
+
# owner-id
|
14
|
+
# party-id
|
15
|
+
# price type
|
16
|
+
# price-type
|
17
|
+
# responsible-party-id
|
18
|
+
# status-changed-on
|
19
|
+
# updated-at
|
20
|
+
# visible-to
|
21
|
+
class Deal < Subject
|
22
|
+
|
23
|
+
class DealCategory < Base
|
24
|
+
def self.find_by_name(name)
|
25
|
+
find(:all).select{|category| category.name == name}.first
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.delete!(name)
|
29
|
+
category = self.find_by_name(name)
|
30
|
+
delete(category.id) if category
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Highrise
|
2
|
+
module Pagination
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def find_all_across_pages(options = {})
|
9
|
+
records = []
|
10
|
+
each(options) { |record| records << record }
|
11
|
+
records
|
12
|
+
end
|
13
|
+
|
14
|
+
def each(options = {})
|
15
|
+
options[:params] ||= {}
|
16
|
+
options[:params][:n] = 0
|
17
|
+
|
18
|
+
loop do
|
19
|
+
if (records = self.find(:all, options)).any?
|
20
|
+
records.each { |record| yield record }
|
21
|
+
options[:params][:n] += records.size
|
22
|
+
else
|
23
|
+
break # no people included on that page, thus no more people total
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Highrise
|
2
|
+
class Person < Subject
|
3
|
+
DATE_OCCASIONS = ["Birthday", "Anniversary", "First met", "Hired", "Fired"]
|
4
|
+
|
5
|
+
include Pagination
|
6
|
+
include Taggable
|
7
|
+
|
8
|
+
def self.search_by_name(name)
|
9
|
+
find(:first, :from => "/people/search.xml", :params => {:term => name})
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.find_all_by_tag(tag_name)
|
13
|
+
tag = Tag.find_by_name(tag_name)
|
14
|
+
find(:all, :from => "/tags/#{tag.id}.xml")
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.find_all_across_pages_since(time)
|
18
|
+
find_all_across_pages(:params => { :since => time.utc.to_s(:db).gsub(/[^\d]/, '') })
|
19
|
+
end
|
20
|
+
|
21
|
+
def company
|
22
|
+
Company.find(company_id) if company_id
|
23
|
+
end
|
24
|
+
|
25
|
+
def name
|
26
|
+
"#{first_name rescue ''} #{last_name rescue ''}".strip
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def add_date!(description, date, task_owner_id=nil)
|
31
|
+
description_key = DATE_OCCASIONS.include?(description) ? :description : :custom_description
|
32
|
+
custom_date = { description_key => description, :month => date.month, :day => date.day, :year => date.year }
|
33
|
+
if task_owner_id
|
34
|
+
custom_date[:assign_task] = true
|
35
|
+
custom_date[:task_owner_id] = task_owner_id
|
36
|
+
end
|
37
|
+
# TODO: use different way to add date (current throws 406 from Highrise, but works anyways)
|
38
|
+
self.post(:contact_dates, :contact_date => custom_date) rescue nil
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Highrise
|
2
|
+
class Subject < Base
|
3
|
+
def notes
|
4
|
+
Note.find_all_across_pages(:from => "/#{self.class.collection_name}/#{id}/notes.xml")
|
5
|
+
end
|
6
|
+
|
7
|
+
def emails
|
8
|
+
Email.find_all_across_pages(:from => "/#{self.class.collection_name}/#{id}/emails.xml")
|
9
|
+
end
|
10
|
+
|
11
|
+
def upcoming_tasks
|
12
|
+
Task.find(:all, :from => "/#{self.class.collection_name}/#{id}/tasks.xml")
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_note!(note_body)
|
16
|
+
return if note_body.blank?
|
17
|
+
note = Note.new(:body => note_body)
|
18
|
+
self.post(:notes, {}, note.to_xml)
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_deal!(name, category, status=nil, background=nil)
|
22
|
+
category_object = Deal::DealCategory.find_by_name(category)
|
23
|
+
category_id = category_object ? category_object.id : nil
|
24
|
+
deal = Deal.new(:name => name, :background => background, :status => status || "pending", :category_id => category_id)
|
25
|
+
deal.price_type = 'fixed'
|
26
|
+
deal.party_id = self.id
|
27
|
+
deal.save
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_task!(body, due_date, arguments = {})
|
31
|
+
category = Task::TaskCategory.find_by_name(arguments[:category])
|
32
|
+
category_id = category ? category.id : nil
|
33
|
+
|
34
|
+
task = Task.new(arguments.merge(:body => body, :category_id => category_id))
|
35
|
+
|
36
|
+
if Task::DUE_DATES.include?(due_date.to_s)
|
37
|
+
task.frame = due_date
|
38
|
+
else
|
39
|
+
task.frame = 'specific'
|
40
|
+
task.due_at = due_date
|
41
|
+
end
|
42
|
+
|
43
|
+
task.subject_type = self.instance_of?(Kase) ? 'Kase' : 'Party'
|
44
|
+
task.subject_id = self.id
|
45
|
+
task.save
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|