sugarcrm 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/README.rdoc +15 -10
  2. data/Rakefile +4 -1
  3. data/VERSION +1 -1
  4. data/lib/sugarcrm.rb +107 -119
  5. data/lib/sugarcrm/api/get_available_modules.rb +17 -0
  6. data/lib/sugarcrm/api/get_document_revision.rb +14 -0
  7. data/lib/sugarcrm/{get_entries.rb → api/get_entries.rb} +2 -2
  8. data/lib/sugarcrm/api/get_entries_count.rb +20 -0
  9. data/lib/sugarcrm/{get_entry.rb → api/get_entry.rb} +5 -8
  10. data/lib/sugarcrm/{get_entry_list.rb → api/get_entry_list.rb} +2 -2
  11. data/lib/sugarcrm/{get_module_fields.rb → api/get_module_fields.rb} +6 -3
  12. data/lib/sugarcrm/api/get_note_attachment.rb +15 -0
  13. data/lib/sugarcrm/{get_relationships.rb → api/get_relationship.rb} +9 -4
  14. data/lib/sugarcrm/api/get_report_entries.rb +19 -0
  15. data/lib/sugarcrm/api/get_server_info.rb +7 -0
  16. data/lib/sugarcrm/api/get_user_id.rb +13 -0
  17. data/lib/sugarcrm/api/get_user_team_id.rb +14 -0
  18. data/lib/sugarcrm/api/login.rb +18 -0
  19. data/lib/sugarcrm/api/logout.rb +15 -0
  20. data/lib/sugarcrm/api/seamless_login.rb +13 -0
  21. data/lib/sugarcrm/api/search_by_module.rb +24 -0
  22. data/lib/sugarcrm/api/set_campaign_merge.rb +15 -0
  23. data/lib/sugarcrm/api/set_document_revision.rb +15 -0
  24. data/lib/sugarcrm/api/set_entries.rb +15 -0
  25. data/lib/sugarcrm/api/set_entry.rb +15 -0
  26. data/lib/sugarcrm/api/set_note_attachment.rb +3 -0
  27. data/lib/sugarcrm/api/set_relationship.rb +18 -0
  28. data/lib/sugarcrm/api/set_relationships.rb +22 -0
  29. data/lib/sugarcrm/connection.rb +112 -0
  30. data/lib/sugarcrm/exceptions.rb +16 -0
  31. data/lib/sugarcrm/module.rb +11 -0
  32. data/lib/sugarcrm/request.rb +28 -0
  33. data/lib/sugarcrm/response.rb +29 -0
  34. data/test/helper.rb +5 -0
  35. data/test/test_connection.rb +60 -0
  36. data/test/test_response.rb +36 -0
  37. data/test/test_sugarcrm.rb +31 -45
  38. metadata +56 -17
  39. data/lib/net/http_digest_auth.rb +0 -44
  40. data/lib/stdlib/array.rb +0 -14
  41. data/lib/stdlib/hash.rb +0 -17
  42. data/lib/stdlib/object.rb +0 -5
  43. data/test/test_json_to_obj.rb +0 -48
data/README.rdoc CHANGED
@@ -6,18 +6,19 @@ REST Bindings for SugarCRM!
6
6
 
7
7
  == SUMMARY:
8
8
 
9
- Ruby Gem for interacting with SugarCRM via REST
9
+ Ruby Gem for interacting with SugarCRM via REST.
10
10
 
11
- == FEATURES/PROBLEMS:
11
+ == Description:
12
+
13
+ I've implemented all of the basic API calls that SugarCRM supports, and am actively building an abstraction layer
14
+ on top of the basic API methods. The end result will be to provide ActiveRecord style finders and first class
15
+ objects. Some of this functionality is included today.
12
16
 
13
- * The response hash gets converted into a Ruby object, via Hash#to_obj method.
14
- * Only the following methods are currently implemented:
17
+ == FEATURES/PROBLEMS:
15
18
 
