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 660 660 if attributes.is_a?(Array) 661 661 attributes.collect { |attr| create!(attr) } 662 662 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 665 666 object = new(attributes) 666 667 object.save! 667 668 object -
activerecord/lib/active_record/base.rb
old new 399 399 # Inherit :readonly from finder scope if set. Otherwise, 400 400 # if :joins is not blank then :readonly defaults to true. 401 401 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] 404 404 elsif !options[:joins].blank? 405 405 options[:readonly] = true 406 406 end … … 462 462 if attributes.is_a?(Array) 463 463 attributes.collect { |attr| create(attr) } 464 464 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 467 468 object = new(attributes) 468 469 object.save 469 470 object … … 500 501 # Billing.update_all "category = 'authorized', approved = 1", "author = 'David'" 501 502 def update_all(updates, conditions = nil) 502 503 sql = "UPDATE #{table_name} SET #{sanitize_sql(updates)} " 503 add_conditions!(sql, conditions )504 add_conditions!(sql, conditions, scoped_methods[:find]) 504 505 connection.update(sql, "#{name} Update") 505 506 end 506 507 … … 516 517 # Post.delete_all "person_id = 5 AND (category = 'Something' OR category = 'Else')" 517 518 def delete_all(conditions = nil) 518 519 sql = "DELETE FROM #{table_name} " 519 add_conditions!(sql, conditions )520 add_conditions!(sql, conditions, scoped_methods[:find]) 520 521 connection.delete(sql, "#{name} Delete all") 521 522 end 522 523 … … 525 526 def count(conditions = nil, joins = nil) 526 527 sql = "SELECT COUNT(*) FROM #{table_name} " 527 528 sql << " #{joins} " if joins 528 add_conditions!(sql, conditions )529 add_conditions!(sql, conditions, scoped_methods[:find]) 529 530 count_by_sql(sql) 530 531 end 531 532 … … 849 850 logger.level = old_logger_level if logger 850 851 end 851 852 853 VALID_SCOPING_METHODS = [:find, :create] 854 VALID_KEYS_FOR_FIND_SCOPE = [:conditions, :joins, :offset, :limit, :readonly] 855 852 856 # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash. 853 857 # method_name may be :find or :create. 854 858 # :find parameters may include the <tt>:conditions</tt>, <tt>:joins</tt>, … … 861 865 # a.blog_id == 1 862 866 # end 863 867 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) 869 870 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) 874 875 end 875 876 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 879 878 yield 880 879 ensure 881 self.scoped_methods = nil880 self.scoped_methods = old_methods 882 881 end 883 882 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 884 902 # Overwrite the default class equality method to provide support for association proxies. 885 903 def ===(object) 886 904 object.is_a?(self) … … 888 906 889 907 # Deprecated 890 908 def threaded_connections 891 allow_concurrency909 self.allow_concurrency 892 910 end 893 911 894 912 # Deprecated … … 932 950 end 933 951 934 952 def construct_finder_sql(options) 953 finder_scope = scoped_methods[:find] 935 954 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) 938 957 sql << " GROUP BY #{options[:group]} " if options[:group] 939 958 sql << " ORDER BY #{options[:order]} " if options[:order] 940 add_limit!(sql, options )959 add_limit!(sql, options, finder_scope) 941 960 sql 942 961 end 943 962 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 947 968 connection.add_limit_offset!(sql, options) 948 969 end 949 970 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] 952 973 sql << " #{join} " if join 953 974 end 954 975 955 976 # 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 958 980 segments << sanitize_sql(conditions) unless conditions.nil? 959 981 segments << type_condition unless descends_from_active_record? 960 982 segments.compact! … … 1075 1097 @@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses } 1076 1098 end 1077 1099 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 end1082 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] : scope1087 end1088 end1089 1090 1100 def scoped_methods 1091 1101 if allow_concurrency 1092 Thread.current[:scoped_methods] ||= {}1093 Thread.current[:scoped_methods][self] ||= nil1102 scoped_methods = (Thread.current[:scoped_methods] ||= {}) 1103 scoped_methods[self] ||= {} 1094 1104 else 1095 @scoped_methods ||= nil1105 @scoped_methods ||= {} 1096 1106 end 1097 1107 end 1098 1108 1099 1109 def scoped_methods=(value) 1100 1110 if allow_concurrency 1101 Thread.current[:scoped_methods] ||= {}1102 Thread.current[:scoped_methods][self] = value1111 scoped_methods = (Thread.current[:scoped_methods] ||= {}) 1112 scoped_methods[self] = value 1103 1113 else 1104 1114 @scoped_methods = value 1105 1115 end