sugarcrm 0.9.7 → 0.9.8
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/lib/sugarcrm/associations.rb +3 -0
- data/lib/sugarcrm/associations/association.rb +104 -0
- data/lib/sugarcrm/associations/association_cache.rb +30 -0
- data/lib/sugarcrm/associations/association_collection.rb +4 -5
- data/lib/sugarcrm/associations/association_methods.rb +32 -63
- data/lib/sugarcrm/associations/associations.rb +62 -0
- data/lib/sugarcrm/attributes/attribute_methods.rb +3 -3
- data/lib/sugarcrm/base.rb +7 -11
- data/lib/sugarcrm/connection/api/get_entries.rb +20 -20
- data/lib/sugarcrm/connection/api/get_entries_count.rb +17 -17
- data/lib/sugarcrm/connection/api/get_entry_list.rb +28 -28
- data/lib/sugarcrm/connection/api/get_module_fields.rb +13 -17
- data/lib/sugarcrm/connection/api/get_note_attachment.rb +12 -12
- data/lib/sugarcrm/connection/api/get_relationships.rb +27 -31
- data/lib/sugarcrm/connection/api/get_report_entries.rb +14 -14
- data/lib/sugarcrm/connection/api/login.rb +1 -1
- data/lib/sugarcrm/connection/api/logout.rb +1 -1
- data/lib/sugarcrm/connection/api/seamless_login.rb +1 -1
- data/lib/sugarcrm/connection/helper.rb +28 -3
- data/lib/sugarcrm/connection/request.rb +1 -1
- data/lib/sugarcrm/connection/response.rb +3 -2
- data/lib/sugarcrm/exceptions.rb +1 -0
- data/lib/sugarcrm/module.rb +16 -14
- data/test/connection/test_get_relationships.rb +2 -2
- data/test/test_association.rb +1 -0
- data/test/test_associations.rb +6 -0
- data/test/test_module.rb +4 -3
- data/test/test_sugarcrm.rb +16 -1
- metadata +35 -38
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.9.
|
1
|
+
0.9.8
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module SugarCRM
|
2
|
+
# Represents an association and it's metadata
|
3
|
+
class Association
|
4
|
+
attr :owner
|
5
|
+
attr :target
|
6
|
+
attr :link_field
|
7
|
+
attr :attributes
|
8
|
+
attr :methods
|
9
|
+
|
10
|
+
def initialize(owner,link_field,opts={})
|
11
|
+
@options = { :define_methods? => true }.merge! opts
|
12
|
+
@owner = owner
|
13
|
+
check_valid_owner
|
14
|
+
@link_field = link_field
|
15
|
+
@attributes = owner.link_fields[link_field]
|
16
|
+
@target = resolve_target
|
17
|
+
@methods = define_methods if @options[:define_methods?]
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns true if the association includes an attribute that matches
|
22
|
+
# the provided string
|
23
|
+
def include?(attribute)
|
24
|
+
return true if attribute.class == @target
|
25
|
+
return true if attribute == link_field
|
26
|
+
return true if methods.include? attribute
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def check_valid_owner
|
33
|
+
valid = @owner.class.ancestors.include? SugarCRM::Base
|
34
|
+
raise InvalidModule, "#{@owner} is not registered, or is not a descendant of SugarCRM::Base" unless valid
|
35
|
+
end
|
36
|
+
|
37
|
+
# Attempts to determine the class of the target in the association
|
38
|
+
def resolve_target
|
39
|
+
# Use the link_field name first
|
40
|
+
klass = @link_field.singularize.camelize
|
41
|
+
return "SugarCRM::#{klass}".constantize if SugarCRM.const_defined? klass
|
42
|
+
|
43
|
+
# Use the link_field attribute "module"
|
44
|
+
if @attributes["module"].length > 0
|
45
|
+
module_name = SugarCRM::Module.find(@attributes["module"])
|
46
|
+
return "SugarCRM::#{module_name.klass}".constantize if SugarCRM.const_defined? module_name.klass
|
47
|
+
end
|
48
|
+
# Use the link_field attribute "relationship"
|
49
|
+
if @attributes["relationship"].length > 0
|
50
|
+
klass = humanized_link_name(@attributes["relationship"]).singularize.camelize
|
51
|
+
return "SugarCRM::#{klass}".constantize if SugarCRM.const_defined? klass
|
52
|
+
end
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
# Generates the association proxy method for related module
|
57
|
+
def define_method(link_field, pretty_name=nil)
|
58
|
+
pretty_name ||= link_field
|
59
|
+
@owner.class.module_eval %Q?
|
60
|
+
def #{pretty_name}
|
61
|
+
query_association :#{link_field}
|
62
|
+
end
|
63
|
+
?
|
64
|
+
pretty_name
|
65
|
+
end
|
66
|
+
|
67
|
+
# Defines methods for accessing the association target on the owner class.
|
68
|
+
# If the link_field name includes the owner class name, it is stripped before
|
69
|
+
# creating the method. If this occurs, we also create an alias to the stripped
|
70
|
+
# method using the full link_field name.
|
71
|
+
def define_methods
|
72
|
+
methods = []
|
73
|
+
pretty_name = humanized_link_name(@link_field)
|
74
|
+
methods << define_method(pretty_name)
|
75
|
+
if pretty_name != @link_field
|
76
|
+
@owner.class.module_eval %Q?
|
77
|
+
alias :#{@link_field} #{pretty_name}
|
78
|
+
?
|
79
|
+
methods << @link_field
|
80
|
+
end
|
81
|
+
methods
|
82
|
+
end
|
83
|
+
|
84
|
+
# Return the name of the relationship excluding the owner part of the name.
|
85
|
+
# e.g. if a custom relationship is defined in Studio between Tasks and Documents,
|
86
|
+
# the link_field will be `tasks_documents` but a human would call the relationship `documents`
|
87
|
+
def humanized_link_name(link_field)
|
88
|
+
# Split the relationship name into parts
|
89
|
+
# "contact_accounts" => ["contact","accounts"]
|
90
|
+
m = link_field.split(/_/)
|
91
|
+
# Determine the parts we don't want
|
92
|
+
# SugarCRM::Contact => ["contacts", "contact"]
|
93
|
+
o = @owner.class._module.table_name
|
94
|
+
# Use array subtraction to remove parts representing the owner side of the relationship
|
95
|
+
# ["contact", "accounts"] - ["contacts", "contact"] => ["accounts"]
|
96
|
+
t = m - [o, o.singularize]
|
97
|
+
# Reassemble whatever's left
|
98
|
+
# "accounts"
|
99
|
+
t.join('_')
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module SugarCRM; module AssociationCache
|
2
|
+
|
3
|
+
attr :association_cache, false
|
4
|
+
|
5
|
+
# Returns true if an association is cached
|
6
|
+
def association_cached?(association)
|
7
|
+
@association_cache.symbolize_keys.include? association.to_sym
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
# Returns true if an association collection has changed
|
13
|
+
def associations_changed?
|
14
|
+
@association_cache.values.each do |collection|
|
15
|
+
return true if collection.changed?
|
16
|
+
end
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
# Updates an association cache entry if it's been initialized
|
21
|
+
def update_association_cache_for(association, target)
|
22
|
+
# only add to the cache if the relationship has been queried
|
23
|
+
@association_cache[association] << target if association_cached? association
|
24
|
+
end
|
25
|
+
|
26
|
+
# Resets the association cache
|
27
|
+
def clear_association_cache
|
28
|
+
@association_cache = {}.with_indifferent_access
|
29
|
+
end
|
30
|
+
end; end
|
@@ -12,7 +12,7 @@ module SugarCRM
|
|
12
12
|
load if preload
|
13
13
|
self
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
def changed?
|
17
17
|
return false unless loaded?
|
18
18
|
return true if added.length > 0
|
@@ -95,8 +95,7 @@ module SugarCRM
|
|
95
95
|
removed.each do |record|
|
96
96
|
disassociate!(record)
|
97
97
|
end
|
98
|
-
|
99
|
-
@original.freeze
|
98
|
+
reload
|
100
99
|
true
|
101
100
|
end
|
102
101
|
|
@@ -118,12 +117,12 @@ module SugarCRM
|
|
118
117
|
# user would be the owner, and EmailAddress.new() is the target
|
119
118
|
def associate!(target, opts={})
|
120
119
|
#target.save! if target.new?
|
121
|
-
@owner.associate!(target,
|
120
|
+
@owner.associate!(target, opts)
|
122
121
|
end
|
123
122
|
|
124
123
|
# Removes a relationship between the current object and the target
|
125
124
|
def disassociate!(target)
|
126
|
-
associate!(target,{:delete => 1})
|
125
|
+
@owner.associate!(target,{:delete => 1})
|
127
126
|
end
|
128
127
|
|
129
128
|
alias :relate! :associate!
|
@@ -1,74 +1,53 @@
|
|
1
1
|
module SugarCRM; module AssociationMethods
|
2
|
-
|
2
|
+
|
3
3
|
module ClassMethods
|
4
|
-
# Returns an array of the module link fields
|
5
|
-
def associations_from_module_link_fields
|
6
|
-
self._module.link_fields.keys
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
attr :association_cache, false
|
11
|
-
|
12
|
-
def association_cached?(association)
|
13
|
-
@association_cache.keys.include? association.to_sym
|
14
4
|
end
|
15
|
-
|
16
|
-
|
5
|
+
|
6
|
+
# Saves all modified associations.
|
7
|
+
def save_modified_associations!
|
17
8
|
@association_cache.values.each do |collection|
|
18
|
-
|
9
|
+
if collection.changed?
|
10
|
+
collection.save!
|
11
|
+
end
|
19
12
|
end
|
20
|
-
|
13
|
+
true
|
21
14
|
end
|
22
15
|
|
23
|
-
#
|
24
|
-
|
25
|
-
|
16
|
+
# Returns the module link fields hash
|
17
|
+
def link_fields
|
18
|
+
self.class._module.link_fields
|
19
|
+
end
|
20
|
+
|
21
|
+
# Creates a relationship between the current object and the target object
|
22
|
+
# The current and target records will have a relationship set
|
23
|
+
# i.e. account.associate!(contact) would link account and contact
|
26
24
|
# In contrast to using account.contacts << contact, this method doesn't load the relationships
|
27
25
|
# before setting the new relationship.
|
28
26
|
# This method is useful when certain modules have many links to other modules: not loading the
|
29
27
|
# relationships allows one ot avoid a Timeout::Error
|
30
|
-
def associate!(target,
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
)
|
42
|
-
raise AssociationFailed,
|
43
|
-
"Couldn't associate #{self.class._module.name}: #{self.id} -> #{target.class._module.table_name}:#{target.id}!" if response["failed"] > 0
|
44
|
-
true
|
45
|
-
end
|
46
|
-
|
47
|
-
protected
|
48
|
-
|
49
|
-
def save_modified_associations
|
50
|
-
@association_cache.values.each do |collection|
|
51
|
-
if collection.changed?
|
52
|
-
return false unless collection.save
|
28
|
+
def associate!(target,opts={})
|
29
|
+
targets = Array.wrap(target)
|
30
|
+
targets.each do |t|
|
31
|
+
association = @associations.find!(t)
|
32
|
+
response = SugarCRM.connection.set_relationship(
|
33
|
+
self.class._module.name, self.id,
|
34
|
+
association.link_field, [t.id], opts
|
35
|
+
)
|
36
|
+
if response["failed"] > 0
|
37
|
+
raise AssociationFailed,
|
38
|
+
"Couldn't associate #{self.class._module.name}: #{self.id} -> #{t}: #{t.id}!"
|
53
39
|
end
|
40
|
+
update_association_cache_for(association.link_field, t)
|
54
41
|
end
|
55
42
|
true
|
56
43
|
end
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
end
|
61
|
-
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
62
47
|
# Generates the association proxy methods for related modules
|
63
48
|
def define_association_methods
|
49
|
+
@associations = Associations.register(self)
|
64
50
|
return if association_methods_generated?
|
65
|
-
@associations.each do |k|
|
66
|
-
self.class.module_eval %Q?
|
67
|
-
def #{k}
|
68
|
-
query_association :#{k}
|
69
|
-
end
|
70
|
-
?
|
71
|
-
end
|
72
51
|
self.class.association_methods_generated = true
|
73
52
|
end
|
74
53
|
|
@@ -94,15 +73,5 @@ module SugarCRM; module AssociationMethods
|
|
94
73
|
@association_cache[association] = collection
|
95
74
|
collection
|
96
75
|
end
|
97
|
-
|
98
|
-
# return the link field involving a relationship with a custom module
|
99
|
-
def get_link_field(other)
|
100
|
-
this_table_name = self.class._module.custom_module? ? self.class._module.name : self.class._module.table_name
|
101
|
-
that_table_name = other.class._module.custom_module? ? other.class._module.name : other.class._module.table_name
|
102
|
-
# the link field will contain the name of both modules
|
103
|
-
link_field = self.associations.detect{|a| a == [this_table_name, that_table_name].join('_') || a == [that_table_name, this_table_name].join('_')}
|
104
|
-
raise "Unable to determine link field between #{self.class._module.name}: #{self.id} and #{other.class._module.table_name}:#{other.id}" unless link_field
|
105
|
-
link_field
|
106
|
-
end
|
107
76
|
|
108
77
|
end; end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module SugarCRM
|
2
|
+
# Holds all the associations for a given class
|
3
|
+
class Associations
|
4
|
+
# Returns an array of Association objects
|
5
|
+
class << self
|
6
|
+
def register(owner)
|
7
|
+
associations = Associations.new
|
8
|
+
owner.link_fields.each_key do |link_field|
|
9
|
+
associations << Association.new(owner,link_field)
|
10
|
+
end
|
11
|
+
associations
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr :associations
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@associations = Set.new
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
# Looks up an association by object, link_field, or method.
|
23
|
+
# Raises an exception if not found
|
24
|
+
def find!(target)
|
25
|
+
@associations.each do |a|
|
26
|
+
return a if a.include? target
|
27
|
+
end
|
28
|
+
raise InvalidAssociation, "Could not lookup association for: #{target}"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Looks up an association by object, link_field, or method.
|
32
|
+
# Returns false if not found
|
33
|
+
def find(association)
|
34
|
+
begin
|
35
|
+
find!
|
36
|
+
rescue InvalidAssociation
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
methods = []
|
47
|
+
@associations.each do |a|
|
48
|
+
a.methods.each do |m|
|
49
|
+
methods << m
|
50
|
+
end
|
51
|
+
end
|
52
|
+
"[#{methods.join(', ')}]"
|
53
|
+
end
|
54
|
+
|
55
|
+
# delegate undefined methods to the @collection array
|
56
|
+
# E.g. contact.cases should behave like an array and allow `length`, `size`, `each`, etc.
|
57
|
+
def method_missing(method_name, *args, &block)
|
58
|
+
@associations.send(method_name.to_sym, *args, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -2,7 +2,7 @@ module SugarCRM; module AttributeMethods
|
|
2
2
|
|
3
3
|
module ClassMethods
|
4
4
|
# Returns a hash of the module fields from the module
|
5
|
-
def
|
5
|
+
def attributes_from_module
|
6
6
|
fields = {}.with_indifferent_access
|
7
7
|
self._module.fields.keys.sort.each do |k|
|
8
8
|
fields[k] = nil
|
@@ -74,7 +74,7 @@ module SugarCRM; module AttributeMethods
|
|
74
74
|
# royally screws up our typecasting code, so we handle it here.
|
75
75
|
def merge_attributes(attrs={})
|
76
76
|
# copy attributes from the parent module fields array
|
77
|
-
@attributes = self.class.
|
77
|
+
@attributes = self.class.attributes_from_module
|
78
78
|
# populate the attributes with values from the attrs provided to init.
|
79
79
|
@attributes.keys.each do |name|
|
80
80
|
write_attribute name, attrs[name] if attrs[name]
|
@@ -125,7 +125,7 @@ module SugarCRM; module AttributeMethods
|
|
125
125
|
|
126
126
|
# Wrapper for invoking save on modified_attributes
|
127
127
|
# sets the id if it's a new record
|
128
|
-
def save_modified_attributes
|
128
|
+
def save_modified_attributes!
|
129
129
|
# Complain if we aren't valid
|
130
130
|
raise InvalidRecord, errors.to_a.join(", ") if !valid?
|
131
131
|
# Send the save request
|
data/lib/sugarcrm/base.rb
CHANGED
@@ -5,7 +5,7 @@ module SugarCRM; class Base
|
|
5
5
|
|
6
6
|
# This holds our connection
|
7
7
|
cattr_accessor :connection, :instance_writer => false
|
8
|
-
|
8
|
+
|
9
9
|
# Tracks if we have extended our class with attribute methods yet.
|
10
10
|
class_attribute :attribute_methods_generated
|
11
11
|
self.attribute_methods_generated = false
|
@@ -230,11 +230,7 @@ module SugarCRM; class Base
|
|
230
230
|
record = find(:first, options)
|
231
231
|
|
232
232
|
if record.nil?
|
233
|
-
record = self.new
|
234
|
-
r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty?
|
235
|
-
r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty?
|
236
|
-
end
|
237
|
-
#{'yield(record) if block_given?'}
|
233
|
+
record = self.new(unprotected_attributes_for_create)
|
238
234
|
#{'record.save' if instantiator == :create}
|
239
235
|
record
|
240
236
|
else
|
@@ -250,7 +246,7 @@ module SugarCRM; class Base
|
|
250
246
|
end
|
251
247
|
|
252
248
|
def all_attributes_exists?(attribute_names)
|
253
|
-
attribute_names.all? { |name|
|
249
|
+
attribute_names.all? { |name| attributes_from_module.include?(name) }
|
254
250
|
end
|
255
251
|
|
256
252
|
def construct_attributes_from_arguments(attribute_names, arguments)
|
@@ -269,11 +265,10 @@ module SugarCRM; class Base
|
|
269
265
|
end
|
270
266
|
|
271
267
|
# Creates an instance of a Module Class, i.e. Account, User, Contact, etc.
|
272
|
-
def initialize(attributes={})
|
268
|
+
def initialize(attributes={}, &block)
|
273
269
|
@modified_attributes = {}
|
274
270
|
merge_attributes(attributes.with_indifferent_access)
|
275
271
|
clear_association_cache
|
276
|
-
@associations = self.class.associations_from_module_link_fields
|
277
272
|
define_attribute_methods
|
278
273
|
define_association_methods
|
279
274
|
typecast_attributes
|
@@ -308,8 +303,8 @@ module SugarCRM; class Base
|
|
308
303
|
# Saves the current object, and any modified associations.
|
309
304
|
# Raises an exceptions if save fails for any reason.
|
310
305
|
def save!
|
311
|
-
save_modified_attributes
|
312
|
-
save_modified_associations
|
306
|
+
save_modified_attributes!
|
307
|
+
save_modified_associations!
|
313
308
|
true
|
314
309
|
end
|
315
310
|
|
@@ -371,6 +366,7 @@ module SugarCRM; class Base
|
|
371
366
|
include AttributeSerializers
|
372
367
|
include AssociationMethods
|
373
368
|
extend AssociationMethods::ClassMethods
|
369
|
+
include AssociationCache
|
374
370
|
end
|
375
371
|
|
376
372
|
end; end
|
@@ -1,23 +1,23 @@
|
|
1
1
|
module SugarCRM; class Connection
|
2
|
-
# Retrieve a list of SugarBeans by ID. This method will not
|
3
|
-
# work with the report module.
|
4
|
-
def get_entries(module_name, ids, opts={})
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
2
|
+
# Retrieve a list of SugarBeans by ID. This method will not
|
3
|
+
# work with the report module.
|
4
|
+
def get_entries(module_name, ids, opts={})
|
5
|
+
login! unless logged_in?
|
6
|
+
options = {
|
7
|
+
:fields => [],
|
8
|
+
:link_fields => [],
|
9
|
+
}.merge! opts
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
11
|
+
json = <<-EOF
|
12
|
+
{
|
13
|
+
\"session\": \"#{@session}\"\,
|
14
|
+
\"module_name\": \"#{module_name}\"\,
|
15
|
+
\"ids\": #{ids.to_json}\,
|
16
|
+
\"select_fields\": #{resolve_fields(module_name, options[:fields])}\,
|
17
|
+
\"link_name_to_fields_array\": #{options[:link_fields].to_json}\,
|
18
|
+
}
|
19
|
+
EOF
|
20
|
+
json.gsub!(/^\s{6}/,'')
|
21
|
+
SugarCRM::Response.handle(send!(:get_entries, json))
|
22
|
+
end
|
23
23
|
end; end
|
@@ -1,20 +1,20 @@
|
|
1
1
|
module SugarCRM; class Connection
|
2
|
-
# Retrieves the specified number of records in a module.
|
3
|
-
def get_entries_count(module_name, query, opts={})
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
# Retrieves the specified number of records in a module.
|
3
|
+
def get_entries_count(module_name, query, opts={})
|
4
|
+
login! unless logged_in?
|
5
|
+
options = {
|
6
|
+
:deleted => 0
|
7
|
+
}.merge! opts
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
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
|
+
send!(:get_entries_count, json)
|
19
|
+
end
|
20
20
|
end; end
|
@@ -1,31 +1,31 @@
|
|
1
1
|
module SugarCRM; class Connection
|
2
|
-
# Retrieve a list of SugarBeans. This is the primary method for getting
|
3
|
-
# a list of SugarBeans using the REST API.
|
4
|
-
def get_entry_list(module_name, query, opts={})
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
2
|
+
# Retrieve a list of SugarBeans. This is the primary method for getting
|
3
|
+
# a list of SugarBeans using the REST API.
|
4
|
+
def get_entry_list(module_name, query, opts={})
|
5
|
+
login! unless logged_in?
|
6
|
+
options = {
|
7
|
+
:order_by => '',
|
8
|
+
:offset => '',
|
9
|
+
:fields => [],
|
10
|
+
:link_fields => [],
|
11
|
+
:limit => '',
|
12
|
+
:deleted => 0
|
13
|
+
}.merge! opts
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
15
|
+
json = <<-EOF
|
16
|
+
{
|
17
|
+
\"session\": \"#{@session}\"\,
|
18
|
+
\"module_name\": \"#{module_name}\"\,
|
19
|
+
\"query\": \"#{query}\"\,
|
20
|
+
\"order_by\": \"#{options[:order_by]}\"\,
|
21
|
+
\"offset\": \"#{options[:offset]}\"\,
|
22
|
+
\"select_fields\": #{resolve_fields(module_name, options[:fields])}\,
|
23
|
+
\"link_name_to_fields_array\": #{options[:link_fields].to_json}\,
|
24
|
+
\"max_results\": \"#{options[:limit]}\"\,
|
25
|
+
\"deleted\": #{options[:deleted]}
|
26
|
+
}
|
27
|
+
EOF
|
28
|
+
json.gsub!(/^\s{6}/,'')
|
29
|
+
SugarCRM::Response.handle(send!(:get_entry_list, json))
|
30
|
+
end
|
31
31
|
end; end
|
@@ -1,19 +1,15 @@
|
|
1
1
|
module SugarCRM; class Connection
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
alias :get_fields :get_module_fields
|
18
|
-
|
2
|
+
# Retrieves the vardef information of the specified bean.
|
3
|
+
def get_module_fields(module_name)
|
4
|
+
login! unless logged_in?
|
5
|
+
json = <<-EOF
|
6
|
+
{
|
7
|
+
\"session\": \"#{@session}\"\,
|
8
|
+
\"module_name": \"#{module_name}"
|
9
|
+
}
|
10
|
+
EOF
|
11
|
+
json.gsub!(/^\s{6}/,'')
|
12
|
+
SugarCRM::Response.handle(send!(:get_module_fields, json))
|
13
|
+
end
|
14
|
+
alias :get_fields :get_module_fields
|
19
15
|
end; end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module SugarCRM; class Connection
|
2
|
-
# Retrieves an attachment from a note.
|
3
|
-
def get_note_attachment(id)
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
2
|
+
# Retrieves an attachment from a note.
|
3
|
+
def get_note_attachment(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
|
+
send!(:get_note_attachment, json)
|
13
|
+
end
|
14
14
|
end; end
|
@@ -1,34 +1,30 @@
|
|
1
1
|
module SugarCRM; class Connection
|
2
|
-
# Retrieves a collection of beans that are related
|
3
|
-
# to the specified bean and, optionally, returns
|
4
|
-
# relationship data
|
5
|
-
def get_relationships(module_name, id, related_to, opts={})
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
related_module = related_to.classify
|
15
|
-
|
16
|
-
json = <<-EOF
|
17
|
-
{
|
18
|
-
\"session\": \"#{@session}\"\,
|
19
|
-
\"module_name\": \"#{module_name}\"\,
|
20
|
-
\"module_id\": \"#{id}\"\,
|
21
|
-
\"link_field_name\": \"#{related_to.downcase}\"\,
|
22
|
-
\"related_module_query\": \"#{options[:query]}\"\,
|
23
|
-
\"related_fields\": #{resolve_fields(related_module, options[:fields])}\,
|
24
|
-
\"related_module_link_name_to_fields_array\": #{options[:link_fields].to_json}\,
|
25
|
-
\"deleted\": #{options[:deleted]}
|
26
|
-
}
|
27
|
-
EOF
|
28
|
-
json.gsub!(/^\s{6}/,'')
|
29
|
-
SugarCRM::Response.new(send!(:get_relationships, json)).to_obj
|
30
|
-
end
|
31
|
-
|
32
|
-
alias :get_relationship :get_relationships
|
2
|
+
# Retrieves a collection of beans that are related
|
3
|
+
# to the specified bean and, optionally, returns
|
4
|
+
# relationship data
|
5
|
+
def get_relationships(module_name, id, related_to, opts={})
|
6
|
+
login! unless logged_in?
|
7
|
+
options = {
|
8
|
+
:query => '',
|
9
|
+
:fields => [],
|
10
|
+
:link_fields => [],
|
11
|
+
:deleted => ''
|
12
|
+
}.merge! opts
|
33
13
|
|
14
|
+
json = <<-EOF
|
15
|
+
{
|
16
|
+
\"session\": \"#{@session}\"\,
|
17
|
+
\"module_name\": \"#{module_name}\"\,
|
18
|
+
\"module_id\": \"#{id}\"\,
|
19
|
+
\"link_field_name\": \"#{related_to.downcase}\"\,
|
20
|
+
\"related_module_query\": \"#{options[:query]}\"\,
|
21
|
+
\"related_fields\": #{resolve_related_fields(module_name, related_to)}\,
|
22
|
+
\"related_module_link_name_to_fields_array\": #{options[:link_fields].to_json}\,
|
23
|
+
\"deleted\": #{options[:deleted]}
|
24
|
+
}
|
25
|
+
EOF
|
26
|
+
json.gsub!(/^\s{6}/,'')
|
27
|
+
SugarCRM::Response.new(send!(:get_relationships, json), {:always_return_array => true}).to_obj
|
28
|
+
end
|
29
|
+
alias :get_relationship :get_relationships
|
34
30
|
end; end
|
@@ -1,17 +1,17 @@
|
|
1
1
|
module SugarCRM; class Connection
|
2
|
-
# Retrieves a list of report entries based on specified report IDs.
|
3
|
-
def get_report_entries(ids, opts={})
|
4
|
-
|
5
|
-
|
2
|
+
# Retrieves a list of report entries based on specified report IDs.
|
3
|
+
def get_report_entries(ids, opts={})
|
4
|
+
login! unless logged_in?
|
5
|
+
options = {:select_fields => ''}.merge! opts
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
7
|
+
json = <<-EOF
|
8
|
+
{
|
9
|
+
\"session\": \"#{@session}\"\,
|
10
|
+
\"ids\": #{ids.to_json}\,
|
11
|
+
\"select_fields\": \"#{options[:select_fields].to_json}\"
|
12
|
+
}
|
13
|
+
EOF
|
14
|
+
json.gsub!(/^\s{6}/,'')
|
15
|
+
send!(:get_report_entries, json)
|
16
|
+
end
|
17
17
|
end; end
|
@@ -1,15 +1,40 @@
|
|
1
1
|
module SugarCRM; class Connection
|
2
|
+
# Attempts to return a list of fields for the target of the association.
|
3
|
+
# i.e. if we are associating Contact -> Account, using the "contacts" link
|
4
|
+
# field name - this will lookup the contacts association and try to determine
|
5
|
+
# the target object type (Contact). It will then pull the fields for that object
|
6
|
+
# and shove them in the related_fields portion of the get_relationship request.
|
7
|
+
def resolve_related_fields(module_name, link_field)
|
8
|
+
a = Association.new(class_for(module_name), link_field)
|
9
|
+
if a.target
|
10
|
+
fields = a.target.new.attributes.keys
|
11
|
+
else
|
12
|
+
fields = ["id"]
|
13
|
+
end
|
14
|
+
fields.to_json
|
15
|
+
end
|
16
|
+
|
2
17
|
def resolve_fields(module_name, fields)
|
3
18
|
# FIXME: This is to work around a bug in SugarCRM 6.0
|
4
19
|
# where no fields are returned if no fields are specified
|
5
20
|
if fields.length == 0
|
6
|
-
mod = Module.find(module_name)
|
21
|
+
mod = Module.find(module_name.classify)
|
7
22
|
if mod
|
8
23
|
fields = mod.fields.keys
|
9
|
-
else
|
10
|
-
fields = "id"
|
24
|
+
else
|
25
|
+
fields = ["id"]
|
11
26
|
end
|
12
27
|
end
|
13
28
|
return fields.to_json
|
14
29
|
end
|
30
|
+
|
31
|
+
# Returns an instance of class for the provided module name
|
32
|
+
def class_for(module_name)
|
33
|
+
begin
|
34
|
+
klass = "SugarCRM::#{module_name.classify}".constantize.new
|
35
|
+
rescue NameError
|
36
|
+
raise InvalidModule, "Module: #{module_name} is not registered"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
15
40
|
end; end
|
@@ -26,7 +26,8 @@ module SugarCRM; class Response
|
|
26
26
|
|
27
27
|
attr :response, false
|
28
28
|
|
29
|
-
def initialize(json)
|
29
|
+
def initialize(json,opts={})
|
30
|
+
@options = { :always_return_array => false }.merge! opts
|
30
31
|
@response = json
|
31
32
|
@response = json.with_indifferent_access if json.is_a? Hash
|
32
33
|
end
|
@@ -52,7 +53,7 @@ module SugarCRM; class Response
|
|
52
53
|
end
|
53
54
|
end
|
54
55
|
# If we only have one result, just return the object
|
55
|
-
if objects.length == 1
|
56
|
+
if objects.length == 1 && !@options[:always_return_array]
|
56
57
|
return objects[0]
|
57
58
|
else
|
58
59
|
return objects
|
data/lib/sugarcrm/exceptions.rb
CHANGED
data/lib/sugarcrm/module.rb
CHANGED
@@ -7,6 +7,7 @@ module SugarCRM
|
|
7
7
|
attr :klass, true
|
8
8
|
attr :fields, true
|
9
9
|
attr :link_fields, true
|
10
|
+
alias :bean :klass
|
10
11
|
|
11
12
|
# Dynamically register objects based on Module name
|
12
13
|
# I.e. a SugarCRM Module named Users will generate
|
@@ -15,27 +16,28 @@ module SugarCRM
|
|
15
16
|
@name = name
|
16
17
|
@klass = name.classify
|
17
18
|
@table_name = name.tableize
|
18
|
-
|
19
|
-
# set table name for custom attibutes
|
20
|
-
# custom attributes are contained in a table named after the module, with a '_cstm' suffix
|
21
|
-
# the module's table name must be tableized for the modules that ship with SugarCRM
|
22
|
-
# for custom modules (created in the Studio), table name don't need to be tableized: the name passed to the constructor is already tableized
|
23
|
-
unless self.custom_module?
|
24
|
-
@custom_table_name = @table_name + "_cstm"
|
25
|
-
else
|
26
|
-
@custom_table_name = name + "_cstm"
|
27
|
-
end
|
28
|
-
|
19
|
+
@custom_table_name = resolve_custom_table_name
|
29
20
|
@fields = {}
|
30
21
|
@link_fields = {}
|
31
22
|
@fields_registered = false
|
32
23
|
self
|
33
24
|
end
|
34
25
|
|
35
|
-
#
|
36
|
-
# ship in the
|
26
|
+
# Return true if this module was created in the SugarCRM Studio (i.e. it is not part of the modules that
|
27
|
+
# ship in the default SugarCRM configuration)
|
37
28
|
def custom_module?
|
38
|
-
|
29
|
+
# custom module names are all lower_case, whereas SugarCRM modules are CamelCase
|
30
|
+
@name.downcase == @name
|
31
|
+
end
|
32
|
+
|
33
|
+
# Set table name for custom attibutes
|
34
|
+
# Custom attributes are contained in a table named after the module, with a '_cstm' suffix.
|
35
|
+
# The module's table name must be tableized for the modules that ship with SugarCRM.
|
36
|
+
# For custom modules (created in the Studio), table name don't need to be tableized since
|
37
|
+
# the name passed to the constructor is already tableized
|
38
|
+
def resolve_custom_table_name
|
39
|
+
@custom_table_name = @table_name + "_cstm"
|
40
|
+
@custom_table_name = @name + "_cstm" if custom_module?
|
39
41
|
end
|
40
42
|
|
41
43
|
# Returns the fields associated with the module
|
@@ -6,10 +6,10 @@ class TestGetRelationships < Test::Unit::TestCase
|
|
6
6
|
SugarCRM::Connection.new(URL, USER, PASS, {:register_modules => false, :debug => false})
|
7
7
|
end
|
8
8
|
should "return a list of email_addresses when sent #get_relationship and a user_id" do
|
9
|
-
|
9
|
+
email_addresses = SugarCRM.connection.get_relationships(
|
10
10
|
"Users",1,"email_addresses"
|
11
11
|
)
|
12
|
-
assert_instance_of SugarCRM::EmailAddress,
|
12
|
+
assert_instance_of SugarCRM::EmailAddress, email_addresses.first
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
#TODO: Figure out how to test this.
|
data/test/test_associations.rb
CHANGED
@@ -31,5 +31,11 @@ class TestAssociations < Test::Unit::TestCase
|
|
31
31
|
assert u.meetings.save!
|
32
32
|
assert !u.meetings.include?(m)
|
33
33
|
end
|
34
|
+
|
35
|
+
# TODO: Fix created_by_link to only return a single result
|
36
|
+
should "return a user when sent #created_by_link" do
|
37
|
+
a = SugarCRM::Account.first
|
38
|
+
assert_instance_of SugarCRM::User, a.created_by_link.first
|
39
|
+
end
|
34
40
|
end
|
35
41
|
end
|
data/test/test_module.rb
CHANGED
@@ -15,8 +15,9 @@ class TestModule < Test::Unit::TestCase
|
|
15
15
|
assert SugarCRM::User._module.required_fields.include? :user_name
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
# TODO: Figure out a way to test this.
|
19
|
+
#should "return the custom table name when #custom_table_name" do
|
20
|
+
# assert_equal "accounts_cstm", SugarCRM::Account._module.custom_table_name
|
21
|
+
#end
|
21
22
|
end
|
22
23
|
end
|
data/test/test_sugarcrm.rb
CHANGED
@@ -39,7 +39,7 @@ class TestSugarCRM < Test::Unit::TestCase
|
|
39
39
|
end
|
40
40
|
|
41
41
|
should "respond to self.attributes_from_modules_fields" do
|
42
|
-
assert_instance_of ActiveSupport::HashWithIndifferentAccess, SugarCRM::User.
|
42
|
+
assert_instance_of ActiveSupport::HashWithIndifferentAccess, SugarCRM::User.attributes_from_module
|
43
43
|
end
|
44
44
|
|
45
45
|
should "return an instance of itself when #new" do
|
@@ -134,6 +134,21 @@ class TestSugarCRM < Test::Unit::TestCase
|
|
134
134
|
assert_equal "sarah@example.com", u.email_addresses.first.email_address
|
135
135
|
end
|
136
136
|
|
137
|
+
should "create or retrieve a record when #find_or_create_by_name" do
|
138
|
+
a = SugarCRM::Account.find_or_create_by_name("Really Important Co. Name")
|
139
|
+
assert_instance_of SugarCRM::Account, a
|
140
|
+
assert !a.new?
|
141
|
+
b = SugarCRM::Account.find_or_create_by_name("Really Important Co. Name")
|
142
|
+
assert a == b
|
143
|
+
assert a.delete
|
144
|
+
end
|
145
|
+
|
146
|
+
# should "support saving of records with special characters in them" do
|
147
|
+
# a = SugarCRM::Account.new
|
148
|
+
# a.name = "COHEN, WEISS & SIMON LLP"
|
149
|
+
# assert a.save!
|
150
|
+
# end
|
151
|
+
|
137
152
|
end
|
138
153
|
|
139
154
|
end
|
metadata
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sugarcrm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
4
|
+
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 0
|
8
7
|
- 9
|
9
|
-
-
|
10
|
-
version: 0.9.
|
8
|
+
- 8
|
9
|
+
version: 0.9.8
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Carl Hicks
|
@@ -15,99 +14,93 @@ autorequire:
|
|
15
14
|
bindir: bin
|
16
15
|
cert_chain: []
|
17
16
|
|
18
|
-
date: 2011-01-
|
17
|
+
date: 2011-01-18 00:00:00 -08:00
|
19
18
|
default_executable:
|
20
19
|
dependencies:
|
21
20
|
- !ruby/object:Gem::Dependency
|
22
|
-
|
23
|
-
|
21
|
+
name: activesupport
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
23
|
none: false
|
25
24
|
requirements:
|
26
25
|
- - ">="
|
27
26
|
- !ruby/object:Gem::Version
|
28
|
-
hash: 7
|
29
27
|
segments:
|
30
28
|
- 3
|
31
29
|
- 0
|
32
30
|
- 0
|
33
31
|
version: 3.0.0
|
34
|
-
|
32
|
+
type: :runtime
|
35
33
|
prerelease: false
|
36
|
-
|
34
|
+
version_requirements: *id001
|
37
35
|
- !ruby/object:Gem::Dependency
|
38
|
-
|
39
|
-
|
36
|
+
name: i18n
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
38
|
none: false
|
41
39
|
requirements:
|
42
40
|
- - ">="
|
43
41
|
- !ruby/object:Gem::Version
|
44
|
-
hash: 3
|
45
42
|
segments:
|
46
43
|
- 0
|
47
44
|
version: "0"
|
48
|
-
|
45
|
+
type: :runtime
|
49
46
|
prerelease: false
|
50
|
-
|
47
|
+
version_requirements: *id002
|
51
48
|
- !ruby/object:Gem::Dependency
|
52
|
-
|
53
|
-
|
49
|
+
name: shoulda
|
50
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
51
|
none: false
|
55
52
|
requirements:
|
56
53
|
- - ">="
|
57
54
|
- !ruby/object:Gem::Version
|
58
|
-
hash: 3
|
59
55
|
segments:
|
60
56
|
- 0
|
61
57
|
version: "0"
|
62
|
-
|
58
|
+
type: :development
|
63
59
|
prerelease: false
|
64
|
-
|
60
|
+
version_requirements: *id003
|
65
61
|
- !ruby/object:Gem::Dependency
|
66
|
-
|
67
|
-
|
62
|
+
name: bundler
|
63
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
68
64
|
none: false
|
69
65
|
requirements:
|
70
66
|
- - ~>
|
71
67
|
- !ruby/object:Gem::Version
|
72
|
-
hash: 23
|
73
68
|
segments:
|
74
69
|
- 1
|
75
70
|
- 0
|
76
71
|
- 0
|
77
72
|
version: 1.0.0
|
78
|
-
|
73
|
+
type: :development
|
79
74
|
prerelease: false
|
80
|
-
|
75
|
+
version_requirements: *id004
|
81
76
|
- !ruby/object:Gem::Dependency
|
82
|
-
|
83
|
-
|
77
|
+
name: jeweler
|
78
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
84
79
|
none: false
|
85
80
|
requirements:
|
86
81
|
- - ~>
|
87
82
|
- !ruby/object:Gem::Version
|
88
|
-
hash: 7
|
89
83
|
segments:
|
90
84
|
- 1
|
91
85
|
- 5
|
92
86
|
- 2
|
93
87
|
version: 1.5.2
|
94
|
-
|
88
|
+
type: :development
|
95
89
|
prerelease: false
|
96
|
-
|
90
|
+
version_requirements: *id005
|
97
91
|
- !ruby/object:Gem::Dependency
|
98
|
-
|
99
|
-
|
92
|
+
name: rcov
|
93
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
100
94
|
none: false
|
101
95
|
requirements:
|
102
96
|
- - ">="
|
103
97
|
- !ruby/object:Gem::Version
|
104
|
-
hash: 3
|
105
98
|
segments:
|
106
99
|
- 0
|
107
100
|
version: "0"
|
108
|
-
|
101
|
+
type: :development
|
109
102
|
prerelease: false
|
110
|
-
|
103
|
+
version_requirements: *id006
|
111
104
|
description: A less clunky way to interact with SugarCRM via REST. Instead of SugarCRM.connection.get_entry("Users", "1") you could use SugarCRM::User.find(1). There is support for collections a la SugarCRM::User.find(1).email_addresses, or SugarCRM::Contact.first.meetings << new_meeting. ActiveRecord style finders are in place, with limited support for conditions and joins.
|
112
105
|
email: carl.hicks@gmail.com
|
113
106
|
executables: []
|
@@ -126,8 +119,11 @@ files:
|
|
126
119
|
- VERSION
|
127
120
|
- lib/sugarcrm.rb
|
128
121
|
- lib/sugarcrm/associations.rb
|
122
|
+
- lib/sugarcrm/associations/association.rb
|
123
|
+
- lib/sugarcrm/associations/association_cache.rb
|
129
124
|
- lib/sugarcrm/associations/association_collection.rb
|
130
125
|
- lib/sugarcrm/associations/association_methods.rb
|
126
|
+
- lib/sugarcrm/associations/associations.rb
|
131
127
|
- lib/sugarcrm/attributes.rb
|
132
128
|
- lib/sugarcrm/attributes/attribute_methods.rb
|
133
129
|
- lib/sugarcrm/attributes/attribute_serializers.rb
|
@@ -180,6 +176,7 @@ files:
|
|
180
176
|
- test/connection/test_logout.rb
|
181
177
|
- test/connection/test_set_relationship.rb
|
182
178
|
- test/helper.rb
|
179
|
+
- test/test_association.rb
|
183
180
|
- test/test_association_collection.rb
|
184
181
|
- test/test_associations.rb
|
185
182
|
- test/test_connection.rb
|
@@ -200,7 +197,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
200
197
|
requirements:
|
201
198
|
- - ">="
|
202
199
|
- !ruby/object:Gem::Version
|
203
|
-
hash:
|
200
|
+
hash: -1258950620288984122
|
204
201
|
segments:
|
205
202
|
- 0
|
206
203
|
version: "0"
|
@@ -209,14 +206,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
209
206
|
requirements:
|
210
207
|
- - ">="
|
211
208
|
- !ruby/object:Gem::Version
|
212
|
-
hash: 3
|
213
209
|
segments:
|
214
210
|
- 0
|
215
211
|
version: "0"
|
216
212
|
requirements: []
|
217
213
|
|
218
214
|
rubyforge_project:
|
219
|
-
rubygems_version: 1.
|
215
|
+
rubygems_version: 1.3.7
|
220
216
|
signing_key:
|
221
217
|
specification_version: 3
|
222
218
|
summary: Ruby based REST client for SugarCRM
|
@@ -234,6 +230,7 @@ test_files:
|
|
234
230
|
- test/connection/test_logout.rb
|
235
231
|
- test/connection/test_set_relationship.rb
|
236
232
|
- test/helper.rb
|
233
|
+
- test/test_association.rb
|
237
234
|
- test/test_association_collection.rb
|
238
235
|
- test/test_associations.rb
|
239
236
|
- test/test_connection.rb
|