16
- * get_entry
17
- * get_entries
18
- * get_entry_list
19
- * get_module_fields
20
- * get_relationships
19
+ * Auto-generation of Module specific objects. When a connection is established, get_available_modules is called
20
+ and the resultant modules are turned into SugarCRM::Module classes.
21
+ * If you just want to use the vanilla API, you can access the methods directly on the connection object.
21
22
 
22
23
  == SYNOPSIS:
23
24
 
@@ -25,7 +26,7 @@ Ruby Gem for interacting with SugarCRM via REST
25
26
  sugarcrm = SugarCRM::Base.new("http://localhost/sugarcrm", 'user', 'password', {:debug => false})
26
27
 
27
28
  # Lookup a user by name. Find any associated accounts
28
- sugarcrm.get_entry_list(
29
+ sugarcrm.connection.get_entry_list(
29
30
  "Users",
30
31
  "users.user_name = \'#{USER}\'",
31
32
  {
@@ -39,8 +40,12 @@ Ruby Gem for interacting with SugarCRM via REST
39
40
  }
40
41
  )
41
42
 
43
+ # Retrieve a user by ID, using the SugarCRM::User Proxy object
44
+ user = SugarCRM::User.find(id)
45
+
42
46
  == REQUIREMENTS:
43
47
 
48
+ * activesupport gem
44
49
  * json gem
45
50
 
46
51
  == INSTALL:
data/Rakefile CHANGED
@@ -6,12 +6,15 @@ begin
6
6
  Jeweler::Tasks.new do |gem|
7
7
  gem.name = "sugarcrm"
8
8
  gem.summary = %Q{Ruby based REST client for SugarCRM}
9
- gem.description = %Q{A Ruby based REST client for SugarCRM.}
9
+ gem.description = %Q{I've implemented all of the basic API calls that SugarCRM supports, and am actively building an abstraction layer
10
+ on top of the basic API methods. The end result will be to provide ActiveRecord style finders and first class
11
+ objects. Some of this functionality is included today.}
10
12
  gem.email = "carl.hicks@gmail.com"
11
13
  gem.homepage = "http://github.com/chicks/sugarcrm"
12
14
  gem.authors = ["Carl Hicks"]
13
15
  gem.add_development_dependency "shoulda", ">= 0"
14
16
  gem.add_dependency "json", ">= 0"
17
+ gem.add_dependency "activesupport", ">= 0"
15
18
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
19
  end
17
20
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.3
1
+ 0.6.0
data/lib/sugarcrm.rb CHANGED
@@ -3,153 +3,141 @@
3
3
  module SugarCRM
4
4
 
5
5
  Dir["#{File.dirname(__FILE__)}/sugarcrm/**/*.rb"].each { |f| load(f) }
6
- Dir["#{File.dirname(__FILE__)}/stdlib/**/*.rb"].each { |f| load(f) }
7
6
 
8
7
  require 'pp'
9
- require 'ostruct'
10
8
  require 'uri'
11
9
  require 'net/https'
12
- require 'openssl'
13
10
  require 'digest/md5'
14
11
 
15
12
  require 'rubygems'
13
+ require 'active_support/core_ext'
16
14
  require 'json'
17
-
18
- class LoginError < RuntimeError
19
- end
20
-
21
- class EmptyResponse < RuntimeError
22
- end
23
-
24
- class UnhandledResponse < RuntimeError
25
- end
26
-
27
- class InvalidSugarCRMUrl < RuntimeError
28
- end
15
+ require 'json/add/rails'
29
16
 
30
17
  class Base
31
-
32
- URL = "/service/v2/rest.php"
33
- attr :url, true
34
- attr :user, false
35
- attr :pass, false
36
- attr :ssl, false
37
- attr :connection, true
38
- attr :session, true
18
+ # Unset all of the instance methods we don't need.
19
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$|^define_method$|^class$|^instance_of.$)/ }
20
+
21
+ # This holds our connection
22
+ cattr_accessor :connection, :instance_writer => false
23
+
24
+ # Contains the name of the module in SugarCRM
25
+ class_attribute :module_name
26
+ self.module_name = self.name.split(/::/)[-1]
27
+
28
+ # Contains the fields found on the current module
29
+ class_attribute :module_fields
30
+ self.module_fields = {}
31
+
32
+ # Tracks if we have extended our class with attribute methods yet.
33
+ class_attribute :attribute_methods_generated
34
+ self.attribute_methods_generated = false
35
+
36
+ # Contains a list of attributes
37
+ attr :attributes, true
39
38
  attr :debug, true
