sugarcrm 0.8.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/README.rdoc +58 -19
  2. data/Rakefile +1 -4
  3. data/VERSION +1 -1
  4. data/lib/sugarcrm.rb +7 -4
  5. data/lib/sugarcrm/associations.rb +2 -0
  6. data/lib/sugarcrm/associations/association_collection.rb +143 -0
  7. data/lib/sugarcrm/associations/association_methods.rb +92 -0
  8. data/lib/sugarcrm/attributes.rb +4 -0
  9. data/lib/sugarcrm/attributes/attribute_methods.rb +131 -0
  10. data/lib/sugarcrm/attributes/attribute_serializers.rb +55 -0
  11. data/lib/sugarcrm/attributes/attribute_typecast.rb +39 -0
  12. data/lib/sugarcrm/attributes/attribute_validations.rb +37 -0
  13. data/lib/sugarcrm/base.rb +77 -21
  14. data/lib/sugarcrm/connection.rb +3 -137
  15. data/lib/sugarcrm/connection/api/get_entry_list.rb +1 -1
  16. data/lib/sugarcrm/connection/api/get_note_attachment.rb +0 -1
  17. data/lib/sugarcrm/connection/api/set_relationship.rb +3 -0
  18. data/lib/sugarcrm/connection/connection.rb +144 -0
  19. data/lib/sugarcrm/{request.rb → connection/request.rb} +2 -10
  20. data/lib/sugarcrm/{response.rb → connection/response.rb} +10 -4
  21. data/lib/sugarcrm/exceptions.rb +14 -26
  22. data/lib/sugarcrm/module.rb +19 -18
  23. data/test/connection/test_get_entry.rb +5 -5
  24. data/test/connection/test_get_module_fields.rb +1 -1
  25. data/test/connection/test_set_relationship.rb +13 -21
  26. data/test/helper.rb +1 -1
  27. data/test/test_association_collection.rb +12 -0
  28. data/test/test_associations.rb +33 -0
  29. data/test/test_connection.rb +0 -7
  30. data/test/test_module.rb +1 -1
  31. data/test/test_sugarcrm.rb +16 -8
  32. metadata +22 -28
  33. data/lib/sugarcrm/association_methods.rb +0 -46
  34. data/lib/sugarcrm/attribute_methods.rb +0 -170
@@ -2,7 +2,6 @@ module SugarCRM; class Connection
2
2
  # Retrieves an attachment from a note.
3
3
  def get_note_attachment(id)
4
4
  login! unless logged_in?
5
-
6
5
  json = <<-EOF
7
6
  {
8
7
  \"session\": \"#{@session}\"\,
@@ -19,6 +19,9 @@ module SugarCRM; class Connection
19
19
  }
20
20
  EOF
21
21
  json.gsub!(/^\s{6}/,'')
22
+ # TODO: Add a handler for the response. By default it returns a hash like:
23
+ # {failed => 0, deleted => 0, created => 1}. We should add this to the
24
+ # Response.handle method and return true/false depending on the outcome.
22
25
  send!(:set_relationship, json)
23
26
  end
24
27
  end; end
