sugarcrm 0.9.7 → 0.9.8
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/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
|