40
- attr :to_obj, true
41
39
 
42
- def initialize(url, user, pass, opts={})
40
+ def self.establish_connection(url, user, pass, opts={})
43
41
  options = {
44
42
  :debug => false,
45
- :to_obj => true
46
43
  }.merge(opts)
47
-
48
44
  @debug = options[:debug]
49
- @to_obj = options[:to_obj]
50
-
51
- @url = URI.parse(url)
52
- @user = user
53
- @pass = pass
54
-
55
- # Handles http/https in url string
56
- @ssl = false
57
- @ssl = true if @url.scheme == "https"
58
-
59
- # Appends the rest.php path onto the end of the URL if it's not included
60
- if @url.path !~ /rest.php$/
61
- @url.path += URL
62
- end
63
-
64
- login!
65
- raise LoginError, "Invalid Login" unless logged_in?
45
+ @@connection = SugarCRM::Connection.new(url, user, pass, @debug)
66
46
  end
67
-
68
- def connected?
69
- return false unless @connection
70
- return false unless @connection.started?
71
- true
47
+
48
+ # Registers the module fields on the class
49
+ def self.register_module_fields
50
+ self.module_fields = connection.get_fields(self.module_name)["module_fields"] if self.module_fields.length == 0
51
+ end
52
+
53
+ def initialize(attributes={})
54
+ @attributes = attributes_from_module_fields.merge(attributes)
55
+ define_attribute_methods
56
+ end
57
+
58
+ def inspect
59
+ self
60
+ end
61
+
62
+ def to_s
63
+ attrs = []
64
+ @attributes.each_key do |k|
65
+ attrs << "#{k}: #{attribute_for_inspect(k)}"
66
+ end
67
+ "#<#{self.class} #{attrs.join(", ")}>"
72
68
  end
73
69
 
74
- def logged_in?
75
- @session ? true : false
70
+ # Returns an <tt>#inspect</tt>-like string for the value of the
71
+ # attribute +attr_name+. String attributes are elided after 50
72
+ # characters, and Date and Time attributes are returned in the
73
+ # <tt>:db</tt> format. Other attributes return the value of
74
+ # <tt>#inspect</tt> without modification.
75
+ #
76
+ # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
77
+ #
78
+ # person.attribute_for_inspect(:name)
79
+ # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
80
+ #
81
+ # person.attribute_for_inspect(:created_at)
82
+ # # => '"2009-01-12 04:48:57"'
83
+ def attribute_for_inspect(attr_name)
84
+ value = read_attribute(attr_name)
85
+
86
+ if value.is_a?(String) && value.length > 50
87
+ "#{value[0..50]}...".inspect
88
+ elsif value.is_a?(Date) || value.is_a?(Time)
89
+ %("#{value.to_s(:db)}")
90
+ else
91
+ value.inspect
92
+ end
76
93
  end
77
94
 
78
95
  protected
79
96
 
80
- def connect!
81
- @connection = Net::HTTP.new(@url.host, @url.port)
82
- if @ssl
83
- @connection.use_ssl = true
84
- @connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
97
+ # Generates get/set methods for keys in the attributes hash
98
+ def define_attribute_methods
99
+ return if attribute_methods_generated?
100
+ @attributes.each_pair do |k,v|
101
+ self.class.module_eval %Q?
102
+ def #{k}
103
+ read_attribute :#{k}
85
104
  end
