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 CHANGED
@@ -1 +1 @@
1
- 0.9.7
1
+ 0.9.8
@@ -1,2 +1,5 @@
1
+ require 'sugarcrm/associations/association'
2
+ require 'sugarcrm/associations/associations'
3
+ require 'sugarcrm/associations/association_cache'
1
4
  require 'sugarcrm/associations/association_methods'
2
5
  require 'sugarcrm/associations/association_collection'
@@ -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
- @original = @collection.dup
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, [target.id], opts)
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
- def associations_changed?
5
+
6
+ # Saves all modified associations.
7
+ def save_modified_associations!
17
8
  @association_cache.values.each do |collection|
18
- return true if collection.changed?
9
+ if collection.changed?
10
+ collection.save!
11
+ end
19
12
  end
20
- false
13
+ true
21
14
  end
22
15
 
23
- # Creates a relationship between the current object and the target
24
- # The current instance and target records will have a relationship set
25
- # i.e. account.associate!(contact) wyould link account and contact
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, target_ids=[], opts={})
31
- if self.class._module.custom_module? || target.class._module.custom_module?
32
- link_field = get_link_field(target)
33
- else
34
- link_field = target.class._module.table_name
35
- end
36
- target_ids = [target.id] if target_ids.size < 1
37
- response = SugarCRM.connection.set_relationship(
38
- self.class._module.name, self.id,
39
- link_field, target_ids,
40
- opts
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
- def clear_association_cache
59
- @association_cache = {}
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 attributes_from_module_fields
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.attributes_from_module_fields
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 do |r|
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| attributes_from_module_fields.include?(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
- login! unless logged_in?
6
- options = {
7
- :fields => [],
8
- :link_fields => [],
9
- }.merge! opts
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
- 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
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
- login! unless logged_in?
5
- options = {
6
- :deleted => 0
7
- }.merge! opts
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
- 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
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
- login! unless logged_in?
6
- options = {
7
- :order_by => '',
8
- :offset => '',
9
- :fields => [],
10
- :link_fields => [],
11
- :limit => '',
12
- :deleted => 0
13
- }.merge! opts
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
- 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
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
- # Retrieves the vardef information on fields of the specified bean.
4
- def get_module_fields(module_name)
5
- login! unless logged_in?
6
- json = <<-"EOF"
7
- {
8
- \"session\": \"#{@session}\"\,
9
- \"module_name": \"#{module_name}"
10
- }
11
- EOF
12
- json.gsub!(/^\s{6}/,'')
13
- SugarCRM::Response.handle(send!(:get_module_fields, json))
14
- #send!(:get_module_fields, json)
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
- 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
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
- login! unless logged_in?
7
- options = {
8
- :query => '',
9
- :fields => [],
10
- :link_fields => [],
11
- :deleted => ''
12
- }.merge! opts
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
- login! unless logged_in?
5
- options = {:select_fields => ''}.merge! opts
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
- 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
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
@@ -12,7 +12,7 @@ module SugarCRM; class Connection
12
12
  \"application\": \"\"
13
13
  }
14
14
  EOF
15
- json.gsub!(/^\s{8}/,'')
15
+ json.gsub!(/^\s{6}/,'')
16
16
  response = send!(:login, json)
17
17
  end
18
18
  end; end
@@ -9,7 +9,7 @@ module SugarCRM; class Connection
9
9
  }
10
10
  }
11
11
  EOF
12
- json.gsub!(/^\s{8}/,'')
12
+ json.gsub!(/^\s{6}/,'')
13
13
  send!(:logout, json)
14
14
  end
15
15
  end; end
@@ -7,7 +7,7 @@ module SugarCRM; class Connection
7
7
  \"session\": \"#{@session}\"
8
8
  }
9
9
  EOF
10
- json.gsub!(/^\s{8}/,'')
10
+ json.gsub!(/^\s{6}/,'')
11
11
  response = send!(:seamless_login, json)
12
12
  end
13
13
  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
@@ -15,7 +15,7 @@ module SugarCRM; class Request
15
15
  @request << '&rest_data=' << @json
16
16
  if debug
17
17
  puts "#{method}: Request:"
18
- pp @request
18
+ puts @request
19
19
  puts "\n"
20
20
  end
21
21
  self
@@ -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
@@ -13,4 +13,5 @@ module SugarCRM
13
13
  class UninitializedModule < RuntimeError; end
14
14
  class InvalidAttribute < RuntimeError; end
15
15
  class InvalidAttributeType < RuntimeError; end
16
+ class InvalidAssociation < RuntimeError; end
16
17
  end
@@ -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
- # return true if this module was created in the SugarCRM Studio (i.e. it is not part of the modules that
36
- # ship in the dfault SugarCRM configuration
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
- name.downcase == name # custom module names are all lower_case, whereas SugarCRM modules are CamelCase
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
- email_address = SugarCRM.connection.get_relationships(
9
+ email_addresses = SugarCRM.connection.get_relationships(
10
10
  "Users",1,"email_addresses"
11
11
  )
12
- assert_instance_of SugarCRM::EmailAddress, email_address
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.
@@ -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
- should "return the custom table name when #custom_table_name" do
19
- assert_equal "accounts_cstm", SugarCRM::Account._module.custom_table_name
20
- end
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
@@ -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.attributes_from_module_fields
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
- hash: 53
5
- prerelease:
4
+ prerelease: false
6
5
  segments:
7
6
  - 0
8
7
  - 9
9
- - 7
10
- version: 0.9.7
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-15 00:00:00 -08:00
17
+ date: 2011-01-18 00:00:00 -08:00
19
18
  default_executable:
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
22
- type: :runtime
23
- version_requirements: &id001 !ruby/object:Gem::Requirement
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
- requirement: *id001
32
+ type: :runtime
35
33
  prerelease: false
36
- name: activesupport
34
+ version_requirements: *id001
37
35
  - !ruby/object:Gem::Dependency
38
- type: :runtime
39
- version_requirements: &id002 !ruby/object:Gem::Requirement
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
- requirement: *id002
45
+ type: :runtime
49
46
  prerelease: false
50
- name: i18n
47
+ version_requirements: *id002
51
48
  - !ruby/object:Gem::Dependency
52
- type: :development
53
- version_requirements: &id003 !ruby/object:Gem::Requirement
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
- requirement: *id003
58
+ type: :development
63
59
  prerelease: false
64
- name: shoulda
60
+ version_requirements: *id003
65
61
  - !ruby/object:Gem::Dependency
66
- type: :development
67
- version_requirements: &id004 !ruby/object:Gem::Requirement
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
- requirement: *id004
73
+ type: :development
79
74
  prerelease: false
80
- name: bundler
75
+ version_requirements: *id004
81
76
  - !ruby/object:Gem::Dependency
82
- type: :development
83
- version_requirements: &id005 !ruby/object:Gem::Requirement
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
- requirement: *id005
88
+ type: :development
95
89
  prerelease: false
96
- name: jeweler
90
+ version_requirements: *id005
97
91
  - !ruby/object:Gem::Dependency
98
- type: :development
99
- version_requirements: &id006 !ruby/object:Gem::Requirement
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
- requirement: *id006
101
+ type: :development
109
102
  prerelease: false
110
- name: rcov
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: 3
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.4.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