soleone-highrise 0.13.3

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.
Files changed (55) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG +91 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.mkdn +86 -0
  5. data/Rakefile +46 -0
  6. data/VERSION.yml +4 -0
  7. data/autotest/discover.rb +3 -0
  8. data/examples/config_initializers_highrise.rb +12 -0
  9. data/examples/extending.rb +31 -0
  10. data/examples/sample.rb +10 -0
  11. data/highrise.gemspec +131 -0
  12. data/install.rb +1 -0
  13. data/lib/cachable.rb +77 -0
  14. data/lib/highrise.rb +22 -0
  15. data/lib/highrise/base.rb +9 -0
  16. data/lib/highrise/comment.rb +4 -0
  17. data/lib/highrise/company.rb +14 -0
  18. data/lib/highrise/curlhelper.rb +27 -0
  19. data/lib/highrise/curly.rb +101 -0
  20. data/lib/highrise/deal.rb +35 -0
  21. data/lib/highrise/email.rb +9 -0
  22. data/lib/highrise/group.rb +7 -0
  23. data/lib/highrise/kase.rb +8 -0
  24. data/lib/highrise/membership.rb +4 -0
  25. data/lib/highrise/note.rb +9 -0
  26. data/lib/highrise/pagination.rb +29 -0
  27. data/lib/highrise/person.rb +43 -0
  28. data/lib/highrise/subject.rb +50 -0
  29. data/lib/highrise/tag.rb +18 -0
  30. data/lib/highrise/taggable.rb +33 -0
  31. data/lib/highrise/task.rb +26 -0
  32. data/lib/highrise/user.rb +11 -0
  33. data/spec/cachable_spec.rb +68 -0
  34. data/spec/highrise/base_spec.rb +13 -0
  35. data/spec/highrise/comment_spec.rb +14 -0
  36. data/spec/highrise/companies/16883216.html +1723 -0
  37. data/spec/highrise/company_spec.rb +70 -0
  38. data/spec/highrise/curlhelper_spec.rb +35 -0
  39. data/spec/highrise/curly_spec.rb +36 -0
  40. data/spec/highrise/email_spec.rb +25 -0
  41. data/spec/highrise/group_spec.rb +14 -0
  42. data/spec/highrise/kase_spec.rb +25 -0
  43. data/spec/highrise/membership_spec.rb +14 -0
  44. data/spec/highrise/note_spec.rb +23 -0
  45. data/spec/highrise/pagination_spec.rb +10 -0
  46. data/spec/highrise/people/16887003.html +1733 -0
  47. data/spec/highrise/person_spec.rb +83 -0
  48. data/spec/highrise/subject_spec.rb +43 -0
  49. data/spec/highrise/tag_spec.rb +24 -0
  50. data/spec/highrise/task_spec.rb +23 -0
  51. data/spec/highrise/user_spec.rb +30 -0
  52. data/spec/spec.opts +7 -0
  53. data/spec/spec_helper.rb +20 -0
  54. data/uninstall.rb +1 -0
  55. metadata +176 -0
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -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
@@ -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,9 @@
1
+ require File.dirname(__FILE__) + '/../cachable'
2
+
3
+ module Highrise
4
+ class Base < ActiveResource::Base
5
+ end
6
+ class ActiveResource::Connection
7
+ include Cachable
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ module Highrise
2
+ class Comment < Base
3
+ end
4
+ end
@@ -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,9 @@
1
+ module Highrise
2
+ class Email < Base
3
+ include Pagination
4
+
5
+ def comments
6
+ Comment.find(:all, :from => "/emails/#{email_id}/comments.xml")
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Highrise
2
+ class Group < Base
3
+ def self.find_by_name(name)
4
+ find(:all).select{|category| category.name == name}.first
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module Highrise
2
+ class Kase < Subject
3
+ def close!
4
+ self.closed_at = Time.now.utc
5
+ save
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,4 @@
1
+ module Highrise
2
+ class Membership < Base
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module Highrise
2
+ class Note < Base
3
+ include Pagination
4
+
5
+ def comments
6
+ Comment.find(:all, :from => "/notes/#{id}/comments.xml")
7
+ end
8
+ end
9
+ 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