86
- @connection.start
87
- end
88
-
89
- def login!
90
- connect! unless connected?
91
- json = <<-EOF
92
- {
93
- \"user_auth\": {
94
- \"user_name\": \"#{@user}\"\,
95
- \"password\": \"#{OpenSSL::Digest::MD5.new(@pass)}\"\,
96
- \"version\": \"2\"\,
97
- },
98
- \"application\": \"\"
99
- }
100
- EOF
101
- json.gsub!(/^\s{8}/,'')
102
- response = get(:login, json)
103
-
104
- if @to_obj
105
- @session = response.id
106
- else
107
- @session = response["id"]
105
+ def #{k}=(value)
106
+ write_attribute :#{k},value
108
107
  end
108
+ ?
109
109
  end
110
+ self.class.attribute_methods_generated = true
111
+ end
110
112
 
111
- def get(method,json)
112
- query = @url.path.dup
113
- query << '?method=' << method.to_s
114
- query << '&input_type=JSON'
115
- query << '&response_type=JSON'
116
- query << '&rest_data=' << json
117
-
118
- if @debug
119
- puts "#{method}: Request"
120
- puts query
121
- puts "\n"
122
- end
123
- response = @connection.get(URI.escape(query))
124
-
125
- case response
126
- when Net::HTTPOK
127
- raise EmptyResponse unless response.body
128
- response_json = JSON.parse response.body
129
- return false if response_json["result_count"] == 0
130
- if @debug
131
- puts "#{method}: JSON Response:"
132
- pp response_json
133
- puts "\n"
134
- end
135
- response_obj = response_json.to_obj
136
-
137
- if @to_obj
138
- return response_obj
139
- else
140
- return response_json
141
- end
142
- when Net::HTTPNotFound
143
- raise InvalidSugarCRMUrl, "#{@url} is invalid"
144
- else
145
- if @debug
146
- puts "#{method}: Raw Response:"
147
- puts response.body
148
- puts "\n"
149
- end
150
- raise UnhandledResponse, "Can't handle response #{response}"
151
- end
113
+ # Wrapper around class attribute
114
+ def attribute_methods_generated?
115
+ self.class.attribute_methods_generated
116
+ end
117
+
118
+ def module_fields_registered?
119
+ self.class.module_fields.length > 0
120
+ end
121
+
122
+ # Returns a hash of the module fields from the
123
+ def attributes_from_module_fields
124
+ self.class.register_module_fields unless module_fields_registered?
125
+ fields = {}
126
+ self.class.module_fields.keys.sort.each do |k|
127
+ fields[k.to_s] = nil
152
128
  end
129
+ fields
130
+ end
131
+
132
+ # Wrapper around attributes hash
133
+ def read_attribute(key)
134
+ @attributes[key]
135
+ end
136
+
137
+ # Wrapper around attributes hash
138
+ def write_attribute(key, value)
139
+ @attributes[key] = value
140
+ end
153
141
 
154
142
  end
155
143
 
@@ -0,0 +1,17 @@
1
+ module SugarCRM; class Connection
2
+ # Retrieves the list of modules available to the current user logged into the system.
3
+ def get_available_modules
4
+ login! unless logged_in?
5
+ json = <<-EOF
6
+ {
7
+ \"session\": \"#{@session}\"
8
+ }
9
+ EOF
10
+
11
+ json.gsub!(/^\s{6}/,'')
12
+ get(:get_available_modules, json)["modules"]
13
+ end
14
+
15
+ alias :get_modules :get_available_modules
16
+
17
+ end; end
@@ -0,0 +1,14 @@
1
+ module SugarCRM; class Connection
2
+ # Downloads a particular revision of a document.
3
+ def get_document_revision(id)
4
+ login! unless logged_in?
5
+ json = <<-EOF
6
+ {
7
+ \"session\": \"#{@session}\"\,
8
+ \"id\": #{id}
9
+ }
10
+ EOF
11
+ json.gsub!(/^\s{6}/,'')
12
+ get(:get_document_revision, json)
13
+ end
14
+ end; end
@@ -1,4 +1,4 @@
1
- module SugarCRM; class Base
1
+ module SugarCRM; class Connection
2
2
  # Retrieve a list of SugarBeans by ID. This method will not
