├── .ruby-version ├── .gitignore ├── Gemfile ├── autotest └── discover.rb ├── lib ├── highrise │ ├── version.rb │ ├── comment.rb │ ├── group.rb │ ├── membership.rb │ ├── deal_category.rb │ ├── task_category.rb │ ├── recording.rb │ ├── account.rb │ ├── tag.rb │ ├── email.rb │ ├── note.rb │ ├── subject_data.rb │ ├── task.rb │ ├── deal.rb │ ├── party.rb │ ├── company.rb │ ├── subject_field.rb │ ├── user.rb │ ├── kase.rb │ ├── taggable.rb │ ├── person.rb │ ├── searchable.rb │ ├── subject.rb │ ├── base.rb │ ├── custom_fields.rb │ └── pagination.rb └── highrise.rb ├── spec ├── highrise │ ├── group_spec.rb │ ├── comment_spec.rb │ ├── membership_spec.rb │ ├── recording_spec.rb │ ├── pagination_spec.rb │ ├── searchable_spec.rb │ ├── taggable_spec.rb │ ├── account_spec.rb │ ├── task_spec.rb │ ├── email_spec.rb │ ├── note_spec.rb │ ├── deal_category_spec.rb │ ├── task_category_spec.rb │ ├── tag_spec.rb │ ├── user_spec.rb │ ├── party_spec.rb │ ├── searchable_behavior.rb │ ├── deal_spec.rb │ ├── taggable_behavior.rb │ ├── kase_spec.rb │ ├── subject_data_spec.rb │ ├── subject_field_spec.rb │ ├── subject_spec.rb │ ├── base_spec.rb │ ├── pagination_behavior.rb │ ├── company_spec.rb │ └── person_spec.rb └── spec_helper.rb ├── examples ├── config_initializers_highrise.rb ├── sample.rb └── extending.rb ├── Rakefile ├── MIT-LICENSE ├── highrise.gemspec.erb ├── certs └── gem-public_cert.pem ├── Gemfile.lock ├── README.md └── highrise.gemspec /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.3.1 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/** 2 | .rvmrc 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /autotest/discover.rb: -------------------------------------------------------------------------------- 1 | Autotest.add_discovery { "rspec2" } 2 | -------------------------------------------------------------------------------- /lib/highrise/version.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | VERSION = "3.2.3" 3 | end -------------------------------------------------------------------------------- /lib/highrise/comment.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class Comment < Base; end 3 | end -------------------------------------------------------------------------------- /lib/highrise/group.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class Group < Base; end 3 | end -------------------------------------------------------------------------------- /lib/highrise/membership.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class Membership < Base; end 3 | end -------------------------------------------------------------------------------- /lib/highrise/deal_category.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class DealCategory < Base; end 3 | end -------------------------------------------------------------------------------- /lib/highrise/task_category.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class TaskCategory < Base; end 3 | end -------------------------------------------------------------------------------- /lib/highrise/recording.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class Recording < Base 3 | include Pagination 4 | end 5 | end -------------------------------------------------------------------------------- /spec/highrise/group_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Group do 4 | it { should be_a_kind_of Highrise::Base } 5 | end 6 | -------------------------------------------------------------------------------- /spec/highrise/comment_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Comment do 4 | it { should be_a_kind_of Highrise::Base } 5 | end 6 | -------------------------------------------------------------------------------- /spec/highrise/membership_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Membership do 4 | it { should be_a_kind_of Highrise::Base } 5 | end 6 | -------------------------------------------------------------------------------- /lib/highrise/account.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class Account < Base 3 | def self.me 4 | find(:one, :from => "/account.xml") 5 | end 6 | end 7 | end -------------------------------------------------------------------------------- /examples/config_initializers_highrise.rb: -------------------------------------------------------------------------------- 1 | if Rails.env != 'test' then 2 | Highrise::Base.site = 'https://example.com.i' 3 | Highrise::Base.user = 'my_fancy_auth_token' 4 | end -------------------------------------------------------------------------------- /spec/highrise/recording_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Recording do 4 | it { should be_a_kind_of Highrise::Base } 5 | 6 | it_should_behave_like "a paginated class" 7 | end -------------------------------------------------------------------------------- /lib/highrise/tag.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class Tag < Base 3 | def ==(object) 4 | (object.instance_of?(self.class) && object.id == self.id && object.name == self.name) 5 | end 6 | end 7 | end -------------------------------------------------------------------------------- /examples/sample.rb: -------------------------------------------------------------------------------- 1 | require 'highrise' 2 | require 'pp' 3 | 4 | Highrise::Base.site = 'https://yoursite.highrisehq.com' 5 | Highrise::Base.user = 'xxx' 6 | 7 | @tags = Highrise::Tag.find(:all) 8 | 9 | pp @tags -------------------------------------------------------------------------------- /lib/highrise/email.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class Email < Base 3 | include Pagination 4 | 5 | def comments 6 | Comment.find(:all, :from => "/emails/#{id}/comments.xml") 7 | end 8 | end 9 | end -------------------------------------------------------------------------------- /lib/highrise/note.rb: -------------------------------------------------------------------------------- 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 -------------------------------------------------------------------------------- /lib/highrise/subject_data.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class SubjectData < Base 3 | def==other 4 | attributes["value"] == other.attributes["value"] && 5 | attributes["subject_field_label"] == other.attributes["subject_field_label"] 6 | end 7 | end 8 | end -------------------------------------------------------------------------------- /spec/highrise/pagination_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Pagination do 4 | class TestClass < Highrise::Base; include Highrise::Pagination; end 5 | subject { TestClass.new } 6 | 7 | it_should_behave_like "a paginated class" 8 | end 9 | -------------------------------------------------------------------------------- /spec/highrise/searchable_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Searchable do 4 | class TestClass < Highrise::Base; include Highrise::Searchable; end 5 | subject { TestClass.new } 6 | 7 | it_should_behave_like "a searchable class" 8 | end 9 | -------------------------------------------------------------------------------- /spec/highrise/taggable_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Taggable do 4 | class TestClass < Highrise::Base; include Highrise::Taggable; end 5 | 6 | subject { TestClass.new } 7 | 8 | it_should_behave_like "a taggable class" 9 | end 10 | -------------------------------------------------------------------------------- /lib/highrise/task.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class Task < Base 3 | # find(:all, :from => :upcoming) 4 | # find(:all, :from => :assigned) 5 | # find(:all, :from => :completed) 6 | 7 | def complete! 8 | load_attributes_from_response(post(:complete)) 9 | end 10 | end 11 | end -------------------------------------------------------------------------------- /spec/highrise/account_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Account do 4 | it { should be_a_kind_of Highrise::Base } 5 | 6 | it ".me" do 7 | Highrise::Account.should_receive(:find).with(:one, {:from => "/account.xml"}).and_return(subject) 8 | Highrise::Account.me.should == subject 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/highrise/deal.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class Deal < Subject 3 | include Pagination 4 | def update_status(status) 5 | raise ArgumentError, "status must be one of 'pending', 'won', or 'lost'" unless %w[pending won lost].include?(status) 6 | self.put(:status, :status => {:name => status}) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/highrise/party.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class Party < Base 3 | def self.recently_viewed 4 | find(:all, :from => "/parties/recently_viewed.xml") 5 | end 6 | 7 | def self.deletions_since(time) 8 | find(:all, :from => "/parties/deletions.xml", :params => { :since => time.utc.strftime("%Y%m%d%H%M%S") }) 9 | end 10 | end 11 | end -------------------------------------------------------------------------------- /spec/highrise/task_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Task do 4 | it { should be_a_kind_of Highrise::Base } 5 | 6 | it "#complete!" do 7 | subject.should_receive(:load_attributes_from_response).with("post") 8 | subject.should_receive(:post).with(:complete).and_return("post") 9 | subject.complete! 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/highrise/company.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class Company < Subject 3 | include Pagination 4 | include Taggable 5 | include Searchable 6 | include CustomFields 7 | 8 | def people 9 | Person.find_all_across_pages(:from => "/companies/#{id}/people.xml") 10 | end 11 | 12 | def label 13 | 'Party' 14 | end 15 | end 16 | end -------------------------------------------------------------------------------- /spec/highrise/email_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Email do 4 | it { should be_a_kind_of Highrise::Base } 5 | 6 | it_should_behave_like "a paginated class" 7 | 8 | it "#comments" do 9 | subject.should_receive(:id).and_return(1) 10 | Highrise::Comment.should_receive(:find).with(:all, {:from=>"/emails/1/comments.xml"}).and_return("comments") 11 | subject.comments.should == "comments" 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/highrise/note_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Note do 4 | subject { Highrise::Note.new(:id => 1) } 5 | 6 | it { should be_a_kind_of Highrise::Base } 7 | 8 | it_should_behave_like "a paginated class" 9 | 10 | it "#comments" do 11 | Highrise::Comment.should_receive(:find).with(:all, {:from=>"/notes/1/comments.xml"}).and_return("comments") 12 | subject.comments.should == "comments" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.setup 3 | 4 | require File.join(File.dirname(__FILE__), '/../lib/highrise') 5 | 6 | Highrise::Base.user = ENV['HIGHRISE_USER'] || 'x' 7 | Highrise::Base.oauth_token = ENV['HIGHRISE_TOKEN'] || 'TOKEN' 8 | Highrise::Base.site = ENV['HIGHRISE_SITE'] || 'https://www.example.com' 9 | 10 | require 'highrise/pagination_behavior' 11 | require 'highrise/searchable_behavior' 12 | require 'highrise/taggable_behavior' 13 | require 'active_resource/http_mock' 14 | 15 | -------------------------------------------------------------------------------- /spec/highrise/deal_category_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::DealCategory do 4 | subject { Highrise::DealCategory.new(:id => 1, :name => "Deal Category") } 5 | 6 | it { should be_a_kind_of Highrise::Base } 7 | 8 | it ".find_by_name" do 9 | deal_category = Highrise::DealCategory.new(:id => 2, :name => "Another Deal Category") 10 | Highrise::DealCategory.should_receive(:find).with(:all).and_return([deal_category, subject]) 11 | Highrise::DealCategory.find_by_name("Deal Category").should == subject 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/highrise/task_category_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::TaskCategory do 4 | subject { Highrise::TaskCategory.new(:id => 1, :name => "Task Category") } 5 | 6 | it { should be_a_kind_of Highrise::Base } 7 | 8 | it ".find_by_name" do 9 | task_category = Highrise::TaskCategory.new(:id => 2, :name => "Another Task Category") 10 | Highrise::TaskCategory.should_receive(:find).with(:all).and_return([task_category, subject]) 11 | Highrise::TaskCategory.find_by_name("Task Category").should == subject 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/highrise/subject_field.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class SubjectField < Base 3 | 4 | def initialize(attributes = {}, persisted = false) 5 | super 6 | @use_cache = false 7 | end 8 | 9 | def self.use_cache(use_cache = true) 10 | @use_cache = use_cache 11 | end 12 | 13 | def self.find_every(options) 14 | if @use_cache 15 | @subject_field_cache ||= super 16 | else 17 | super 18 | end 19 | end 20 | 21 | def self.invalidate_cache 22 | @subject_field_cache = nil 23 | end 24 | end 25 | end 26 | 27 | -------------------------------------------------------------------------------- /spec/highrise/tag_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Tag do 4 | subject { Highrise::Tag.new(:id => 1, :name => "Name") } 5 | 6 | it { should be_a_kind_of Highrise::Base } 7 | 8 | it "supports equality" do 9 | tag = Highrise::Tag.new(:id => 1, :name => "Name") 10 | subject.should == tag 11 | end 12 | 13 | it ".find_by_name" do 14 | tag = Highrise::Tag.new(:id => 2, :name => "Next") 15 | Highrise::Tag.should_receive(:find).with(:all).and_return([tag, subject]) 16 | Highrise::Tag.find_by_name("Name").should == subject 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/highrise/user.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class User < Base 3 | def join(group) 4 | Membership.create(:user_id => id, :group_id => group.id) 5 | end 6 | 7 | # Permits API-key retrieval using name and password. 8 | # Highrise::User.site = "https://yourcompany.highrise.com" 9 | # Highrise::User.user = "your_user_name" 10 | # Highrise::User.password = "s3kr3t" 11 | # Highrise::User.me.token # contains the API token for "your_user_name" 12 | def self.me 13 | user = User.new() 14 | find(:one, :from => "/me.xml") 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /spec/highrise/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::User do 4 | it { should be_a_kind_of Highrise::Base } 5 | 6 | it ".me" do 7 | Highrise::User.should_receive(:find).with(:one, {:from => "/me.xml"}).and_return(subject) 8 | Highrise::User.me.should == subject 9 | end 10 | 11 | it "#join" do 12 | group_mock = mock("group") 13 | group_mock.should_receive(:id).and_return(2) 14 | subject.should_receive(:id).and_return(1) 15 | Highrise::Membership.should_receive(:create).with({:user_id=>1, :group_id=>2}) 16 | subject.join(group_mock) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/highrise/party_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Party do 4 | it { should be_a_kind_of Highrise::Base } 5 | 6 | it ".recently_viewed" do 7 | Highrise::Party.should_receive(:find).with(:all, {:from => '/parties/recently_viewed.xml'}) 8 | Highrise::Party.recently_viewed 9 | end 10 | 11 | it ".deletions_since" do 12 | time = Time.parse("Wed Jan 14 15:43:11 -0200 2009") 13 | Highrise::Party.should_receive(:find).with(:all, {:from => '/parties/deletions.xml', :params=>{:since=>"20090114174311"}}).and_return("result") 14 | Highrise::Party.deletions_since(time).should == "result" 15 | end 16 | end -------------------------------------------------------------------------------- /spec/highrise/searchable_behavior.rb: -------------------------------------------------------------------------------- 1 | shared_examples_for "a searchable class" do 2 | it { subject.class.included_modules.should include(Highrise::Searchable) } 3 | 4 | it ".search" do 5 | find_args = {:from => "/#{subject.class.collection_name}/search.xml", :params => {"criteria[email]" => "john.doe@example.com", "criteria[zip]" => "90210"}} 6 | if subject.class.respond_to?(:find_all_across_pages) 7 | subject.class.should_receive(:find_all_across_pages).with(find_args) 8 | else 9 | subject.class.should_receive(:find).with(:all, find_args) 10 | end 11 | subject.class.search(:email => "john.doe@example.com", :zip => "90210") 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/highrise/kase.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class Kase < Subject 3 | include Pagination 4 | 5 | def open! 6 | update_attribute(:closed_at, nil) 7 | end 8 | 9 | def close! 10 | update_attribute(:closed_at, Time.now.utc) 11 | end 12 | 13 | def self.open 14 | Kase.find(:all, :from => "/kases/open.xml") 15 | end 16 | 17 | def self.closed 18 | Kase.find(:all, :from => "/kases/closed.xml") 19 | end 20 | 21 | def self.all_open_across_pages 22 | find_all_across_pages(:from => "/kases/open.xml") 23 | end 24 | 25 | def self.all_closed_across_pages 26 | find_all_across_pages(:from => "/kases/closed.xml") 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'bundler' 3 | Bundler::GemHelper.install_tasks if File.exist? 'highrise.gemspec' 4 | 5 | require 'rspec/core/rake_task' 6 | require 'erb' 7 | 8 | desc 'Default: run unit tests.' 9 | task :default => :spec 10 | 11 | desc "Run all specs" 12 | RSpec::Core::RakeTask.new do |t| 13 | t.pattern = 'spec/**/*_spec.rb' 14 | t.rspec_opts = ["-c", "-f progress"] 15 | end 16 | 17 | desc 'Generate gemspec' 18 | task :gemspec do 19 | gemspec = 'highrise.gemspec' 20 | 21 | #FileUtils.rm gemspec if File.exist? gemspec 22 | template = ERB.new File.new("#{gemspec}.erb").read 23 | 24 | begin 25 | gemspec_file = File.open(gemspec,'w') 26 | gemspec_file.write template.result(binding) 27 | ensure 28 | gemspec_file.close 29 | end 30 | 31 | puts "done!" 32 | end -------------------------------------------------------------------------------- /lib/highrise.rb: -------------------------------------------------------------------------------- 1 | require 'highrise/base' 2 | require 'highrise/pagination' 3 | require 'highrise/taggable' 4 | require 'highrise/searchable' 5 | require 'highrise/custom_fields' 6 | require 'highrise/subject' 7 | require 'highrise/comment' 8 | require 'highrise/company' 9 | require 'highrise/email' 10 | require 'highrise/group' 11 | require 'highrise/kase' 12 | require 'highrise/membership' 13 | require 'highrise/note' 14 | require 'highrise/person' 15 | require 'highrise/task' 16 | require 'highrise/user' 17 | require 'highrise/tag' 18 | require 'highrise/deal' 19 | require 'highrise/account' 20 | require 'highrise/deal_category' 21 | require 'highrise/task_category' 22 | require 'highrise/party' 23 | require 'highrise/recording' 24 | require 'highrise/subject_data' 25 | require 'highrise/subject_field' 26 | -------------------------------------------------------------------------------- /spec/highrise/deal_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Deal do 4 | subject { Highrise::Deal.new(:id => 1) } 5 | 6 | it { should be_a_kind_of Highrise::Subject } 7 | 8 | it ".add_note" do 9 | Highrise::Note.should_receive(:create).with({:body=>"body", :subject_id=>1, :subject_type=>'Deal'}).and_return(mock('note')) 10 | subject.add_note :body=>'body' 11 | end 12 | 13 | describe ".update_status" do 14 | it { expect { subject.update_status("invalid") }.to raise_error(ArgumentError) } 15 | 16 | %w[pending won lost].each do |status| 17 | it "updates status to #{status}" do 18 | subject.should_receive(:put).with(:status, :status => {:name => status}) 19 | subject.update_status(status) 20 | end 21 | end 22 | end 23 | end -------------------------------------------------------------------------------- /lib/highrise/taggable.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | module Taggable 3 | 4 | def tags 5 | unless self.attributes.has_key?("tags") 6 | person_or_company = self.class.find(id) 7 | self.attributes["tags"] = person_or_company.attributes.has_key?("tags") ? person_or_company.tags : [] 8 | end 9 | self.attributes["tags"] 10 | end 11 | 12 | def tag!(tag_name) 13 | self.post(:tags, :name => tag_name) unless tag_name.blank? 14 | end 15 | 16 | def untag!(tag_name) 17 | to_delete = self.tags.find{|tag| tag.attributes['name'] == tag_name} unless tag_name.blank? 18 | self.untag_id!(to_delete.attributes['id']) unless to_delete.nil? 19 | end 20 | protected 21 | def untag_id!(tag_id) 22 | self.delete("tags/#{tag_id}") 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/highrise/taggable_behavior.rb: -------------------------------------------------------------------------------- 1 | shared_examples_for "a taggable class" do 2 | before(:each) do 3 | (@tags = []).tap do 4 | @tags << Highrise::Tag.new(:name => "cliente", :id => 414578) 5 | @tags << Highrise::Tag.new(:name => "ged", :id => 414580) 6 | @tags << Highrise::Tag.new(:name => "iepc", :id => 414579) 7 | end 8 | end 9 | 10 | it { subject.class.included_modules.should include(Highrise::Taggable) } 11 | 12 | it "#tag!(tag_name)" do 13 | subject.should_receive(:post).with(:tags, :name => "client" ).and_return(true) 14 | subject.tag!("client").should be_true 15 | end 16 | 17 | it "#untag!(tag_name)" do 18 | subject.should_receive(:tags).and_return(@tags) 19 | subject.should_receive(:delete).with("tags/414578").and_return(true) 20 | subject.untag!("cliente").should be_true 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/highrise/person.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class Person < Subject 3 | include Pagination 4 | include Taggable 5 | include Searchable 6 | include CustomFields 7 | 8 | def company 9 | Company.find(company_id) if company_id 10 | end 11 | 12 | def name 13 | "#{first_name rescue ''} #{last_name rescue ''}".strip 14 | end 15 | 16 | def address 17 | contact_data.addresses.first 18 | end 19 | 20 | def web_address 21 | contact_data.web_addresses.first 22 | end 23 | 24 | def email_addresses 25 | contact_data.email_addresses.collect { |address| address.address } rescue [] 26 | end 27 | 28 | def phone_numbers 29 | contact_data.phone_numbers.collect { |phone_number| phone_number.number } rescue [] 30 | end 31 | 32 | def label 33 | 'Party' 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /examples/extending.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Example of extending a class when you need to synthesize an attribute. 3 | # 4 | # Adds Highrise::Person.{phone,fax,email} to the Person class inside your 5 | # module 6 | # 7 | 8 | module MyModule 9 | include Highrise 10 | 11 | Highrise::Person.class_eval do 12 | class << self 13 | def lookup(id, list, item, location) 14 | module_eval <<-EOT 15 | def #{id} 16 | contact_data.#{list}.each do |i| 17 | return i.#{item}.strip if i.location == "#{location}" 18 | end 19 | '' 20 | end 21 | EOT 22 | end 23 | 24 | private :lookup 25 | end 26 | 27 | lookup(:phone, 'phone_numbers', 'number', 'Work') 28 | lookup(:fax, 'phone_numbers', 'number', 'Fax') 29 | lookup(:email, 'email_addresses', 'address', 'Work') 30 | end 31 | end -------------------------------------------------------------------------------- /lib/highrise/searchable.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | module Searchable 3 | def self.included(base) 4 | base.extend(ClassMethods) 5 | end 6 | 7 | module ClassMethods 8 | # List By Search Criteria 9 | # Ex: Highrise::Person.search(:email => "john.doe@example.com", :country => "CA") 10 | # Available criteria are: city, state, country, zip, phone, email 11 | def search(options = {}) 12 | search_params = options.inject({}) { |h, (k, v)| h["criteria[#{k}]"] = v; h } 13 | # This might have to be changed in the future if other non-pagable resources become searchable 14 | if self.respond_to?(:find_all_across_pages) 15 | self.find_all_across_pages(:from => "/#{self.collection_name}/search.xml", :params => search_params) 16 | else 17 | self.find(:all, {:from => "/#{self.collection_name}/search.xml", :params => search_params}) 18 | end 19 | end 20 | end 21 | end 22 | end -------------------------------------------------------------------------------- /lib/highrise/subject.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | class Subject < Base 3 | def notes(options={}) 4 | options.merge!(:from => "/#{self.class.collection_name}/#{id}/notes.xml") 5 | Note.find_all_across_pages(options) 6 | end 7 | 8 | def add_note(attrs={}) 9 | attrs[:subject_id] = self.id 10 | attrs[:subject_type] = self.label 11 | Note.create attrs 12 | end 13 | 14 | def add_task(attrs={}) 15 | attrs[:subject_id] = self.id 16 | attrs[:subject_type] = self.label 17 | Task.create attrs 18 | end 19 | 20 | def emails(options={}) 21 | options.merge!(:from => "/#{self.class.collection_name}/#{id}/emails.xml") 22 | Email.find_all_across_pages(options) 23 | end 24 | 25 | def upcoming_tasks(options={}) 26 | options.merge!(:from => "/#{self.class.collection_name}/#{id}/tasks.xml") 27 | Task.find(:all, options) 28 | end 29 | 30 | def label 31 | self.class.name.split('::').last 32 | end 33 | end 34 | end -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Ken Mayer & Marcos Tapajos 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. 21 | -------------------------------------------------------------------------------- /spec/highrise/kase_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Kase do 4 | it { should be_a_kind_of Highrise::Subject } 5 | 6 | it_should_behave_like "a paginated class" 7 | 8 | it "#close!" do 9 | mocked_now = Time.parse("Wed Jan 14 15:43:11 -0200 2009") 10 | Time.should_receive(:now).and_return(mocked_now) 11 | subject.should_receive(:update_attribute).with(:closed_at, mocked_now.utc) 12 | subject.close! 13 | end 14 | 15 | it "#open!" do 16 | subject.should_receive(:update_attribute).with(:closed_at, nil) 17 | subject.open! 18 | end 19 | 20 | it ".all_open_across_pages" do 21 | subject.class.should_receive(:find).with(:all,{:from=>"/kases/open.xml",:params=>{:n=>0}}).and_return(["things"]) 22 | subject.class.should_receive(:find).with(:all,{:from=>"/kases/open.xml",:params=>{:n=>1}}).and_return([]) 23 | subject.class.all_open_across_pages.should == ["things"] 24 | end 25 | 26 | it ".all_closed_across_pages" do 27 | subject.class.should_receive(:find).with(:all,{:from=>"/kases/closed.xml",:params=>{:n=>0}}).and_return(["things"]) 28 | subject.class.should_receive(:find).with(:all,{:from=>"/kases/closed.xml",:params=>{:n=>1}}).and_return([]) 29 | subject.class.all_closed_across_pages.should == ["things"] 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /highrise.gemspec.erb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "highrise/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "highrise" 7 | s.version = Highrise::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | 10 | s.required_rubygems_version = ">= 1.3.6" 11 | s.add_dependency "activeresource", ">= 3.2.13" 12 | s.add_development_dependency "rspec" 13 | s.add_development_dependency "rake" 14 | 15 | s.files = <%= `git ls-files`.split("\n") %> 16 | s.test_files = <%= `git ls-files -- {test,spec,features,examples}/*`.split("\n") %> 17 | s.require_paths = ["lib"] 18 | 19 | s.authors = ["Marcos Tapaj\303\263s", "Ken Mayer"] 20 | s.email = ["marcos@tapajos.me", "kmayer@bitwrangler.com"] 21 | s.homepage = "http://github.com/tapajos/highrise" 22 | s.summary = "Ruby wrapper around Highrise API" 23 | s.description = <<-EOT 24 | Based on the original API module from DHH, http://developer.37signals.com/highrise/, this 25 | gem is a cleaned up, tested version of the same. 26 | 27 | Configure by adding the following: 28 | 29 | require 'highrise' 30 | Highrise::Base.site = 'http://your_site.highrisehq.com/' 31 | Highrise::Base.user = 'your_api_auth_token' 32 | EOT 33 | 34 | end -------------------------------------------------------------------------------- /certs/gem-public_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDbDCCAlSgAwIBAgIBATANBgkqhkiG9w0BAQUFADA+MRAwDgYDVQQDDAd0YXBh 3 | am9zMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZFgNjb20w 4 | HhcNMTMwMzA1MDEyMjQ3WhcNMTQwMzA1MDEyMjQ3WjA+MRAwDgYDVQQDDAd0YXBh 5 | am9zMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZFgNjb20w 6 | ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDYaGKh+qrV7Ai1jZdQwlHt 7 | /CaUilnHaNHlC5wQh9cniIwjJJ4Ml75m31Yp97Y9piEP+9mND4NqwtF8rYBmDKbJ 8 | J/AiQe1IEeuaZzIA2YCYjEIoBESoJvCDatDHk33nopAHCfYr5vKlx5DVDHWwaoK/ 9 | 0+NqHyZ37eRxLOGRg2zAN6tFiWGZuy4ye1mlgZR1PVqLjRT0H9kM3lP31jIPdOaW 10 | IvAGEAA+4SR3SIyAp/8RRWlDsu45TVAhd1gbq9KcqZLYclVWzpqGPDrb9tMiiqKN 11 | he8qj5C04diXGSEbaENnuhhDql7G6QRVaBtD5SSvssSL+g8DEVxQb35vijGm6/NX 12 | AgMBAAGjdTBzMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBQH+XwV 13 | Sqz1jBfSrok4PWfhCQGeXjAcBgNVHREEFTATgRF0YXBham9zQGdtYWlsLmNvbTAc 14 | BgNVHRIEFTATgRF0YXBham9zQGdtYWlsLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEA 15 | qiFtedQQsl7JAU0tQBIj5W3XzyRJxbuR4eq9WOhXBirtLgzZccFphPFSDEatl3Sc 16 | ALC6a86WOmc4Hp023Nb/JamQ2D9nx871k0Cj19tiJDRAbWYEYpim3w+3NXbFzDUE 17 | 0DSKV/BbYqm9vjnj+LnTnCr1qhPPRsPI3xWASv6kRXQ3E3b7NjRSEL+KZ4u0eCjW 18 | qIPM7CDD+hW+YgumNQEED7Qtss2XMiouMysrMNoGUQbJJLBE7I2XwBxKNSA1P2l0 19 | G2tNecqPmHNhgiiojhWB/DTZQKuYJqb+7hCbrLXa/y7kFYwjFR+O0EJvritmuQiS 20 | GVMhotYhO3Lpnij5gFI1AQ== 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /spec/highrise/subject_data_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::SubjectData do 4 | it { should be_a_kind_of Highrise::Base } 5 | 6 | it "Two different subject datas with different values are not equal" do 7 | martini = Highrise::SubjectData.new({:id => 1, :value => "Martini", :subject_field_id => 3, :subject_field_label => "Cocktail"}) 8 | sling = Highrise::SubjectData.new({:id => 2, :value => "Singapore Sling", :subject_field_id => 4, :subject_field_label => "Cocktail"}) 9 | martini.should_not==sling 10 | end 11 | 12 | it "Two different subject datas with different labels are not equal" do 13 | martini = Highrise::SubjectData.new({:id => 1, :value => "Martini", :subject_field_id => 3, :subject_field_label => "Cocktail"}) 14 | sling = Highrise::SubjectData.new({:id => 2, :value => "Martini", :subject_field_id => 4, :subject_field_label => "Vermouth Brands"}) 15 | martini.should_not==sling 16 | end 17 | 18 | it "Two the same subject datas are equal" do 19 | martini = Highrise::SubjectData.new({:id => 1, :value => "Martini", :subject_field_id => 3, :subject_field_label => "Cocktail"}) 20 | another_martini = Highrise::SubjectData.new({:id => 2, :value => "Martini", :subject_field_id => 4, :subject_field_label => "Cocktail"}) 21 | martini.should==another_martini 22 | end 23 | 24 | end -------------------------------------------------------------------------------- /lib/highrise/base.rb: -------------------------------------------------------------------------------- 1 | require 'active_resource' 2 | 3 | module Highrise 4 | class Base < ActiveResource::Base 5 | protected 6 | 7 | class << self 8 | # If headers are not defined in a given subclass, then obtain 9 | # headers from the superclass. 10 | # http://opensoul.org/blog/archives/2010/02/16/active-resource-in-practice/ 11 | def headers 12 | if defined?(@headers) 13 | @headers 14 | elsif superclass != Object && superclass.headers 15 | superclass.headers 16 | else 17 | @headers ||= {} 18 | end 19 | end 20 | 21 | def oauth_token=(token) 22 | headers['Authorization'] = "Bearer #{token}" 23 | end 24 | end 25 | 26 | # Fix for ActiveResource 3.1+ errors 27 | self.format = :xml 28 | 29 | # Dynamic finder for attributes 30 | def self.method_missing(method, *args) 31 | if method.to_s =~ /^find_(all_)?by_([_a-zA-Z]\w*)$/ 32 | raise ArgumentError, "Dynamic finder method must take an argument." if args.empty? 33 | options = args.extract_options! 34 | resources = respond_to?(:find_all_across_pages) ? send(:find_all_across_pages, options) : send(:find, :all) 35 | resources.send($1 == 'all_' ? 'select' : 'detect') { |container| container.send($2) == args.first } 36 | else 37 | super 38 | end 39 | end 40 | 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | highrise (3.2.2) 5 | activeresource (>= 3.2.13) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | activemodel (6.0.3.1) 11 | activesupport (= 6.0.3.1) 12 | activemodel-serializers-xml (1.0.2) 13 | activemodel (> 5.x) 14 | activesupport (> 5.x) 15 | builder (~> 3.1) 16 | activeresource (5.1.1) 17 | activemodel (>= 5.0, < 7) 18 | activemodel-serializers-xml (~> 1.0) 19 | activesupport (>= 5.0, < 7) 20 | activesupport (6.0.3.1) 21 | concurrent-ruby (~> 1.0, >= 1.0.2) 22 | i18n (>= 0.7, < 2) 23 | minitest (~> 5.1) 24 | tzinfo (~> 1.1) 25 | zeitwerk (~> 2.2, >= 2.2.2) 26 | builder (3.2.4) 27 | concurrent-ruby (1.1.6) 28 | diff-lcs (1.2.1) 29 | i18n (1.8.2) 30 | concurrent-ruby (~> 1.0) 31 | minitest (5.14.1) 32 | rake (13.0.1) 33 | rspec (2.13.0) 34 | rspec-core (~> 2.13.0) 35 | rspec-expectations (~> 2.13.0) 36 | rspec-mocks (~> 2.13.0) 37 | rspec-core (2.13.0) 38 | rspec-expectations (2.13.0) 39 | diff-lcs (>= 1.1.3, < 2.0) 40 | rspec-mocks (2.13.0) 41 | thread_safe (0.3.6) 42 | thread_safe (0.3.6-java) 43 | tzinfo (1.2.7) 44 | thread_safe (~> 0.1) 45 | zeitwerk (2.3.0) 46 | 47 | PLATFORMS 48 | java 49 | ruby 50 | 51 | DEPENDENCIES 52 | highrise! 53 | rake 54 | rspec 55 | -------------------------------------------------------------------------------- /spec/highrise/subject_field_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::SubjectField do 4 | it { should be_a_kind_of Highrise::Base } 5 | let(:two_subject_fields){ [ 6 | Highrise::SubjectField.new({:id => 1, :label => "Cabbage"}), 7 | Highrise::SubjectField.new({:id => 2, :label => "Chicken"})] 8 | } 9 | let(:four_subject_fields){ [ 10 | two_subject_fields, 11 | Highrise::SubjectField.new({:id => 3, :label => "Pasta"}), 12 | Highrise::SubjectField.new({:id => 4, :label => "Beans"})].flatten 13 | } 14 | let(:subject_field_request){ ActiveResource::Request.new(:get, '/subject_fields.xml', nil, {"Authorization"=>"Bearer OAUTH_TOKEN", "Accept"=>"application/xml"}) } 15 | let(:two_subject_fields_request_pair){ {subject_field_request => ActiveResource::Response.new(two_subject_fields.to_xml, 200, {})} } 16 | let(:four_subject_fields_request_pair){ { subject_field_request => ActiveResource::Response.new(four_subject_fields.to_xml, 200, {})} } 17 | 18 | context 'cache disabled (default)' do 19 | it "does not use cache for queries" do 20 | ActiveResource::HttpMock.respond_to(two_subject_fields_request_pair) 21 | Highrise::SubjectField.find(:all) 22 | ActiveResource::HttpMock.respond_to(four_subject_fields_request_pair) 23 | Highrise::SubjectField.find(:all).size.should== 4 24 | end 25 | end 26 | 27 | context 'cache enabled (opt-in)' do 28 | before(:each) do 29 | Highrise::SubjectField.use_cache(true) 30 | end 31 | it "caches 'find all' to prevent too much queries for the SubjectFields" do 32 | ActiveResource::HttpMock.respond_to(two_subject_fields_request_pair) 33 | Highrise::SubjectField.find(:all) 34 | ActiveResource::HttpMock.reset! 35 | Highrise::SubjectField.find(:all).size.should== 2 36 | end 37 | it "invalidates cache" do 38 | ActiveResource::HttpMock.respond_to(two_subject_fields_request_pair) 39 | Highrise::SubjectField.find(:all) 40 | Highrise::SubjectField.invalidate_cache 41 | ActiveResource::HttpMock.respond_to(four_subject_fields_request_pair) 42 | Highrise::SubjectField.find(:all).size.should== 4 43 | end 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /spec/highrise/subject_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Subject do 4 | subject { Highrise::Subject.new(:id => 1) } 5 | 6 | it { should be_a_kind_of Highrise::Base } 7 | 8 | it "#notes" do 9 | Highrise::Note.should_receive(:find_all_across_pages).with({:from=>"/subjects/1/notes.xml"}).and_return("notes") 10 | subject.notes.should == "notes" 11 | end 12 | 13 | it "#add_note" do 14 | Highrise::Note.should_receive(:create).with({:body=>"body", :subject_id=>1, :subject_type=>'Subject'}).and_return(mock('note')) 15 | subject.add_note :body=>'body' 16 | end 17 | 18 | it "#add_task" do 19 | Highrise::Task.should_receive(:create).with({:body=>"body", :subject_id=>1, :subject_type=>'Subject'}).and_return(mock('task')) 20 | subject.add_task :body=>'body' 21 | end 22 | 23 | it "#emails" do 24 | Highrise::Email.should_receive(:find_all_across_pages).with({:from=>"/subjects/1/emails.xml"}).and_return("emails") 25 | subject.emails.should == "emails" 26 | end 27 | 28 | it "#upcoming_tasks" do 29 | Highrise::Task.should_receive(:find).with(:all, {:from=>"/subjects/1/tasks.xml"}).and_return("tasks") 30 | subject.upcoming_tasks.should == "tasks" 31 | end 32 | 33 | context 'finding with since param' do 34 | before(:each) do 35 | @utc_time_str = "20090114174311" 36 | end 37 | 38 | it "#notes" do 39 | Highrise::Note.should_receive(:find_all_across_pages).with({:from=>"/subjects/1/notes.xml", :params=>{:since=>@utc_time_str}}).and_return("notes") 40 | subject.notes(:params=>{:since=>@utc_time_str}).should == "notes" 41 | end 42 | 43 | it "#emails" do 44 | Highrise::Email.should_receive(:find_all_across_pages).with({:from=>"/subjects/1/emails.xml", :params=>{:since=>@utc_time_str}}).and_return("emails") 45 | subject.emails(:params=>{:since=>@utc_time_str}).should == "emails" 46 | end 47 | 48 | 49 | it "#tasks" do 50 | Highrise::Task.should_receive(:find).with(:all, {:from=>"/subjects/1/tasks.xml", :params=>{:since=>@utc_time_str}}).and_return("tasks") 51 | subject.upcoming_tasks(:params=>{:since=>@utc_time_str}).should == "tasks" 52 | end 53 | end 54 | 55 | it { subject.label.should == "Subject" } 56 | end -------------------------------------------------------------------------------- /spec/highrise/base_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Base do 4 | it { subject.should be_a_kind_of ActiveResource::Base } 5 | 6 | describe "dynamic finder methods" do 7 | context "without pagination" do 8 | before do 9 | @deal_one = Highrise::Base.new(:id => 1, :name => "A deal") 10 | @deal_two = Highrise::Base.new(:id => 2, :name => "A deal") 11 | @deal_three = Highrise::Base.new(:id => 3, :name => "Another deal") 12 | Highrise::Base.should_receive(:find).with(:all).and_return([@deal_one, @deal_two, @deal_three]) 13 | end 14 | it ".find_by_(attribute) finds one" do 15 | Highrise::Base.find_by_name("A deal").should == @deal_one 16 | end 17 | 18 | it ".find_all_by_(attribute) finds all" do 19 | Highrise::Base.find_all_by_name("A deal").should == [@deal_one, @deal_two] 20 | end 21 | end 22 | 23 | context "with pagination" do 24 | before do 25 | class PaginatedBaseClass < Highrise::Base; include Highrise::Pagination; end 26 | @john_doe = PaginatedBaseClass.new(:id => 1, :first_name => "John") 27 | @john_baker = PaginatedBaseClass.new(:id => 2, :first_name => "John") 28 | @joe_smith = PaginatedBaseClass.new(:id => 3, :first_name => "Joe") 29 | PaginatedBaseClass.should_receive(:find_all_across_pages).and_return([@john_doe, @john_baker, @joe_smith]) 30 | end 31 | it ".find_by_(attribute) finds one" do 32 | PaginatedBaseClass.find_by_first_name("John").should == @john_doe 33 | end 34 | 35 | it ".find_all_by_(attribute) finds all" do 36 | PaginatedBaseClass.find_all_by_first_name("John").should == [@john_doe, @john_baker] 37 | end 38 | end 39 | 40 | it "expects arguments to the finder" do 41 | expect { Highrise::Base.find_all_by_first_name }.to raise_error(ArgumentError) 42 | end 43 | 44 | it "falls back to regular method missing" do 45 | expect { Highrise::Base.any_other_method }.to raise_error(NoMethodError) 46 | end 47 | end 48 | 49 | describe "when using an oauth token" do 50 | it ".oauth_token= writes a bearer authorization header" do 51 | Highrise::Base.oauth_token = 'OAUTH_TOKEN' 52 | Highrise::Base.headers['Authorization'].should == 'Bearer OAUTH_TOKEN' 53 | end 54 | end 55 | end -------------------------------------------------------------------------------- /lib/highrise/custom_fields.rb: -------------------------------------------------------------------------------- 1 | module Highrise 2 | module CustomFields 3 | def field(field_label) 4 | custom_fields = attributes["subject_datas"] ||= [] 5 | field = custom_fields.detect do |field| 6 | field.subject_field_label == field_label 7 | end 8 | field ? field.value : nil 9 | end 10 | 11 | def new_subject_data(field, value) 12 | Highrise::SubjectData.new(:subject_field_id => field.id, :subject_field_label => field.label, :value => value) 13 | end 14 | 15 | def set_field_value(field_label, new_value) 16 | custom_fields = attributes["subject_datas"] ||= [] 17 | custom_fields.each do |field| 18 | return field.value = new_value if field.subject_field_label == field_label 19 | end 20 | 21 | SubjectField.find(:all).each do |custom_field| 22 | if custom_field.label == field_label 23 | return attributes["subject_datas"] << new_subject_data(custom_field, new_value) 24 | end 25 | end 26 | end 27 | 28 | def transform_subject_field_label field_label 29 | field_label.downcase.tr(' ', '_') 30 | end 31 | 32 | def convert_method_to_field_label method 33 | custom_fields = attributes["subject_datas"] ||= [] 34 | custom_fields.each do |field| 35 | method_name_from_field = transform_subject_field_label(field.subject_field_label) 36 | return field if method_name_from_field == method 37 | end 38 | nil 39 | end 40 | 41 | def method_missing(method_symbol, *args) 42 | method_name = method_symbol.to_s 43 | 44 | if method_name[-1,1] == "=" 45 | attribute_name = method_name[0...-1] 46 | field = convert_method_to_field_label(attribute_name) 47 | return set_field_value(field.subject_field_label, args[0]) if field 48 | 49 | return super if attributes[attribute_name] 50 | 51 | subject_fields = SubjectField.find(:all) 52 | unless subject_fields.nil? 53 | subject_fields.each do |custom_field| 54 | if transform_subject_field_label(custom_field.label) == attribute_name 55 | return attributes["subject_datas"] << new_subject_data(custom_field, args[0]) 56 | end 57 | end 58 | end 59 | else 60 | field = convert_method_to_field_label(method_name) 61 | return field(field.subject_field_label) if field 62 | end 63 | super 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/highrise/pagination.rb: -------------------------------------------------------------------------------- 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 | # This only is usefull for company, person & recordings, but should be safely ignored by other classes 15 | def find_all_across_pages_since(time) 16 | find_all_across_pages(:params => { :since => time.utc.strftime("%Y%m%d%H%M%S") }) 17 | end 18 | 19 | # This is useful only for Company, Person, Note, Comment, Email and Task, but should be safely ignored by other classes 20 | def find_all_deletions_across_pages(options = {}) 21 | # point to the global deletions feed 22 | options[:from] = '/deletions.xml' 23 | 24 | records = [] 25 | each_deletions(options) { |record| records << record } 26 | records 27 | end 28 | 29 | # This is useful only for Company, Person, Note, Comment, Email and Task, but should be safely ignored by other classes 30 | def find_all_deletions_across_pages_since(time) 31 | find_all_deletions_across_pages(:params => { :since => time.utc.strftime("%Y%m%d%H%M%S") }) 32 | end 33 | 34 | private 35 | 36 | def each(options = {}) 37 | options[:params] ||= {} 38 | options[:params][:n] = 0 39 | 40 | loop do 41 | if (records = self.find(:all, options)).try(:any?) 42 | records.each { |record| yield record } 43 | options[:params][:n] += records.size 44 | else 45 | break # no people included on that page, thus no more people total 46 | end 47 | end 48 | end 49 | 50 | def each_deletions(options = {}) 51 | options[:params] ||= {} 52 | # first index for deletions is 1 53 | options[:params][:n] = 1 54 | 55 | loop do 56 | if (records = self.find(:all, options)).try(:any?) 57 | # reject the records whose resource type is different from self 58 | records.reject!{|r| r.class.to_s.split('::').last != self.to_s.split('::').last} 59 | 60 | records.each{ |record| yield record } 61 | 62 | # index increment for deletions is 1 per page of 500 resources 63 | options[:params][:n] += 1 64 | else 65 | break # no deletions included on that page, thus no more deletions 66 | end 67 | end 68 | end 69 | 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Highrise (master) [![Build Status](https://snap-ci.com/tapajos/highrise/branch/master/build_image)](https://snap-ci.com/tapajos/highrise/branch/master) [![Code Climate](https://codeclimate.com/github/tapajos/highrise.png)](https://codeclimate.com/github/tapajos/highrise) 2 | 3 | ## What is it? 4 | 5 | This gem provides a set of classes to access information on [Highrise][h] via the published [API][api]: 6 | 7 | Account, Comment, Company, Deal, DealCategory, Email, Group, Case, Membership, 8 | Note, Party, Person, Recording, Subject, Tag, Task, TaskCategory and User. 9 | 10 | All these classes are inherited from ActiveResouce::Base. Refer to the [ActiveResouce][ar] documentation for more information. 11 | 12 | ## Installing 13 | ```bash 14 | gem install highrise 15 | ``` 16 | ### Dependencies (see highrise.gemspec or run bundle check) 17 | 18 | ### Documentation 19 | 20 | I'm on [rdoc.info][rdoc] (or am I?) 21 | 22 | [Cookbook][cookbook] 23 | 24 | ### Configure your key 25 | ```ruby 26 | require 'highrise' 27 | 28 | Highrise::Base.site = 'https://your_site.highrisehq.com' 29 | Highrise::Base.user = 'api-auth-token' 30 | Highrise::Base.format = :xml 31 | ``` 32 | 33 | If you are using this in a Rails application, putting this code in a config/initializers/highrise.rb 34 | file is recommended. See config_initializers_highrise.rb in the examples/ directory. 35 | 36 | ## Usage 37 | ```ruby 38 | @tags = Highrise::Tag.find(:all) 39 | 40 | @people = Highrise::Person.find_all_across_pages(:params => {:tag_id => 12345}) 41 | 42 | @person.tag!("VIP") 43 | ``` 44 | ## License 45 | 46 | This code is free to be used under the terms of the [MIT license][mit]. 47 | 48 | ## Bugs, Issues, Kudos and Catcalls 49 | 50 | Comments are welcome. Send your feedback through the [issue tracker on GitHub][i] 51 | 52 | If you have fixes: Submit via pull requests. Do not include version changes to the 53 | version file. 54 | 55 | ## Authors 56 | 57 | * [Marcos Tapajós][tapajos] 58 | * [Ken Mayer][kmayer] 59 | 60 | ## [Contributors][list] 61 | 62 | 63 | [api]: http://developer.37signals.com/highrise 64 | [ar]: http://api.rubyonrails.org/classes/ActiveResource/Base.html 65 | [c]: http://api.rubyonrails.org/classes/ActiveSupport/Cache 66 | [h]: http://www.highrisehq.com/ 67 | [i]: https://github.com/tapajos/highrise/issues 68 | [kmayer]: https://github.com/kmayer 69 | [mit]: http://www.opensource.org/licenses/mit-license.php 70 | [cookbook]: https://github.com/tapajos/highrise/wiki/Cookbook 71 | [rdoc]: http://rdoc.info/projects/tapajos/highrise 72 | [tapajos]: http://www.improveit.com.br/en/company/tapajos 73 | [list]: https://github.com/tapajos/highrise/graphs/contributors 74 | [] 75 | -------------------------------------------------------------------------------- /spec/highrise/pagination_behavior.rb: -------------------------------------------------------------------------------- 1 | shared_examples_for "a paginated class" do 2 | it { subject.class.included_modules.should include(Highrise::Pagination) } 3 | 4 | it ".find_all_across_pages" do 5 | subject.class.should_receive(:find).with(:all,{:params=>{:n=>0}}).and_return(["things"]) 6 | subject.class.should_receive(:find).with(:all,{:params=>{:n=>1}}).and_return([]) 7 | subject.class.find_all_across_pages.should == ["things"] 8 | end 9 | 10 | it ".find_all_across_pages with zero results" do 11 | subject.class.should_receive(:find).with(:all,{:params=>{:n=>0}}).and_return(nil) 12 | subject.class.find_all_across_pages.should == [] 13 | end 14 | 15 | it ".find_all_across_pages_since" do 16 | time = Time.parse("Wed Jan 14 15:43:11 -0200 2009") 17 | subject.class.should_receive(:find_all_across_pages).with({:params=>{:since=>"20090114174311"}}).and_return("result") 18 | subject.class.find_all_across_pages_since(time).should == "result" 19 | end 20 | 21 | it ".find_all_deletions_across_pages" do 22 | class TestClass2 < Highrise::Base; include Highrise::Pagination; end 23 | subject_type = subject.class.to_s.split('::').last 24 | deleted_resource_1 = subject.class.new(:id => 12, :type => subject_type) 25 | deleted_resource_2 = TestClass2.new(:id => 34, :type => 'TestClass2') 26 | deleted_resource_3 = subject.class.new(:id => 45, :type => subject_type) 27 | 28 | subject.class.should_receive(:find).with(:all,{:from => '/deletions.xml', :params=>{:n=>1}}).and_return([deleted_resource_1, deleted_resource_2, deleted_resource_3]) 29 | subject.class.should_receive(:find).with(:all,{:from => '/deletions.xml', :params=>{:n=>2}}).and_return([]) 30 | subject.class.find_all_deletions_across_pages.should == [deleted_resource_1, deleted_resource_3] 31 | end 32 | 33 | it ".find_all_deletions_across_pages with zero results" do 34 | subject.class.should_receive(:find).with(:all,{:from => '/deletions.xml', :params=>{:n=>1}}).and_return(nil) 35 | subject.class.find_all_deletions_across_pages.should == [] 36 | end 37 | 38 | it ".find_all_deletions_across_pages_since" do 39 | class TestClass2 < Highrise::Base; include Highrise::Pagination; end 40 | subject_type = subject.class.to_s.split('::').last 41 | time = Time.parse("Wed Jan 14 15:43:11 -0200 2009") 42 | deleted_resource_1 = subject.class.new(:id => 12, :type => subject_type) 43 | deleted_resource_2 = TestClass2.new(:id => 34, :type => 'TestClass2') 44 | deleted_resource_3 = subject.class.new(:id => 45, :type => subject_type) 45 | 46 | subject.class.should_receive(:find).with(:all,{:from => '/deletions.xml', :params=>{:n=>1, :since=>"20090114174311"}}).and_return([deleted_resource_1, deleted_resource_2, deleted_resource_3]) 47 | subject.class.should_receive(:find).with(:all,{:from => '/deletions.xml', :params=>{:n=>2, :since=>"20090114174311"}}).and_return([]) 48 | subject.class.find_all_deletions_across_pages_since(time).should == [deleted_resource_1, deleted_resource_3] 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/highrise/company_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Highrise::Company do 4 | subject { Highrise::Company.new(:id => 1) } 5 | 6 | it { should be_a_kind_of Highrise::Base } 7 | it_should_behave_like "a paginated class" 8 | it_should_behave_like "a taggable class" 9 | it_should_behave_like "a searchable class" 10 | 11 | it "#people" do 12 | Highrise::Person.should_receive(:find_all_across_pages).with(:from=>"/companies/1/people.xml").and_return("people") 13 | subject.people.should == "people" 14 | end 15 | 16 | describe "Custom fields" do 17 | 18 | before (:each) do 19 | @fruit_company = Highrise::Company.new({ :company => { 20 | :id => 1, 21 | :name => "John Doe & Co.", 22 | :subject_datas => [{ 23 | :subject_field_label => "Fruit Banana", 24 | :value => "Yellow" 25 | }, { 26 | :subject_field_label => "Fruit Grape", 27 | :value => "Green" 28 | }] 29 | } 30 | }) 31 | @subject_field_blueberry = Highrise::SubjectField.new ({:id => 1, :label => "Fruit Blueberry"}) 32 | @subject_field_papaya = Highrise::SubjectField.new ({:id => 2, :label => "Fruit Papaya"}) 33 | end 34 | 35 | it "Can get the value of a custom field via the field method" do 36 | @fruit_company.field("Fruit Banana").should== "Yellow" 37 | end 38 | 39 | it "Can get the value of a custom field using a custom method call" do 40 | @fruit_company.fruit_grape.should== "Green" 41 | end 42 | 43 | it "Will raise an exception on an unknown field" do 44 | expect {@fruit_company.unknown_fruit}.to raise_exception(NoMethodError) 45 | end 46 | 47 | it "Can set the value of a custom field via the field method" do 48 | @fruit_company.set_field_value("Fruit Grape", "Red") 49 | @fruit_company.field("Fruit Grape").should== "Red" 50 | end 51 | 52 | it "Can set the value of a custom field" do 53 | @fruit_company.fruit_grape= "Red" 54 | @fruit_company.field("Fruit Grape").should== "Red" 55 | end 56 | 57 | it "Assignment just returns the arguments (like ActiveResource base does)" do 58 | Highrise::SubjectField.should_receive(:find).with(:all).and_return [] 59 | (@fruit_company.unknown_fruit = 10).should== 10 60 | end 61 | 62 | it "Can deal with the find returning nil (which is a bit ugly in the ActiveResource API)" do 63 | Highrise::SubjectField.should_receive(:find).with(:all).and_return nil 64 | (@fruit_company.unknown_fruit = 10).should== 10 65 | end 66 | 67 | it "Can set the value of a custom field that wasn't there via the field method, but that was defined (happens on new Person)" do 68 | Highrise::SubjectField.should_receive(:find).with(:all).and_return([@subject_field_papaya, @subject_field_blueberry]) 69 | @fruit_company.set_field_value("Fruit Blueberry", "Purple") 70 | @fruit_company.field("Fruit Blueberry").should== "Purple" 71 | @fruit_company.attributes["subject_datas"][2].subject_field_id.should == 1 72 | end 73 | 74 | it "Can still set and read the usual way of reading attrivutes" do 75 | @fruit_company.name = "Jacob" 76 | @fruit_company.name.should== "Jacob" 77 | 78 | end 79 | 80 | end 81 | 82 | it { subject.label.should == 'Party' } 83 | end 84 | -------------------------------------------------------------------------------- /highrise.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "highrise/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "highrise" 7 | s.version = Highrise::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | 10 | s.required_rubygems_version = ">= 1.3.6" 11 | s.add_dependency "activeresource", ">= 3.2.13" 12 | s.add_development_dependency "rspec" 13 | s.add_development_dependency "rake" 14 | 15 | s.files = [".gitignore", ".ruby-version", ".travis.yml", "Gemfile", "Gemfile.lock", "MIT-LICENSE", "README.md", "Rakefile", "autotest/discover.rb", "certs/gem-public_cert.pem", "examples/config_initializers_highrise.rb", "examples/extending.rb", "examples/sample.rb", "highrise.gemspec", "highrise.gemspec.erb", "lib/highrise.rb", "lib/highrise/account.rb", "lib/highrise/base.rb", "lib/highrise/comment.rb", "lib/highrise/company.rb", "lib/highrise/custom_fields.rb", "lib/highrise/deal.rb", "lib/highrise/deal_category.rb", "lib/highrise/email.rb", "lib/highrise/group.rb", "lib/highrise/kase.rb", "lib/highrise/membership.rb", "lib/highrise/note.rb", "lib/highrise/pagination.rb", "lib/highrise/party.rb", "lib/highrise/person.rb", "lib/highrise/recording.rb", "lib/highrise/searchable.rb", "lib/highrise/subject.rb", "lib/highrise/subject_data.rb", "lib/highrise/subject_field.rb", "lib/highrise/tag.rb", "lib/highrise/taggable.rb", "lib/highrise/task.rb", "lib/highrise/task_category.rb", "lib/highrise/user.rb", "lib/highrise/version.rb", "spec/highrise/account_spec.rb", "spec/highrise/base_spec.rb", "spec/highrise/comment_spec.rb", "spec/highrise/company_spec.rb", "spec/highrise/deal_category_spec.rb", "spec/highrise/deal_spec.rb", "spec/highrise/email_spec.rb", "spec/highrise/group_spec.rb", "spec/highrise/kase_spec.rb", "spec/highrise/membership_spec.rb", "spec/highrise/note_spec.rb", "spec/highrise/pagination_behavior.rb", "spec/highrise/pagination_spec.rb", "spec/highrise/party_spec.rb", "spec/highrise/person_spec.rb", "spec/highrise/recording_spec.rb", "spec/highrise/searchable_behavior.rb", "spec/highrise/searchable_spec.rb", "spec/highrise/subject_data_spec.rb", "spec/highrise/subject_field_spec.rb", "spec/highrise/subject_spec.rb", "spec/highrise/tag_spec.rb", "spec/highrise/taggable_behavior.rb", "spec/highrise/taggable_spec.rb", "spec/highrise/task_category_spec.rb", "spec/highrise/task_spec.rb", "spec/highrise/user_spec.rb", "spec/spec_helper.rb"] 16 | s.test_files = ["examples/config_initializers_highrise.rb", "examples/extending.rb", "examples/sample.rb", "spec/highrise/account_spec.rb", "spec/highrise/base_spec.rb", "spec/highrise/comment_spec.rb", "spec/highrise/company_spec.rb", "spec/highrise/deal_category_spec.rb", "spec/highrise/deal_spec.rb", "spec/highrise/email_spec.rb", "spec/highrise/group_spec.rb", "spec/highrise/kase_spec.rb", "spec/highrise/membership_spec.rb", "spec/highrise/note_spec.rb", "spec/highrise/pagination_behavior.rb", "spec/highrise/pagination_spec.rb", "spec/highrise/party_spec.rb", "spec/highrise/person_spec.rb", "spec/highrise/recording_spec.rb", "spec/highrise/searchable_behavior.rb", "spec/highrise/searchable_spec.rb", "spec/highrise/subject_data_spec.rb", "spec/highrise/subject_field_spec.rb", "spec/highrise/subject_spec.rb", "spec/highrise/tag_spec.rb", "spec/highrise/taggable_behavior.rb", "spec/highrise/taggable_spec.rb", "spec/highrise/task_category_spec.rb", "spec/highrise/task_spec.rb", "spec/highrise/user_spec.rb", "spec/spec_helper.rb"] 17 | s.require_paths = ["lib"] 18 | 19 | s.authors = ["Marcos Tapaj\303\263s", "Ken Mayer"] 20 | s.email = ["marcos@tapajos.me", "kmayer@bitwrangler.com"] 21 | s.homepage = "http://github.com/tapajos/highrise" 22 | s.summary = "Ruby wrapper around Highrise API" 23 | s.description = <<-EOT 24 | Based on the original API module from DHH, http://developer.37signals.com/highrise/, this 25 | gem is a cleaned up, tested version of the same. 26 | 27 | Configure by adding the following: 28 | 29 | require 'highrise' 30 | Highrise::Base.site = 'http://your_site.highrisehq.com/' 31 | Highrise::Base.user = 'your_api_auth_token' 32 | EOT 33 | 34 | end -------------------------------------------------------------------------------- /spec/highrise/person_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'spec_helper' 3 | 4 | describe Highrise::Person do 5 | subject { Highrise::Person.new(:id => 1) } 6 | 7 | it { should be_a_kind_of Highrise::Subject } 8 | 9 | it_should_behave_like "a paginated class" 10 | it_should_behave_like "a taggable class" 11 | it_should_behave_like "a searchable class" 12 | 13 | describe "#company" do 14 | it "returns nil when it doesn't have a company" do 15 | subject.should_receive(:company_id).and_return(nil) 16 | subject.company.should be_nil 17 | end 18 | 19 | it "delegate to Highrise::Company when have company_id" do 20 | subject.should_receive(:company_id).at_least(2).times.and_return(1) 21 | Highrise::Company.should_receive(:find).with(1).and_return("company") 22 | subject.company.should == "company" 23 | end 24 | end 25 | 26 | it "#name" do 27 | subject.should_receive(:first_name).and_return("Marcos") 28 | subject.should_receive(:last_name).and_return("Tapajós ") 29 | subject.name.should == "Marcos Tapajós" 30 | end 31 | 32 | describe "#email_addresses" do 33 | it "returns an empty array when there are none set" do 34 | subject.email_addresses.should == [] 35 | end 36 | 37 | it "returns all email_addresses as string in an array" do 38 | subject = Highrise::Person.new(:id => 1, 39 | :contact_data => { 40 | :email_addresses => [{ 41 | :email_address => { 42 | :address => "important@person.com" 43 | } 44 | }] 45 | }) 46 | subject.email_addresses.should == ["important@person.com"] 47 | end 48 | end 49 | 50 | describe "#phone_numbers" do 51 | it "returns an empty array when there is none set" do 52 | subject.phone_numbers.should== [] 53 | end 54 | 55 | it "returns all phone numbers as a string aray" do 56 | subject = Highrise::Person.new(:id => 1, 57 | :contact_data => { 58 | :phone_numbers => [{ 59 | :phone_number => { 60 | :number => "123456789" 61 | } 62 | }] 63 | }) 64 | subject.phone_numbers.should== [ "123456789" ] 65 | end 66 | end 67 | 68 | describe "#tags" do 69 | 70 | let(:person_tags) { [ 71 | {'id' => "414578", 'name' => "cliente"}, 72 | {'id' => "414580", 'name' => "ged"}, 73 | {'id' => "414579", 'name' => "iepc"} ] 74 | } 75 | 76 | let(:person_john_doe) { { :id => 1, :first_name => "John", :last_name => "Doe" } } 77 | let(:person_john_doe_request){ ActiveResource::Request.new(:get, '/people/1.xml', nil, {"Authorization"=>"Bearer OAUTH_TOKEN", "Accept"=>"application/xml"}) } 78 | let(:person_john_doe_request_pair){ {person_john_doe_request => ActiveResource::Response.new(person_john_doe.to_xml, 200, {})} } 79 | 80 | it "should return the tags as a Highrise::Tag" do 81 | person_john_doe[:tags] = person_tags 82 | ActiveResource::HttpMock.respond_to(person_john_doe_request_pair) 83 | tags = person_tags.collect {|tag| Highrise::Tag.new(tag)} 84 | subject.tags.should == tags 85 | end 86 | 87 | it "should return an empty collection when there are no tags" do 88 | ActiveResource::HttpMock.respond_to(person_john_doe_request_pair) 89 | subject.tags.should == [] 90 | end 91 | end 92 | 93 | 94 | describe "#tags" do 95 | before(:each) do 96 | (@tags = []).tap do 97 | @tags << {'id' => "414578", 'name' => "cliente"} 98 | @tags << {'id' => "414580", 'name' => "ged"} 99 | @tags << {'id' => "414579", 'name' => "iepc"} 100 | end 101 | subject.attributes["tags"] = @tags 102 | end 103 | it { 104 | subject.tags.should == @tags 105 | } 106 | end 107 | 108 | describe "Custom fields" do 109 | 110 | before (:each) do 111 | @fruit_person = Highrise::Person.new({ :person => { 112 | :id => 1, 113 | :first_name => "John", 114 | :last_name => "Doe", 115 | :subject_datas => [{ 116 | :subject_field_label => "Fruit Banana", 117 | :value => "Yellow" 118 | }, { 119 | :subject_field_label => "Fruit Grape", 120 | :value => "Green" 121 | }] 122 | } 123 | }) 124 | @subject_field_blueberry = Highrise::SubjectField.new ({:id => 1, :label => "Fruit Blueberry"}) 125 | @subject_field_papaya = Highrise::SubjectField.new ({:id => 2, :label => "Fruit Papaya"}) 126 | end 127 | 128 | it "Can get the value of a custom field via the field method" do 129 | @fruit_person.field("Fruit Banana").should== "Yellow" 130 | end 131 | 132 | it "Can get the value of a custom field using a custom method call" do 133 | @fruit_person.fruit_grape.should== "Green" 134 | end 135 | 136 | it "Will raise an exception on an unknown field" do 137 | expect {@fruit_person.unknown_fruit}.to raise_exception(NoMethodError) 138 | end 139 | 140 | it "Can set the value of a custom field via the field method" do 141 | @fruit_person.set_field_value("Fruit Grape", "Red") 142 | @fruit_person.field("Fruit Grape").should== "Red" 143 | end 144 | 145 | it "Can set the value of a custom field" do 146 | @fruit_person.fruit_grape= "Red" 147 | @fruit_person.fruit_grape.should== "Red" 148 | end 149 | 150 | it "Assignment just returns the arguments (like ActiveResource base does)" do 151 | Highrise::SubjectField.should_receive(:find).with(:all).and_return [] 152 | (@fruit_person.unknown_fruit = 10).should== 10 153 | end 154 | 155 | it "Can deal with the find returning nil (which is a bit ugly in the ActiveResource API)" do 156 | Highrise::SubjectField.should_receive(:find).with(:all).and_return nil 157 | (@fruit_person.unknown_fruit = 10).should== 10 158 | end 159 | 160 | it "Can set the value of a custom field that wasn't there via the field method, but that was defined (happens on new Person)" do 161 | Highrise::SubjectField.should_receive(:find).with(:all).and_return([@subject_field_papaya, @subject_field_blueberry]) 162 | @fruit_person.set_field_value("Fruit Blueberry", "Purple") 163 | @fruit_person.field("Fruit Blueberry").should== "Purple" 164 | @fruit_person.attributes["subject_datas"][2].subject_field_id.should == 1 165 | end 166 | 167 | it "Can still set and read the usual way of reading attrivutes" do 168 | @fruit_person.first_name = "Jacob" 169 | @fruit_person.first_name.should== "Jacob" 170 | 171 | end 172 | 173 | end 174 | 175 | it { subject.label.should == 'Party' } 176 | end 177 | --------------------------------------------------------------------------------