@@ -0,0 +1,144 @@
1
+ module SugarCRM; class Connection
2
+
3
+ URL = "/service/v2/rest.php"
4
+ # Set this to filter out debug output on a certain method (i.e. get_modules, or get_fields)
5
+ DONT_SHOW_DEBUG_FOR = []
6
+ RESPONSE_IS_NOT_JSON = [:get_user_id, :get_user_team_id]
7
+
8
+ attr :url, true
9
+ attr :user, false
10
+ attr :pass, false
11
+ attr :session, true
12
+ attr :connection, true
13
+ attr :options, true
14
+ attr :request, true
15
+ attr :response, true
16
+
17
+ # This is the singleton connection class.
18
+ def initialize(url, user, pass, options={})
19
+ @options = {
20
+ :debug => false,
21
+ :register_modules => true
22
+ }.merge(options)
23
+ @url = URI.parse(url)
24
+ @user = user
25
+ @pass = pass
26
+ @request = ""
27
+ @response = ""
28
+ resolve_url
29
+ login!
30
+ self
31
+ end
32
+
33
+ # Check to see if we are logged in
34
+ def logged_in?
35
+ connect! unless connected?
36
+ @session ? true : false
37
+ end
38
+
39
+ # Login
40
+ def login!
41
+ @session = login["id"]
42
+ raise SugarCRM::LoginError, "Invalid Login" unless logged_in?
43
+ SugarCRM.connection = self
44
+ SugarCRM::Base.connection = self
45
+ Module.register_all if @options[:register_modules]
46
+ end
47
+
48
+ # Check to see if we are connected
49
+ def connected?
50
+ return false unless @connection
51
+ return false unless @connection.started?
52
+ true
53
+ end
54
+
55
+ # Connect
56
+ def connect!
57
+ @connection = Net::HTTP.new(@url.host, @url.port)
58
+ if @url.scheme == "https"
59
+ @connection.use_ssl = true
60
+ @connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
61
+ end
62
+ @connection.start
63
+ end
64
+
65
+ # Send a request to the Sugar Instance
66
+ def send!(method, json)
67
+ @request = SugarCRM::Request.new(@url, method, json, @options[:debug])
68
+ begin
69
+ if @request.length > 3900
70
+ @response = @connection.post(@url.path, @request)
71
+ else
72
+ @response = @connection.get(@url.path.dup + "?" + @request.to_s)
73
+ end
74
+ rescue Errno::ECONNRESET
75
+ retry!(method, json)
76
+ rescue EOFError
77
+ retry!(method, json)
78
+ end
79
+ handle_response
80
+ end
81
+
82
+ # Sometimes our connection just disappears but we still have a session.
83
+ # This method forces a reconnect and relogin to update the session and resend
84
+ # the request.
85
+ def retry!(method, json)
86
+ connect!
87
+ login!
88
+ send!(method,json)
89
+ end
90
+
91
+ def debug=(debug)
92
+ options[:debug] = debug
93
+ end
94
+
95
+ def debug?
96
+ options[:debug]
97
+ end
98
+
99
+ private
100
+
101
+ def handle_response
102
+ case @response
103
+ when Net::HTTPOK
104
+ return process_response
105
+ when Net::HTTPNotFound
106
+ raise SugarCRM::InvalidSugarCRMUrl, "#{@url} is invalid"
107
+ when Net::HTTPInternalServerError
108
+ raise SugarCRM::InvalidRequest, "#{@request} is invalid"
109
+ else
110
+ if @options[:debug]
111
+ puts "#{@request.method}: Raw Response:"
112
+ puts @response.body
113
+ puts "\n"
114
+ end
115
+ raise SugarCRM::UnhandledResponse, "Can't handle response #{@response}"
116
+ end
117
+ end
118
+
119
+ def process_response
120
+ # Complain if our body is empty.
121
+ raise SugarCRM::EmptyResponse unless @response.body
122
+ # Some methods are dumb and don't return a JSON Response
123
+ return @response.body if RESPONSE_IS_NOT_JSON.include? @request.method
124
+ # Push it through the old meat grinder.
125
+ response_json = JSON.parse @response.body
126
+ # Empty result. Is this wise?
127
+ return false if response_json["result_count"] == 0
128
+ # Filter debugging on REALLY BIG responses
129
+ if @options[:debug] && !(DONT_SHOW_DEBUG_FOR.include? @request.method)
130
+ puts "#{@request.method}: JSON Response:"
131
+ pp response_json
132
+ puts "\n"
133
+ end
134
+ return response_json
135
+ end
136
+
137
+ def resolve_url
138
+ # Appends the rest.php path onto the end of the URL if it's not included
139
+ if @url.path !~ /rest.php$/
140
+ @url.path += URL
141
+ end
142
+ end
143
+
144
+ end; end
@@ -1,7 +1,4 @@
1
- module SugarCRM
2
-
3
- class Request
4
-
1
+ module SugarCRM; class Request
5
2
  attr :request, true
6
3
  attr :url, true
7
4
  attr :method, true
@@ -12,12 +9,10 @@ class Request
12
9
  @url = url
13
10
  @method = method
14
11
  @json = json
15
-
16
12
  @request = 'method=' << @method.to_s
17
13
  @request << '&input_type=JSON'
18
14
  @request << '&response_type=JSON'
19
15
  @request << '&rest_data=' << @json
20
-
21
16
  if debug
22
17
  puts "#{method}: Request:"
23
18
  pp @request
@@ -33,7 +28,4 @@ class Request
33
28
  def to_s
34
29
  URI.escape(@request)
35
30
  end
36
-
37
- end
38
-
39
- end
31
+ end; end
@@ -1,5 +1,4 @@
1
1
  module SugarCRM; class Response
2
-
3
2
  class << self
4
3
  # This class handles the response from the server.
5
4
  # It tries to convert the response into an object such as User
@@ -8,11 +7,17 @@ module SugarCRM; class Response
8
7
  r = new(json)
9
8
  begin
10
9
  return r.to_obj
10
+ rescue UninitializedModule => e
11
+ raise e
12
+ rescue InvalidAttribute => e
13
+ raise e
14
+ rescue InvalidAttributeType => e
15
+ raise e
11
16
  rescue => e
12
17
  if SugarCRM.connection.debug?
