civicrm 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -0
- data/.travis.yml +6 -0
- data/CONTRIBUTORS +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +47 -0
- data/History.txt +11 -0
- data/LICENSE +21 -0
- data/README.md +31 -0
- data/Rakefile +12 -0
- data/VERSION +1 -0
- data/bin/civicrm +7 -0
- data/civicrm.gemspec +29 -0
- data/lib/civicrm.rb +47 -0
- data/lib/civicrm/actions/create.rb +17 -0
- data/lib/civicrm/actions/destroy.rb +11 -0
- data/lib/civicrm/actions/find.rb +17 -0
- data/lib/civicrm/actions/list.rb +29 -0
- data/lib/civicrm/actions/update.rb +19 -0
- data/lib/civicrm/client.rb +70 -0
- data/lib/civicrm/errors.rb +9 -0
- data/lib/civicrm/resource.rb +84 -0
- data/lib/civicrm/resources/base.rb +140 -0
- data/lib/civicrm/version.rb +3 -0
- data/lib/civicrm/xml.rb +33 -0
- data/spec/requests/base_spec.rb +62 -0
- data/spec/resources/contact_spec.rb +13 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/civicrm.rb +27 -0
- data/spec/support/civicrm_responses.rb +31 -0
- data/spec/support/test_matchers.rb +37 -0
- metadata +194 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CONTRIBUTORS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Iskander Haziev <gvalmon@gmail.com>
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
civicrm (1.0.1)
|
5
|
+
activemodel
|
6
|
+
activesupport
|
7
|
+
nokogiri
|
8
|
+
rest-client (~> 1.4)
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: https://rubygems.org/
|
12
|
+
specs:
|
13
|
+
activemodel (3.2.13)
|
14
|
+
activesupport (= 3.2.13)
|
15
|
+
builder (~> 3.0.0)
|
16
|
+
activesupport (3.2.13)
|
17
|
+
i18n (= 0.6.1)
|
18
|
+
multi_json (~> 1.0)
|
19
|
+
builder (3.0.4)
|
20
|
+
diff-lcs (1.2.1)
|
21
|
+
i18n (0.6.1)
|
22
|
+
metaclass (0.0.1)
|
23
|
+
mime-types (1.22)
|
24
|
+
mocha (0.10.5)
|
25
|
+
metaclass (~> 0.0.1)
|
26
|
+
multi_json (1.7.2)
|
27
|
+
nokogiri (1.5.9)
|
28
|
+
rake (10.0.3)
|
29
|
+
rest-client (1.6.7)
|
30
|
+
mime-types (>= 1.16)
|
31
|
+
rspec (2.13.0)
|
32
|
+
rspec-core (~> 2.13.0)
|
33
|
+
rspec-expectations (~> 2.13.0)
|
34
|
+
rspec-mocks (~> 2.13.0)
|
35
|
+
rspec-core (2.13.1)
|
36
|
+
rspec-expectations (2.13.0)
|
37
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
38
|
+
rspec-mocks (2.13.0)
|
39
|
+
|
40
|
+
PLATFORMS
|
41
|
+
ruby
|
42
|
+
|
43
|
+
DEPENDENCIES
|
44
|
+
civicrm!
|
45
|
+
mocha
|
46
|
+
rake
|
47
|
+
rspec
|
data/History.txt
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2013 CiviCrm.com. (https://civicrm.com)
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
## Installation
|
2
|
+
|
3
|
+
* $ gem install civicrm
|
4
|
+
|
5
|
+
## Getting started
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
* CiviCrm.site_key = 'YOUR_SITE_KEY'
|
9
|
+
* // More info about site_key:
|
10
|
+
* // http://wiki.civicrm.org/confluence/display/CRMDOC43/Managing+Scheduled+Jobs
|
11
|
+
* CiviCrm.api_base = 'https://www.example.org/path/to/civi/codebase/'
|
12
|
+
* CiviCrm.authenticate('demo', 'demo')
|
13
|
+
```
|
14
|
+
|
15
|
+
## CiviCrm Objects
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
* CiviCrm::Contact.all # get list of contacts
|
19
|
+
* CiviCrm::Contact.create(contact_type: 'Organization', organization_name: 'test') # create contact
|
20
|
+
* CiviCrm::Contact.find(1).delete # find and delete
|
21
|
+
```
|
22
|
+
|
23
|
+
## Testing
|
24
|
+
```
|
25
|
+
rspec spec
|
26
|
+
```
|
27
|
+
|
28
|
+
## Useful links
|
29
|
+
|
30
|
+
* http://wiki.civicrm.org/confluence/display/CRMDOC43/REST+interface
|
31
|
+
* http://drupal.demo.civicrm.org/civicrm/api/explorer
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/bin/civicrm
ADDED
data/civicrm.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
|
+
|
3
|
+
require 'civicrm/version'
|
4
|
+
|
5
|
+
spec = Gem::Specification.new do |s|
|
6
|
+
s.name = 'civicrm'
|
7
|
+
s.version = CiviCrm::VERSION
|
8
|
+
s.summary = 'Ruby bindings for the CiviCRM API'
|
9
|
+
s.description = 'See http://civicrm.org for details.'
|
10
|
+
s.authors = ['Iskander Haziev']
|
11
|
+
s.email = ['gvalmon@gmail.com']
|
12
|
+
s.homepage = 'http://civicrm.org'
|
13
|
+
s.executables = 'civicrm'
|
14
|
+
s.require_paths = %w{lib}
|
15
|
+
|
16
|
+
s.add_dependency('rest-client', '~> 1.4')
|
17
|
+
s.add_dependency('activesupport')
|
18
|
+
s.add_dependency('activemodel')
|
19
|
+
s.add_dependency('nokogiri')
|
20
|
+
|
21
|
+
s.add_development_dependency('rake')
|
22
|
+
s.add_development_dependency('rspec')
|
23
|
+
s.add_development_dependency('mocha')
|
24
|
+
|
25
|
+
s.files = `git ls-files`.split("\n")
|
26
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
27
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
28
|
+
s.require_paths = ['lib']
|
29
|
+
end
|
data/lib/civicrm.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'active_support/deprecation'
|
2
|
+
require 'active_support/core_ext/module'
|
3
|
+
require 'active_support/core_ext/hash'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
require 'rest-client'
|
6
|
+
require 'nokogiri'
|
7
|
+
|
8
|
+
# utils
|
9
|
+
require 'civicrm/client'
|
10
|
+
require 'civicrm/xml'
|
11
|
+
require 'civicrm/resource'
|
12
|
+
require 'civicrm/version'
|
13
|
+
|
14
|
+
# actions
|
15
|
+
require 'civicrm/actions/list'
|
16
|
+
require 'civicrm/actions/create'
|
17
|
+
require 'civicrm/actions/update'
|
18
|
+
require 'civicrm/actions/destroy'
|
19
|
+
require 'civicrm/actions/find'
|
20
|
+
|
21
|
+
# exceptions
|
22
|
+
require 'civicrm/errors'
|
23
|
+
|
24
|
+
# resources
|
25
|
+
require 'civicrm/resources/base'
|
26
|
+
|
27
|
+
module CiviCrm
|
28
|
+
@@api_key = nil
|
29
|
+
@@site_key = nil
|
30
|
+
@@api_base = 'https://www.example.org/path/to/civi/codebase'
|
31
|
+
@@api_version = 'v3'
|
32
|
+
@@user_authenticated = false
|
33
|
+
|
34
|
+
mattr_accessor :api_key, :api_base, :api_version, :site_key
|
35
|
+
|
36
|
+
def self.api_url(path = '')
|
37
|
+
base = "#{api_base}/civicrm/extern/rest.php?#{path}"
|
38
|
+
base += "&api_key=#{@@api_key}" if @@api_key
|
39
|
+
base += "&key=#{@@site_key}" if @@site_key
|
40
|
+
base
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.authenticate(name, password)
|
44
|
+
auth = Client.request(:post, q: 'civicrm/login', name: name, pass: password)
|
45
|
+
@@api_key = auth[0]['api_key']
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CiviCrm
|
2
|
+
module Actions
|
3
|
+
module Create
|
4
|
+
module ClassMethods
|
5
|
+
def create(attrs = {})
|
6
|
+
params = {'entity' => entity_class_name, 'action' => 'create'}
|
7
|
+
response = CiviCrm::Client.request(:post, params.merge(attrs))
|
8
|
+
Resource.build_from(response.first, params)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module CiviCrm
|
2
|
+
module Actions
|
3
|
+
module Destroy
|
4
|
+
def delete
|
5
|
+
params = {'entity' => self.class.entity_class_name, 'action' => 'delete', 'id' => id}
|
6
|
+
response = CiviCrm::Client.request(:post, params)
|
7
|
+
refresh_from(response.first.to_hash)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CiviCrm
|
2
|
+
module Actions
|
3
|
+
module Find
|
4
|
+
module ClassMethods
|
5
|
+
def find(id)
|
6
|
+
params = {'entity' => entity_class_name, 'action' => 'getsingle', 'id' => id}
|
7
|
+
response = CiviCrm::Client.request(:get, params)
|
8
|
+
Resource.build_from(response.first, params)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module CiviCrm
|
2
|
+
module Actions
|
3
|
+
module List
|
4
|
+
module ClassMethods
|
5
|
+
def all(params = {})
|
6
|
+
params.merge!('entity' => entity_class_name, 'action' => 'get')
|
7
|
+
response = CiviCrm::Client.request(:get, params)
|
8
|
+
Resource.build_from(response, params)
|
9
|
+
end
|
10
|
+
|
11
|
+
def count
|
12
|
+
all.count
|
13
|
+
end
|
14
|
+
|
15
|
+
def first
|
16
|
+
all.first
|
17
|
+
end
|
18
|
+
|
19
|
+
def last
|
20
|
+
all.last
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.included(base)
|
25
|
+
base.extend(ClassMethods)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CiviCrm
|
2
|
+
module Actions
|
3
|
+
module Update
|
4
|
+
# TODO: this method doesn't work yet
|
5
|
+
def update(attrs = {})
|
6
|
+
params = {'entity' => self.class.entity_class_name, 'action' => 'replace', 'id' => id}
|
7
|
+
new_attrs = attributes.merge(attrs)
|
8
|
+
response = CiviCrm::Client.request(:post, params.merge(values: new_attrs))
|
9
|
+
refresh_from(response.first.to_hash)
|
10
|
+
end
|
11
|
+
|
12
|
+
def save
|
13
|
+
@previously_changed = changes
|
14
|
+
@changed_attributes.clear
|
15
|
+
update()
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module CiviCrm
|
2
|
+
class Client
|
3
|
+
class << self
|
4
|
+
|
5
|
+
# Returns parsed class inherited from CiviCrm::Resource
|
6
|
+
def request(method, params = {})
|
7
|
+
unless CiviCrm.site_key
|
8
|
+
raise CiviCrm::Errors::Unauthorized, "Please specify CiviCrm.site_key"
|
9
|
+
end
|
10
|
+
headers = {
|
11
|
+
:user_agent => "CiviCrm RubyClient/#{CiviCrm::VERSION}"
|
12
|
+
}
|
13
|
+
|
14
|
+
opts = {
|
15
|
+
:method => method,
|
16
|
+
:timeout => 80,
|
17
|
+
:headers => headers
|
18
|
+
}
|
19
|
+
|
20
|
+
# build params
|
21
|
+
case method.to_s.downcase.to_sym
|
22
|
+
when :get, :head, :delete
|
23
|
+
path = params.count > 0 ? stringify_params(params) : ''
|
24
|
+
else
|
25
|
+
opts[:payload] = stringify_params(params)
|
26
|
+
end
|
27
|
+
opts[:url] = CiviCrm.api_url(path)
|
28
|
+
response = execute(opts)
|
29
|
+
body, code = response.body, response.code
|
30
|
+
CiviCrm::XML.parse(body)
|
31
|
+
end
|
32
|
+
|
33
|
+
def execute(opts)
|
34
|
+
RestClient::Request.execute(opts)
|
35
|
+
rescue RuntimeError => e
|
36
|
+
case e.http_code.to_i
|
37
|
+
when 400
|
38
|
+
raise CiviCrm::Errors::BadRequest, e.http_body
|
39
|
+
when 401
|
40
|
+
raise CiviCrm::Errors::Unauthorized, e.http_body
|
41
|
+
when 403
|
42
|
+
raise CiviCrm::Errors::Forbidden, e.http_body
|
43
|
+
when 404
|
44
|
+
raise CiviCrm::Errors::NotFound, e.http_body
|
45
|
+
when 500
|
46
|
+
raise CiviCrm::Errors::InternalError, e.http_body
|
47
|
+
else
|
48
|
+
raise e
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def uri_escape(key)
|
53
|
+
URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
54
|
+
end
|
55
|
+
|
56
|
+
def flatten_params(params, parent_key = nil)
|
57
|
+
result = []
|
58
|
+
params.each do |key, value|
|
59
|
+
flatten_key = parent_key ? "#{parent_key}[#{uri_escape(key)}]" : uri_escape(key)
|
60
|
+
result += value.is_a?(Hash) ? flatten_params(value, flatten_key) : [[flatten_key, value]]
|
61
|
+
end
|
62
|
+
result
|
63
|
+
end
|
64
|
+
|
65
|
+
def stringify_params(params)
|
66
|
+
flatten_params(params).collect{|key, value| "#{key}=#{uri_escape(value)}"}.join('&')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'active_model/dirty'
|
2
|
+
module CiviCrm
|
3
|
+
class Resource
|
4
|
+
include ActiveModel::Dirty
|
5
|
+
class_attribute :entity_name
|
6
|
+
|
7
|
+
def initialize(values = {})
|
8
|
+
@values = {}
|
9
|
+
@id = values['id'] if values['id']
|
10
|
+
refresh_from(values)
|
11
|
+
self.class.define_attribute_methods(attributes.keys)
|
12
|
+
end
|
13
|
+
|
14
|
+
# we will use this method for creating nested resources
|
15
|
+
def refresh_from(values)
|
16
|
+
values.each do |key, value|
|
17
|
+
@values[key] = value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s(*opts)
|
22
|
+
@values.to_json
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
id_string = !@id.nil? ? " id=#{@id}" : ""
|
27
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}> #{attributes}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_missing(name, *opts)
|
31
|
+
if name[-1] == '='
|
32
|
+
name = name.to_s.gsub(/\=$/, '')
|
33
|
+
send(:"#{name}_will_change!")
|
34
|
+
@values[name.to_s] = opts.first if @values.has_key?(name.to_s)
|
35
|
+
else
|
36
|
+
@values[name.to_s] if @values.has_key?(name.to_s)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_json(*opts)
|
41
|
+
CiviCrm::JSON.encode(@values)
|
42
|
+
end
|
43
|
+
|
44
|
+
def as_json(*opts)
|
45
|
+
@values.as_json(*a)
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_hash
|
49
|
+
@values
|
50
|
+
end
|
51
|
+
|
52
|
+
def attributes
|
53
|
+
to_hash.reject{ |k, v| v.is_a? (CiviCrm::Resource)}
|
54
|
+
end
|
55
|
+
|
56
|
+
def attribute(key)
|
57
|
+
to_hash[key]
|
58
|
+
end
|
59
|
+
|
60
|
+
class << self
|
61
|
+
def entity(name = nil)
|
62
|
+
self.entity_name = name
|
63
|
+
end
|
64
|
+
|
65
|
+
def entity_class_name
|
66
|
+
self.entity_name.to_s.classify
|
67
|
+
end
|
68
|
+
|
69
|
+
def build_from(resp, request_params = {})
|
70
|
+
entity = request_params['entity']
|
71
|
+
case resp
|
72
|
+
when Array
|
73
|
+
resp.map { |values| build_from(values, request_params) }
|
74
|
+
when Hash
|
75
|
+
klass = "CiviCrm::#{entity.classify}".constantize
|
76
|
+
resource = klass.new(resp)
|
77
|
+
resource
|
78
|
+
else
|
79
|
+
resp
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module CiviCrm
|
2
|
+
class BaseResource < CiviCrm::Resource
|
3
|
+
include CiviCrm::Actions::List
|
4
|
+
include CiviCrm::Actions::Find
|
5
|
+
include CiviCrm::Actions::Create
|
6
|
+
include CiviCrm::Actions::Update
|
7
|
+
include CiviCrm::Actions::Destroy
|
8
|
+
end
|
9
|
+
end
|
10
|
+
module CiviCrm
|
11
|
+
class Activity < BaseResource
|
12
|
+
entity :activity
|
13
|
+
end
|
14
|
+
class ActivityType < BaseResource
|
15
|
+
entity :activity_type
|
16
|
+
end
|
17
|
+
class Address < BaseResource
|
18
|
+
entity :address
|
19
|
+
end
|
20
|
+
class Batch < BaseResource
|
21
|
+
entity :batch
|
22
|
+
end
|
23
|
+
class Campaign < BaseResource
|
24
|
+
entity :campaign
|
25
|
+
end
|
26
|
+
class Case < BaseResource
|
27
|
+
entity :case
|
28
|
+
end
|
29
|
+
class Constant < BaseResource
|
30
|
+
entity :constant
|
31
|
+
end
|
32
|
+
class Contact < BaseResource
|
33
|
+
entity :contact
|
34
|
+
end
|
35
|
+
class ContactType < BaseResource
|
36
|
+
entity :contact_type
|
37
|
+
end
|
38
|
+
class Contribution < BaseResource
|
39
|
+
entity :contribution
|
40
|
+
end
|
41
|
+
class ContributionPage < BaseResource
|
42
|
+
entity :contribution_page
|
43
|
+
end
|
44
|
+
class ContributionRecur < BaseResource
|
45
|
+
entity :contribution_recur
|
46
|
+
end
|
47
|
+
class CustomField < BaseResource
|
48
|
+
entity :custom_field
|
49
|
+
end
|
50
|
+
class CustomGroup < BaseResource
|
51
|
+
entity :custom_group
|
52
|
+
end
|
53
|
+
class CustomSearch < BaseResource
|
54
|
+
entity :custom_search
|
55
|
+
end
|
56
|
+
class CustomValue < BaseResource
|
57
|
+
entity :custom_value
|
58
|
+
end
|
59
|
+
class Domain < BaseResource
|
60
|
+
entity :domain
|
61
|
+
end
|
62
|
+
class Email < BaseResource
|
63
|
+
entity :email
|
64
|
+
end
|
65
|
+
class Entity < BaseResource
|
66
|
+
entity :entity
|
67
|
+
end
|
68
|
+
class EntityTag < BaseResource
|
69
|
+
entity :entity_tag
|
70
|
+
end
|
71
|
+
class Event < BaseResource
|
72
|
+
entity :event
|
73
|
+
end
|
74
|
+
class Extension < BaseResource
|
75
|
+
entity :extension
|
76
|
+
end
|
77
|
+
class File < BaseResource
|
78
|
+
entity :file
|
79
|
+
end
|
80
|
+
class Grant < BaseResource
|
81
|
+
entity :grant
|
82
|
+
end
|
83
|
+
class Group < BaseResource
|
84
|
+
entity :group
|
85
|
+
end
|
86
|
+
class GroupContact < BaseResource
|
87
|
+
entity :group_contact
|
88
|
+
end
|
89
|
+
class GroupNesting < BaseResource
|
90
|
+
entity :group_nesting
|
91
|
+
end
|
92
|
+
class GroupOrganization < BaseResource
|
93
|
+
entity :group_organization
|
94
|
+
end
|
95
|
+
class Im < BaseResource
|
96
|
+
entity :im
|
97
|
+
end
|
98
|
+
class Job < BaseResource
|
99
|
+
entity :job
|
100
|
+
end
|
101
|
+
class LineItem < BaseResource
|
102
|
+
entity :line_item
|
103
|
+
end
|
104
|
+
class LocBlock < BaseResource
|
105
|
+
entity :loc_block
|
106
|
+
end
|
107
|
+
class Location < BaseResource
|
108
|
+
entity :location
|
109
|
+
end
|
110
|
+
class LocationType < BaseResource
|
111
|
+
entity :location_type
|
112
|
+
end
|
113
|
+
class MailSettings < BaseResource
|
114
|
+
entity :mail_settings
|
115
|
+
end
|
116
|
+
class Mailing < BaseResource
|
117
|
+
entity :mailing
|
118
|
+
end
|
119
|
+
class Membership < BaseResource
|
120
|
+
entity :membership
|
121
|
+
end
|
122
|
+
class Note < BaseResource
|
123
|
+
entity :note
|
124
|
+
end
|
125
|
+
class Phone < BaseResource
|
126
|
+
entity :phone
|
127
|
+
end
|
128
|
+
class Profile < BaseResource
|
129
|
+
entity :profile
|
130
|
+
end
|
131
|
+
class Setting < BaseResource
|
132
|
+
entity :setting
|
133
|
+
end
|
134
|
+
class Survey < BaseResource
|
135
|
+
entity :survey
|
136
|
+
end
|
137
|
+
class SurveyRespondant < BaseResource
|
138
|
+
entity :survey_respondant
|
139
|
+
end
|
140
|
+
end
|
data/lib/civicrm/xml.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module CiviCrm
|
2
|
+
class XML
|
3
|
+
class << self
|
4
|
+
def parse(text)
|
5
|
+
doc = Nokogiri::XML.parse(text.to_s.gsub("\n", ''))
|
6
|
+
results = doc.xpath('//Result')
|
7
|
+
results.map do |result|
|
8
|
+
hash = {}
|
9
|
+
result.children.each do |attribute|
|
10
|
+
next unless attribute.is_a?(Nokogiri::XML::Element)
|
11
|
+
hash[attribute.name] = attribute.children[0].text rescue nil
|
12
|
+
end
|
13
|
+
hash
|
14
|
+
end
|
15
|
+
end
|
16
|
+
def encode(resources)
|
17
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
18
|
+
xml.ResultSet do
|
19
|
+
Array.wrap(resources).each do |resource|
|
20
|
+
attributes = resource.respond_to?(:attributes) ? resource.attributes : resource
|
21
|
+
xml.Result do
|
22
|
+
attributes.each do |key, value|
|
23
|
+
xml.send key.to_sym, value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
builder.to_xml
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rest_client'
|
3
|
+
require 'ostruct'
|
4
|
+
describe "API Bindings" do
|
5
|
+
|
6
|
+
before :all do
|
7
|
+
@client = authorized_civicrm_client
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should not fetch from network while initializing a new Resource" do
|
11
|
+
@client.expects(:get).never
|
12
|
+
CiviCrm::Contact.new(id: "someid")
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should run authentication" do
|
16
|
+
@client.expects(:post).once.returns(test_response(api_key: 'test'))
|
17
|
+
CiviCrm.authenticate("test", "test")
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "exception handler" do
|
21
|
+
|
22
|
+
before :all do
|
23
|
+
@client = CiviCrm::Client
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should raise CiviCrm::Errors::BadRequest on 400" do
|
27
|
+
exception = RestClient::Exception.new(OpenStruct.new(code: 400, body: 'message'))
|
28
|
+
RestClient::Request.stubs(:execute).raises(exception)
|
29
|
+
expect { @client.execute!({}) }.to raise_error(CiviCrm::Errors::BadRequest)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should raise CiviCrm::Errors::Unauthorized on 401" do
|
33
|
+
exception = RestClient::Exception.new(OpenStruct.new(code: 401, body: 'message'))
|
34
|
+
RestClient::Request.stubs(:execute).raises(exception)
|
35
|
+
expect { @client.execute!({}) }.to raise_error(CiviCrm::Errors::Unauthorized)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should raise CiviCrm::Errors::Forbidden on 403" do
|
39
|
+
exception = RestClient::Exception.new(OpenStruct.new(code: 403, body: 'message'))
|
40
|
+
RestClient::Request.stubs(:execute).raises(exception)
|
41
|
+
expect { @client.execute!({}) }.to raise_error(CiviCrm::Errors::Forbidden)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should raise CiviCrm::Errors::NotFound on 404" do
|
45
|
+
exception = RestClient::Exception.new(OpenStruct.new(code: 404, body: 'message'))
|
46
|
+
RestClient::Request.stubs(:execute).raises(exception)
|
47
|
+
expect { @client.execute!({}) }.to raise_error(CiviCrm::Errors::NotFound)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should raise CiviCrm::Errors::InternalError on 500" do
|
51
|
+
exception = RestClient::Exception.new(OpenStruct.new(code: 500, body: 'message'))
|
52
|
+
RestClient::Request.stubs(:execute).raises(exception)
|
53
|
+
expect { @client.execute!({}) }.to raise_error(CiviCrm::Errors::InternalError)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should raise CiviCrm::Errors::Unauthorized if site_key is not provided" do
|
57
|
+
CiviCrm.site_key = nil
|
58
|
+
RestClient::Request.stubs(:execute).returns('test')
|
59
|
+
expect { @client.request('get', 'test') }.to raise_error(CiviCrm::Errors::Unauthorized)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe CiviCrm::Contact do
|
3
|
+
it { should be_listable_resource }
|
4
|
+
#it { should be_updatable_resource }
|
5
|
+
|
6
|
+
it "should return contact on create" do
|
7
|
+
client = authorized_civicrm_client
|
8
|
+
|
9
|
+
client.expects(:post).once.returns(test_response(test_contact))
|
10
|
+
c = CiviCrm::Contact.create
|
11
|
+
c.should be_a_kind_of(CiviCrm::Contact)
|
12
|
+
end
|
13
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module CiviCrm
|
2
|
+
class Client
|
3
|
+
class << self
|
4
|
+
@mock_rest_client = nil
|
5
|
+
def mock_rest_client(mock_client)
|
6
|
+
@mock_rest_client = mock_client
|
7
|
+
end
|
8
|
+
|
9
|
+
alias_method :execute!, :execute
|
10
|
+
def execute(opts)
|
11
|
+
get_params = (opts[:headers] || {})[:params]
|
12
|
+
post_params = opts[:payload]
|
13
|
+
case opts[:method]
|
14
|
+
when :get then @mock_rest_client.get opts[:url], get_params, post_params
|
15
|
+
when :post then @mock_rest_client.post opts[:url], get_params, post_params
|
16
|
+
when :put then @mock_rest_client.put opts[:url], get_params, post_params
|
17
|
+
when :delete then @mock_rest_client.delete opts[:url], get_params, post_params
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
def authorized_civicrm_client
|
24
|
+
CiviCrm.api_key = 'civicrm'
|
25
|
+
CiviCrm.site_key = 'civicrm'
|
26
|
+
CiviCrm::Client.mock_rest_client(mock)
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module CiviCrm::TestResponses
|
2
|
+
def test_response(body, code = 200)
|
3
|
+
body = CiviCrm::XML.encode(body) if !(body.kind_of? String)
|
4
|
+
m = mock
|
5
|
+
m.instance_variable_set('@data', { body: body, code: code})
|
6
|
+
def m.body; @data[:body]; end
|
7
|
+
def m.code; @data[:code]; end
|
8
|
+
m
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_contact(params = {})
|
12
|
+
{
|
13
|
+
:id => '1',
|
14
|
+
:contact_id => '1',
|
15
|
+
:contact_type => 'Organization',
|
16
|
+
:sort_name => 'Inner City Arts',
|
17
|
+
:display_name => 'Inner City Arts',
|
18
|
+
:name => 'Bruce Lee',
|
19
|
+
:description => 'Foo bar',
|
20
|
+
:created => '1345537759',
|
21
|
+
:livemode => false
|
22
|
+
}.merge(params)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_contact_array
|
26
|
+
{
|
27
|
+
:count => 3, :is_error => 0,
|
28
|
+
:values => [test_contact, test_contact, test_contact]
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
RSpec::Matchers.define :be_listable_resource do |expected|
|
2
|
+
match do |actual|
|
3
|
+
client = authorized_civicrm_client
|
4
|
+
subject = actual.class
|
5
|
+
test_response_hash = send(:"test_#{subject.name.demodulize.underscore}_array")
|
6
|
+
|
7
|
+
client.expects(:get).once.returns(test_response(test_response_hash))
|
8
|
+
c = subject.all
|
9
|
+
|
10
|
+
c.should be_a_kind_of(Array)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
RSpec::Matchers.define :be_updatable_resource do |expected|
|
14
|
+
match do |actual|
|
15
|
+
client = authorized_civicrm_client
|
16
|
+
subject = actual.class
|
17
|
+
|
18
|
+
client.expects(:get).once.returns(test_response(test_contact({name: "foo"})))
|
19
|
+
client.expects(:put).once.returns(test_response(test_contact({name: "bar"})))
|
20
|
+
c = subject.find("resource_id")
|
21
|
+
c.name.should == "foo"
|
22
|
+
c.name = "bar"
|
23
|
+
c.save
|
24
|
+
c.name.should == "bar"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
RSpec::Matchers.define :be_deleteable_resource do |expected|
|
28
|
+
match do |actual|
|
29
|
+
client = authorized_civicrm_client
|
30
|
+
subject = actual.class
|
31
|
+
|
32
|
+
client.expects(:get).once.returns(test_response(test_contact({name: "foo"})))
|
33
|
+
client.expects(:delete).once.returns(test_response(test_contact({name: "bar"})))
|
34
|
+
c = subject.find("resource_id")
|
35
|
+
c.delete
|
36
|
+
end
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: civicrm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Iskander Haziev
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rest-client
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.4'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.4'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activesupport
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: activemodel
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: nokogiri
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rake
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rspec
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: mocha
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
description: See http://civicrm.org for details.
|
127
|
+
email:
|
128
|
+
- gvalmon@gmail.com
|
129
|
+
executables:
|
130
|
+
- civicrm
|
131
|
+
extensions: []
|
132
|
+
extra_rdoc_files: []
|
133
|
+
files:
|
134
|
+
- .gitignore
|
135
|
+
- .travis.yml
|
136
|
+
- CONTRIBUTORS
|
137
|
+
- Gemfile
|
138
|
+
- Gemfile.lock
|
139
|
+
- History.txt
|
140
|
+
- LICENSE
|
141
|
+
- README.md
|
142
|
+
- Rakefile
|
143
|
+
- VERSION
|
144
|
+
- bin/civicrm
|
145
|
+
- civicrm.gemspec
|
146
|
+
- lib/civicrm.rb
|
147
|
+
- lib/civicrm/actions/create.rb
|
148
|
+
- lib/civicrm/actions/destroy.rb
|
149
|
+
- lib/civicrm/actions/find.rb
|
150
|
+
- lib/civicrm/actions/list.rb
|
151
|
+
- lib/civicrm/actions/update.rb
|
152
|
+
- lib/civicrm/client.rb
|
153
|
+
- lib/civicrm/errors.rb
|
154
|
+
- lib/civicrm/resource.rb
|
155
|
+
- lib/civicrm/resources/base.rb
|
156
|
+
- lib/civicrm/version.rb
|
157
|
+
- lib/civicrm/xml.rb
|
158
|
+
- spec/requests/base_spec.rb
|
159
|
+
- spec/resources/contact_spec.rb
|
160
|
+
- spec/spec_helper.rb
|
161
|
+
- spec/support/civicrm.rb
|
162
|
+
- spec/support/civicrm_responses.rb
|
163
|
+
- spec/support/test_matchers.rb
|
164
|
+
homepage: http://civicrm.org
|
165
|
+
licenses: []
|
166
|
+
post_install_message:
|
167
|
+
rdoc_options: []
|
168
|
+
require_paths:
|
169
|
+
- lib
|
170
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
171
|
+
none: false
|
172
|
+
requirements:
|
173
|
+
- - ! '>='
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: '0'
|
176
|
+
segments:
|
177
|
+
- 0
|
178
|
+
hash: -4533903245477044172
|
179
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
180
|
+
none: false
|
181
|
+
requirements:
|
182
|
+
- - ! '>='
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
version: '0'
|
185
|
+
segments:
|
186
|
+
- 0
|
187
|
+
hash: -4533903245477044172
|
188
|
+
requirements: []
|
189
|
+
rubyforge_project:
|
190
|
+
rubygems_version: 1.8.25
|
191
|
+
signing_key:
|
192
|
+
specification_version: 3
|
193
|
+
summary: Ruby bindings for the CiviCRM API
|
194
|
+
test_files: []
|