3
3
  # work with the report module.
4
4
  def get_entries(module_name, ids, options={})
@@ -12,7 +12,7 @@ def get_entries(module_name, ids, options={})
12
12
  {
13
13
  \"session\": \"#{@session}\"\,
14
14
  \"module_name\": \"#{module_name}\"\,
15
- \"ids\": \"#{ids.to_json}\"\,
15
+ \"ids\": #{ids.to_json}\,
16
16
  \"select_fields\": #{options[:fields].to_json}\,
17
17
  \"link_name_to_fields_array\": #{options[:link_fields].to_json}\,
18
18
  }
@@ -0,0 +1,20 @@
1
+ module SugarCRM; class Connection
2
+ # Retrieves the specified number of records in a module.
3
+ def get_entries_count(module_name, query, options={})
4
+ login! unless logged_in?
5
+ {
6
+ :deleted => 0,
7
+ }.merge! options
8
+
9
+ json = <<-EOF
10
+ {
11
+ \"session\": \"#{@session}\"\,
12
+ \"module_name\": \"#{module_name}\"\,
13
+ \"query\": \"#{query}\"\,
14
+ \"deleted\": #{options[:deleted]}
15
+ }
16
+ EOF
17
+ json.gsub!(/^\s{6}/,'')
18
+ get(:get_entries_count, json)
19
+ end
20
+ end; end
@@ -1,4 +1,5 @@
1
- module SugarCRM; class Base
1
+ module SugarCRM; class Connection
2
+ # Retrieves a single SugarBean based on the ID.
2
3
  def get_entry(module_name, id, options={})
3
4
  login! unless logged_in?
4
5
  { :fields => [],
@@ -11,15 +12,11 @@ module SugarCRM; class Base
11
12
  \"module_name\": \"#{module_name}\"\,
12
13
  \"id\": \"#{id}\"\,
13
14
  \"select_fields\": #{options[:fields].to_json}\,
15
+ \"link_name_to_fields_array\": #{options[:link_fields]}\,
14
16
  }
15
17
  EOF
16
-
17
- placeholder = <<-EOF
18
- \"select_fields\": [\"name\"]
19
- \"link_name_to_fields_array\": \"#{options[:link_fields]}\"\,
20
- EOF
21
-
18
+
22
19
  json.gsub!(/^\s{6}/,'')
23
- get(:get_entry, json)
20
+ SugarCRM::Response.new(get(:get_entry, json))
24
21
  end
25
22
  end; end
@@ -1,8 +1,8 @@
1
- module SugarCRM; class Base
1
+ module SugarCRM; class Connection
2
2
  # Retrieve a list of SugarBeans. This is the primary method for getting
3
3
  # a list of SugarBeans using the REST API.
4
4
  def get_entry_list(module_name, query, options={})
5
- login! unless logged_in?
5
+ login! unless logged_in?
6
6
  {
7
7
  :order_by => '',
8
8
  :offset => '',
@@ -1,7 +1,8 @@
1
- module SugarCRM; class Base
2
-
1
+ module SugarCRM; class Connection
2
+
3
+ # Retrieves the vardef information on fields of the specified bean.
3
4
  def get_module_fields(module_name)
4
- login! unless logged_in?
5
+ login! unless logged_in?
5
6
  json = <<-"EOF"
6
7
  {
7
8
  \"session\": \"#{@session}\"\,
@@ -12,4 +13,6 @@ def get_module_fields(module_name)
12
13
  get(:get_module_fields, json)
13
14
  end
14
15
 
16
+ alias :get_fields :get_module_fields
17
+
15
18
  end; end