13
18
  puts "Failed to process JSON:"
14
19
  pp json
15
- puts e
20
+ raise e
16
21
  end
17
22
  return json
18
23
  end
@@ -23,6 +28,7 @@ module SugarCRM; class Response
23
28
 
24
29
  def initialize(json)
25
30
  @response = json
31
+ @response = json.with_indifferent_access if json.is_a? Hash
26
32
  end
27
33
 
28
34
  # Tries to instantiate and return an object with the values
@@ -35,13 +41,13 @@ module SugarCRM; class Response
35
41
  @response["entry_list"].each do |object|
36
42
  attributes = []
37
43
  _module = resolve_module(object)
38
- id = object["id"]
44
+ #id = object["id"]
39
45
  attributes = flatten_name_value_list(object)
40
46
  if SugarCRM.const_get(_module)
41
47
  if attributes.length == 0
42
48
  raise AttributeParsingError, "response contains objects without attributes!"
43
49
  end
44
- objects << SugarCRM.const_get(_module).new(id, attributes)
50
+ objects << SugarCRM.const_get(_module).new(attributes)
45
51
  else
46
52
  raise InvalidModule, "#{_module} does not exist, or is not accessible"
47
53
  end
@@ -1,28 +1,16 @@
1
1
  module SugarCRM
2
- class LoginError < RuntimeError
3
- end
4
-
5
- class EmptyResponse < RuntimeError
6
- end
7
-
8
- class UnhandledResponse < RuntimeError
9
- end
10
-
11
- class InvalidSugarCRMUrl < RuntimeError
12
- end
13
-
14
- class InvalidRequest < RuntimeError
15
- end
16
-
17
- class InvalidModule <RuntimeError
18
- end
19
-
20
- class AttributeParsingError < RuntimeError
21
- end
22
-
23
- class RecordNotFound < RuntimeError
24
- end
25
-
26
- class InvalidRecord < RuntimeError
27
- end
2
+ class LoginError < RuntimeError; end
3
+ class EmptyResponse < RuntimeError; end
4
+ class UnhandledResponse < RuntimeError; end
5
+ class InvalidSugarCRMUrl < RuntimeError; end
6
+ class InvalidRequest < RuntimeError; end
7
+ class InvalidModule <RuntimeError; end
8
+ class AttributeParsingError < RuntimeError; end
9
+ class RecordNotFound < RuntimeError; end
10
+ class InvalidRecord < RuntimeError; end
11
+ class RecordSaveFailed < RuntimeError; end
12
+ class AssociationFailed < RuntimeError; end
13
+ class UninitializedModule < RuntimeError; end
14
+ class InvalidAttribute < RuntimeError; end
15
+ class InvalidAttributeType < RuntimeError; end
28
16
  end
@@ -15,48 +15,49 @@ module SugarCRM
15
15
  @klass = name.classify
16
16
  @table_name = name.tableize
17
17
  @fields = {}
18
- @link_fields = {}
18
+ @link_fields = {}
19
19
  @fields_registered = false
20
20
  self
21
21
  end
22
22
 
23
+ # Returns the fields associated with the module
23
24
  def fields
24
- return @fields if fields?
25
+ return @fields if fields_registered?
25
26
  all_fields = SugarCRM.connection.get_fields(@name)
26
- @fields = all_fields["module_fields"]
27
- @link_fields= all_fields["link_fields"]
27
+ @fields = all_fields["module_fields"].with_indifferent_access
28
+ @link_fields= all_fields["link_fields"]
29
+ handle_empty_arrays
28
30
  @fields_registered = true
29
31
  @fields
30
32
  end
31
33
 
32
- def fields?
34
+ def fields_registered?
33
35
  @fields_registered
34
36
  end
35
37
 
38
+ alias :link_fields_registered? :fields_registered?
39
+
40
+ # Returns the required fields
36
41
  def required_fields
37
42
  required_fields = []
38
- ignore_fields = ["id", "date_entered", "date_modified"]
43
+ ignore_fields = [:id, :date_entered, :date_modified]
39
44
  self.fields.each_value do |field|
40
- next if ignore_fields.include? field["name"]
41
- required_fields << field["name"] if field["required"] == 1
45
+ next if ignore_fields.include? field["name"].to_sym
46
+ required_fields << field["name"].to_sym if field["required"] == 1
42
47
  end
43
48
  required_fields
44
49
  end
45
50
 
46
51
  def link_fields
47
- self.fields unless link_fields?
48
- handle_empty_array
52
+ self.fields unless link_fields_registered?
53
+ handle_empty_arrays
49
54
  @link_fields
50
55
  end
51
56
 
