lib/sugarcrm/base.rb in sugarcrm-0.7.2 vs lib/sugarcrm/base.rb in sugarcrm-0.7.7

- old
+ new

@@ -31,24 +31,256 @@ }.merge(opts) @debug = options[:debug] @@connection = SugarCRM::Connection.new(url, user, pass, @debug) end - # Runs a find against the remote service - def find(id) - response = SugarCRM.connection.get_entry(self._module.name, id, {:fields => self._module.fields.keys}) + def find(*args) + options = args.extract_options! + validate_find_options(options) + + case args.first + when :first then find_initial(options) + when :all then find_every(options) + else find_from_ids(args, options) + end end + + # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the + # same arguments to this method as you can to <tt>find(:first)</tt>. + def first(*args) + find(:first, *args) + end + + # This is an alias for find(:all). You can pass in all the same arguments to this method as you can + # to find(:all) + def all(*args) + find(:all, *args) + end + + private + + def find_initial(options) + options.update(:max_results => 1) + find_every(options) + end + + def find_from_ids(ids, options) + expects_array = ids.first.kind_of?(Array) + return ids.first if expects_array && ids.first.empty? + + ids = ids.flatten.compact.uniq + + case ids.size + when 0 + raise RecordNotFound, "Couldn't find #{name} without an ID" + when 1 + result = find_one(ids.first, options) + expects_array ? [ result ] : result + else + find_some(ids, options) + end + end + + def find_one(id, options) + if result = SugarCRM.connection.get_entry(self._module.name, id, {:fields => self._module.fields.keys}) + result + else + raise RecordNotFound, "Couldn't find #{name} with ID=#{id}#{conditions}" + end + end + + def find_some(ids, options) + result = SugarCRM.connection.get_entries(self._module.name, ids, {:fields => self._module.fields.keys}) + + # Determine expected size from limit and offset, not just ids.size. + expected_size = + if options[:limit] && ids.size > options[:limit] + options[:limit] + else + ids.size + end + + # 11 ids with limit 3, offset 9 should give 2 results. + if options[:offset] && (ids.size - options[:offset] < expected_size) + expected_size = ids.size - options[:offset] + end + + if result.size == expected_size + result + else + raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions} (found #{result.size} results, but was looking for #{expected_size})" + end + end + + def find_every(options) + find_by_sql(options) + end + + def find_by_sql(options) + query = query_from_options(options) + SugarCRM.connection.get_entry_list(self._module.name, query, options) + end + + def query_from_options(options) + conditions = [] + options[:conditions].each_pair do |column, value| + conditions << "#{self._module.table_name}.#{column} = \'#{value}\'" + end + conditions.join(" AND ") + end + + # Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt> + # that are turned into <tt>find(:first, :conditions => ["user_name = ?", user_name])</tt> and + # <tt>find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])</tt> respectively. Also works for + # <tt>find(:all)</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>find(:all, :conditions => ["amount = ?", 50])</tt>. + # + # It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+ + # is actually <tt>find_all_by_amount(amount, options)</tt>. + # + # Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that + # are turned into scoped(:conditions => ["user_name = ?", user_name]) and scoped(:conditions => ["user_name = ? AND password = ?", user_name, password]) + # respectively. + # + # Each dynamic finder, scope or initializer/creator is also defined in the class after it is first invoked, so that future + # attempts to use it do not run through method_missing. + def method_missing(method_id, *arguments, &block) + if match = DynamicFinderMatch.match(method_id) + attribute_names = match.attribute_names + super unless all_attributes_exists?(attribute_names) + if match.finder? + finder = match.finder + bang = match.bang? + # def self.find_by_login_and_activated(*args) + # options = args.extract_options! + # attributes = construct_attributes_from_arguments( + # [:login,:activated], + # args + # ) + # finder_options = { :conditions => attributes } + # validate_find_options(options) + # + # if options[:conditions] + # with_scope(:find => finder_options) do + # find(:first, options) + # end + # else + # find(:first, options.merge(finder_options)) + # end + # end + self.class_eval <<-EOS, __FILE__, __LINE__ + 1 + def self.#{method_id}(*args) + options = args.extract_options! + attributes = construct_attributes_from_arguments( + [:#{attribute_names.join(',:')}], + args + ) + finder_options = { :conditions => attributes } + validate_find_options(options) + + #{'result = ' if bang}if options[:conditions] + with_scope(:find => finder_options) do + find(:#{finder}, options) + end + else + find(:#{finder}, options.merge(finder_options)) + end + #{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect {|pair| "#{pair.first} = #{pair.second}"}.join(\', \')}")' if bang} + end + EOS + send(method_id, *arguments) + elsif match.instantiator? + instantiator = match.instantiator + # def self.find_or_create_by_user_id(*args) + # guard_protected_attributes = false + # + # if args[0].is_a?(Hash) + # guard_protected_attributes = true + # attributes = args[0].with_indifferent_access + # find_attributes = attributes.slice(*[:user_id]) + # else + # find_attributes = attributes = construct_attributes_from_arguments([:user_id], args) + # end + # + # options = { :conditions => find_attributes } + # set_readonly_option!(options) + # + # record = find(:first, options) + # + # if record.nil? + # record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) } + # yield(record) if block_given? + # record.save + # record + # else + # record + # end + # end + self.class_eval <<-EOS, __FILE__, __LINE__ + 1 + def self.#{method_id}(*args) + attributes = [:#{attribute_names.join(',:')}] + protected_attributes_for_create, unprotected_attributes_for_create = {}, {} + args.each_with_index do |arg, i| + if arg.is_a?(Hash) + protected_attributes_for_create = args[i].with_indifferent_access + else + unprotected_attributes_for_create[attributes[i]] = args[i] + end + end + + find_attributes = (protected_attributes_for_create.merge(unprotected_attributes_for_create)).slice(*attributes) + + options = { :conditions => find_attributes } + + record = find(:first, options) + + if record.nil? + record = self.new do |r| + r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty? + r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty? + end + #{'yield(record) if block_given?'} + #{'record.save' if instantiator == :create} + record + else + record + end + end + EOS + send(method_id, *arguments, &block) + end + else + super + end + end + + def all_attributes_exists?(attribute_names) + attribute_names.all? { |name| attributes_from_module_fields.include?(name) } + end + + def construct_attributes_from_arguments(attribute_names, arguments) + attributes = {} + attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] } + attributes + end + + VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, + :order, :select, :readonly, :group, :having, :from, :lock ] + + def validate_find_options(options) #:nodoc: + options.assert_valid_keys(VALID_FIND_OPTIONS) + end + end # Creates an instance of a Module Class, i.e. Account, User, Contact, etc. # This call depends upon SugarCRM.modules having actual data in it. If you # are using Base.establish_connection, you should be fine. But if you are # using the Connection class by itself, you may need to prime the pump with # a call to Module.register_all def initialize(id=nil, attributes={}) @id = id - @attributes = attributes_from_module_fields.merge(attributes) + @attributes = self.class.attributes_from_module_fields.merge(attributes) @associations = associations_from_module_link_fields define_attribute_methods define_association_methods end @@ -74,12 +306,13 @@ end def association_methods_generated? self.class.association_methods_generated end - + Base.class_eval do include AttributeMethods + extend AttributeMethods::ClassMethods include AssociationMethods end end; end \ No newline at end of file