Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #2990: scope_constraints.patch

File scope_constraints.patch, 9.0 kB (added by skaes, 4 years ago)
  • activerecord/lib/active_record/validations.rb

    old new  
    660660        if attributes.is_a?(Array) 
    661661          attributes.collect { |attr| create!(attr) } 
    662662        else 
    663           attributes.reverse_merge!(scope(:create)) if scoped?(:create) 
    664  
     663          if create_scope = scoped_methods[:create] 
     664            attributes.reverse_merge!(create_scope) 
     665          end 
    665666          object = new(attributes) 
    666667          object.save! 
    667668          object 
  • activerecord/lib/active_record/base.rb

    old new  
    399399        # Inherit :readonly from finder scope if set.  Otherwise, 
    400400        # if :joins is not blank then :readonly defaults to true. 
    401401        unless options.has_key?(:readonly) 
    402           if scoped?(:find, :readonly) 
    403             options[:readonly] = scope(:find, :readonly) 
     402          if (finder_scope = scoped_methods[:find]) && finder_scope.has_key?(:readonly) 
     403            options[:readonly] = finder_scope[:readonly] 
    404404          elsif !options[:joins].blank? 
    405405            options[:readonly] = true 
    406406          end 
     
    462462        if attributes.is_a?(Array) 
    463463          attributes.collect { |attr| create(attr) } 
    464464        else 
    465           attributes.reverse_merge!(scope(:create)) if scoped?(:create) 
    466  
     465          if create_scope = scoped_methods[:create] 
     466            attributes.reverse_merge!(create_scope) 
     467          end 
    467468          object = new(attributes) 
    468469          object.save 
    469470          object 
     
    500501      #   Billing.update_all "category = 'authorized', approved = 1", "author = 'David'" 
    501502      def update_all(updates, conditions = nil) 
    502503        sql  = "UPDATE #{table_name} SET #{sanitize_sql(updates)} " 
    503         add_conditions!(sql, conditions
     504        add_conditions!(sql, conditions, scoped_methods[:find]
    504505        connection.update(sql, "#{name} Update") 
    505506      end 
    506507 
     
    516517      #   Post.delete_all "person_id = 5 AND (category = 'Something' OR category = 'Else')" 
    517518      def delete_all(conditions = nil) 
    518519        sql = "DELETE FROM #{table_name} " 
    519         add_conditions!(sql, conditions
     520        add_conditions!(sql, conditions, scoped_methods[:find]
    520521        connection.delete(sql, "#{name} Delete all") 
    521522      end 
    522523 
     
    525526      def count(conditions = nil, joins = nil) 
    526527        sql  = "SELECT COUNT(*) FROM #{table_name} " 
    527528        sql << " #{joins} " if joins 
    528         add_conditions!(sql, conditions
     529        add_conditions!(sql, conditions, scoped_methods[:find]
    529530        count_by_sql(sql) 
    530531      end 
    531532 
     
    849850        logger.level = old_logger_level if logger 
    850851      end 
    851852 
     853      VALID_SCOPING_METHODS = [:find, :create] 
     854      VALID_KEYS_FOR_FIND_SCOPE = [:conditions, :joins, :offset, :limit, :readonly] 
     855 
    852856      # Scope parameters to method calls within the block.  Takes a hash of method_name => parameters hash. 
    853857      # method_name may be :find or :create. 
    854858      # :find parameters may include the <tt>:conditions</tt>, <tt>:joins</tt>, 
     
    861865      #     a.blog_id == 1 
    862866      #   end 
    863867      def with_scope(method_scoping = {}) 
    864         # Dup first and second level of hash (method and params). 
    865         method_scoping = method_scoping.inject({}) do |hash, (method, params)| 
    866           hash[method] = params.dup 
    867           hash 
    868         end 
     868        old_methods = self.scoped_methods 
     869        new_methods = merge_scope_methods(old_methods, method_scoping) 
    869870 
    870         method_scoping.assert_valid_keys [:find, :create] 
    871         if f = method_scoping[:find] 
    872           f.assert_valid_keys [:conditions, :joins, :offset, :limit, :readonly] 
    873           f[:readonly] = true if !f[:joins].blank? && !f.has_key?(:readonly) 
     871        method_scoping.assert_valid_keys VALID_SCOPING_METHODS 
     872        if f = new_methods[:find] 
     873          f.assert_valid_keys VALID_KEYS_FOR_FIND_SCOPE 
     874          f[:readonly] = true unless f[:joins].blank? || f.has_key?(:readonly) 
    874875        end 
    875876 
    876         raise ArgumentError, "Nested scopes are not yet supported: #{scoped_methods.inspect}" unless scoped_methods.nil? 
    877  
    878         self.scoped_methods = method_scoping 
     877        self.scoped_methods = new_methods 
    879878        yield 
    880879      ensure  
    881         self.scoped_methods = nil 
     880        self.scoped_methods = old_methods 
    882881      end 
    883882 
     883      # merge inherited constraints for with_scope. clones the hash structure up to 2nd level. 
     884      # fails, if new_methods[x] has a key which is already present in old_methods[x] 
     885      # so merging {:create => x } with {:find => ...} will succeed, for example 
     886      def merge_scope_methods(old_methods, new_methods) 
     887        result = {} 
     888        new_methods.each do |scope, constraints| 
     889          unless old_constraints = old_methods[scope] 
     890            result[scope] = constraints.dup  
     891          else 
     892            constraints.each_key do |k| 
     893              raise ArgumentError, "in merge_scope_methods: cannot merge old #{old_methods.inspect} with #{new_methods.inspect} for key #{k}" if old_constraints.include?(k) 
     894            end 
     895            result[scope] = old_constraints.merge(constraints) 
     896          end 
     897        end 
     898        result 
     899      end 
     900      private :merge_scope_methods 
     901 
    884902      # Overwrite the default class equality method to provide support for association proxies. 
    885903      def ===(object) 
    886904        object.is_a?(self) 
     
    888906 
    889907      # Deprecated  
    890908      def threaded_connections 
    891         allow_concurrency 
     909        self.allow_concurrency 
    892910      end 
    893911 
    894912      # Deprecated  
     
    932950        end 
    933951 
    934952        def construct_finder_sql(options) 
     953          finder_scope = scoped_methods[:find] 
    935954          sql  = "SELECT #{options[:select] || '*'} FROM #{table_name} " 
    936           add_joins!(sql, options
    937           add_conditions!(sql, options[:conditions]
     955          add_joins!(sql, options, finder_scope
     956          add_conditions!(sql, options[:conditions], finder_scope
    938957          sql << " GROUP BY #{options[:group]} " if options[:group] 
    939958          sql << " ORDER BY #{options[:order]} " if options[:order] 
    940           add_limit!(sql, options
     959          add_limit!(sql, options, finder_scope
    941960          sql 
    942961        end 
    943962 
    944         def add_limit!(sql, options) 
    945           options[:limit]  ||= scope(:find, :limit) 
    946           options[:offset] ||= scope(:find, :offset) 
     963        def add_limit!(sql, options, finder_scope=nil) 
     964          if finder_scope 
     965            options[:limit]  ||= finder_scope[:limit] 
     966            options[:offset] ||= finder_scope[:offset] 
     967          end 
    947968          connection.add_limit_offset!(sql, options) 
    948969        end 
    949970 
    950         def add_joins!(sql, options
    951           join = scope(:find, :joins) || options[:joins] 
     971        def add_joins!(sql, options, finder_scope=nil
     972          join = (finder_scope && finder_scope[:joins]) || options[:joins] 
    952973          sql << " #{join} " if join 
    953974        end 
    954975 
    955976        # Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed. 
    956         def add_conditions!(sql, conditions)           
    957           segments = [scope(:find, :conditions)] 
     977        def add_conditions!(sql, conditions, finder_scope=nil)           
     978          segments = [] 
     979          segments << finder_scope[:conditions] if finder_scope 
    958980          segments << sanitize_sql(conditions) unless conditions.nil? 
    959981          segments << type_condition unless descends_from_active_record?         
    960982          segments.compact! 
     
    10751097          @@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses } 
    10761098        end 
    10771099 
    1078         # Test whether the given method and optional key are scoped. 
    1079         def scoped?(method, key = nil) 
    1080           scoped_methods and scoped_methods.has_key?(method) and (key.nil? or scope(method).has_key?(key)) 
    1081         end 
    1082  
    1083         # Retrieve the scope for the given method and optional key. 
    1084         def scope(method, key = nil) 
    1085           if scoped_methods and scope = scoped_methods[method] 
    1086             key ? scope[key] : scope 
    1087           end 
    1088         end 
    1089  
    10901100        def scoped_methods 
    10911101          if allow_concurrency 
    1092             Thread.current[:scoped_methods] ||= {} 
    1093             Thread.current[:scoped_methods][self] ||= nil 
     1102            scoped_methods = (Thread.current[:scoped_methods] ||= {}) 
     1103            scoped_methods[self] ||= {} 
    10941104          else 
    1095             @scoped_methods ||= nil 
     1105            @scoped_methods ||= {} 
    10961106          end 
    10971107        end 
    10981108 
    10991109        def scoped_methods=(value) 
    11001110          if allow_concurrency 
    1101             Thread.current[:scoped_methods] ||= {} 
    1102             Thread.current[:scoped_methods][self] = value 
     1111            scoped_methods = (Thread.current[:scoped_methods] ||= {}) 
     1112            scoped_methods[self] = value 
    11031113          else 
    11041114            @scoped_methods = value 
    11051115          end