52
- def link_fields?
53
- @fields_registered
54
- end
55
-
56
- def handle_empty_array
57
- if @link_fields.class == Array && @link_fields.length == 0
58
- @link_fields = {}
59
- end
57
+ # TODO: Refactor this to be less repetitive
58
+ def handle_empty_arrays
59
+ @fields = {}.with_indifferent_access if @fields.length == 0
60
+ @link_fields = {}.with_indifferent_access if @link_fields.length == 0
60
61
  end
61
62
 
62
63
  # Registers a single module by name
@@ -7,17 +7,17 @@ class TestGetEntry < Test::Unit::TestCase
7
7
  @response = SugarCRM.connection.get_entry(
8
8
  "Users",
9
9
  1,
10
- {:fields => ["first_name", "last_name"]}
10
+ {:fields => ["first_name", "last_name", "deleted", "date_modified"]}
11
11
  )
12
12
  end
13
- # should "return a single entry when sent #get_entry." do
14
- # assert @response.response. "entry_list"
15
- # end
16
13
  should "return an object when #get_entry" do
17
14
  assert_instance_of SugarCRM::User, @response
18
15
  end
19
16
  should "typecast boolean fields properly" do
20
- assert !(@response.deleted)
17
+ assert_instance_of FalseClass, @response.deleted
18
+ end
19
+ should "typecast date_time fields properly" do
20
+ assert_instance_of DateTime, @response.date_modified
21
21
  end
22
22
  end
23
23
  end
@@ -7,7 +7,7 @@ class TestModuleFields < Test::Unit::TestCase
7
7
  end
8
8
  should "return a hash of module fields when #get_module_fields" do
9
9
  fields = SugarCRM.connection.get_module_fields("Users")
10
- assert_instance_of Hash, fields
10
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, fields
11
11
  assert "address_city", fields.keys[0]
12
12
  end
13
13
  end
@@ -1,27 +1,19 @@
1
1
  require 'helper'
2
2
 
3
- class TestSetRelationships < Test::Unit::TestCase
3
+ class TestSetRelationship < Test::Unit::TestCase
4
4
  context "A SugarCRM.connection" do
5
- setup do
6
- SugarCRM::Connection.new(URL, USER, PASS, {:register_modules => false, :debug => false})
7
-
8
- #retrieve ID of ACLRole and append shove it into the @id array
9
- @id = [SugarCRM::ACLRole.find_by_name("Marketing Administrator").id]
10
- assert @id
11
- end
12
-
13
- should "add the role found above to the user who's id is below when sent #set_relationship" do
14
- response = SugarCRM.connection.set_relationship(
15
- "Users","seed_will_id","aclroles",@id
16
- )
17
- assert_equal("failed0deleted0created1", response.to_s)
18
- end
19
-
20
- should "remove the role that was previously added when sent #set_relationship and delete=1" do
21
- response = SugarCRM.connection.set_relationship(
22
- "Users","seed_will_id","aclroles",@id,opts={:delete => 1}
23
- )
24
- assert_equal("failed0deleted1created0", response.to_s)
5
+ should "add and remove a relationship when #set_relationship" do
6
+ SugarCRM.connect(URL, USER, PASS, {:debug => false})
7
+ meeting = SugarCRM::Meeting.new
8
+ meeting.date_start = DateTime.now
9
+ meeting.duration_hours = 0.5
10
+ meeting.name = "Stupid Meeting"
11
+ assert meeting.save!
12
+ response = SugarCRM.connection.set_relationship("Users","1","meetings", [meeting.id])
13
+ assert response["created"] == 1
14
+ response = SugarCRM.connection.set_relationship("Users","1","meetings", [meeting.id], {:delete => 1})
15
+ assert response["deleted"] == 1
16
+ assert meeting.delete
25
17
  end
26
18
  end
27
19
  end
@@ -8,7 +8,7 @@ require 'sugarcrm'
8
8
 
9
9
  class Test::Unit::TestCase
10
10
  # Replace these with your test instance
11
- URL = "http://valet/sugarcrm"
11
+ URL = "http://localhost:8080/sugarcrm"
12
12
  USER = "admin"
13
13
  PASS = 'letmein'
14
14
 
@@ -0,0 +1,12 @@
1
+ require 'helper'
2
+
3
+ class TestAssociationCollection < Test::Unit::TestCase
4
+ context "A SugarCRM::AssociationCollection instance" do
5
+ should "create a new instance when #new" do
6
+ SugarCRM.connect!(URL, USER, PASS)
7
+ u = SugarCRM::User.find("seed_sarah_id")
8
+ ac = SugarCRM::AssociationCollection.new(u,:email_addresses)
9
+ assert_instance_of SugarCRM::AssociationCollection, ac
10
+ end
11
+ end
12
+ end