Guest User

Untitled

a guest
Jul 18th, 2018
89
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.23 KB | None | 0 0
  1. require 'active_support/core_ext/array/wrap'
  2.  
  3. module ActiveRecord
  4. module Associations
  5. # = Active Record Association Collection
  6. #
  7. # AssociationCollection is an abstract class that provides common stuff to
  8. # ease the implementation of association proxies that represent
  9. # collections. See the class hierarchy in AssociationProxy.
  10. #
  11. # You need to be careful with assumptions regarding the target: The proxy
  12. # does not fetch records from the database until it needs them, but new
  13. # ones created with +build+ are added to the target. So, the target may be
  14. # non-empty and still lack children waiting to be read from the database.
  15. # If you look directly to the database you cannot assume that's the entire
  16. # collection because new records may have been added to the target, etc.
  17. #
  18. # If you need to work on all current children, new and existing records,
  19. # +load_target+ and the +loaded+ flag are your friends.
  20. class AssociationCollection < AssociationProxy #:nodoc:
  21. delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
  22.  
  23. def select(select = nil)
  24. if block_given?
  25. load_target
  26. @target.select.each { |e| yield e }
  27. else
  28. scoped.select(select)
  29. end
  30. end
  31.  
  32. def scoped
  33. with_scope(@scope) { @reflection.klass.scoped }
  34. end
  35.  
  36. def find(*args)
  37. options = args.extract_options!
  38.  
  39. # If using a custom finder_sql, scan the entire collection.
  40. if @reflection.options[:finder_sql]
  41. expects_array = args.first.kind_of?(Array)
  42. ids = args.flatten.compact.uniq.map { |arg| arg.to_i }
  43.  
  44. if ids.size == 1
  45. id = ids.first
  46. record = load_target.detect { |r| id == r.id }
  47. expects_array ? [ record ] : record
  48. else
  49. load_target.select { |r| ids.include?(r.id) }
  50. end
  51. else
  52. merge_options_from_reflection!(options)
  53. construct_find_options!(options)
  54.  
  55. with_scope(:find => @scope[:find].slice(:conditions, :order)) do
  56. relation = @reflection.klass.send(:construct_finder_arel, options, @reflection.klass.send(:current_scoped_methods))
  57.  
  58. case args.first
  59. when :first, :last
  60. relation.send(args.first)
  61. when :all
  62. records = relation.all
  63. @reflection.options[:uniq] ? uniq(records) : records
  64. else
  65. relation.find(*args)
  66. end
  67. end
  68. end
  69. end
  70.  
  71. # Fetches the first one using SQL if possible.
  72. def first(*args)
  73. if fetch_first_or_last_using_find?(args)
  74. find(:first, *args)
  75. else
  76. load_target unless loaded?
  77. args.shift if args.first.kind_of?(Hash) && args.first.empty?
  78. @target.first(*args)
  79. end
  80. end
  81.  
  82. # Fetches the last one using SQL if possible.
  83. def last(*args)
  84. if fetch_first_or_last_using_find?(args)
  85. find(:last, *args)
  86. else
  87. load_target unless loaded?
  88. @target.last(*args)
  89. end
  90. end
  91.  
  92. def to_ary
  93. load_target
  94. if @target.is_a?(Array)
  95. @target
  96. else
  97. Array.wrap(@target)
  98. end
  99. end
  100. alias_method :to_a, :to_ary
  101.  
  102. def reset
  103. reset_target!
  104. reset_scopes_cache!
  105. @loaded = false
  106. end
  107.  
  108. def build(attributes = {}, &block)
  109. if attributes.is_a?(Array)
  110. attributes.collect { |attr| build(attr, &block) }
  111. else
  112. build_record(attributes) do |record|
  113. block.call(record) if block_given?
  114. set_belongs_to_association_for(record)
  115. end
  116. end
  117. end
  118.  
  119. # Add +records+ to this association. Returns +self+ so method calls may be chained.
  120. # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
  121. def <<(*records)
  122. result = true
  123. load_target if @owner.new_record?
  124.  
  125. transaction do
  126. flatten_deeper(records).each do |record|
  127. raise_on_type_mismatch(record)
  128. add_record_to_target_with_callbacks(record) do |r|
  129. result &&= insert_record(record) unless @owner.new_record?
  130. end
  131. end
  132. end
  133.  
  134. result && self
  135. end
  136.  
  137. alias_method :push, :<<
  138. alias_method :concat, :<<
  139.  
  140. # Starts a transaction in the association class's database connection.
  141. #
  142. # class Author < ActiveRecord::Base
  143. # has_many :books
  144. # end
  145. #
  146. # Author.first.books.transaction do
  147. # # same effect as calling Book.transaction
  148. # end
  149. def transaction(*args)
  150. @reflection.klass.transaction(*args) do
  151. yield
  152. end
  153. end
  154.  
  155. # Remove all records from this association
  156. #
  157. # See delete for more info.
  158. def delete_all
  159. load_target
  160. delete(@target)
  161. reset_target!
  162. reset_scopes_cache!
  163. end
  164.  
  165. # Calculate sum using SQL, not Enumerable
  166. def sum(*args)
  167. if block_given?
  168. calculate(:sum, *args) { |*block_args| yield(*block_args) }
  169. else
  170. calculate(:sum, *args)
  171. end
  172. end
  173.  
  174. # Count all records using SQL. If the +:counter_sql+ or +:finder_sql+ option is set for the
  175. # association, it will be used for the query. Otherwise, construct options and pass them with
  176. # scope to the target class's +count+.
  177. def count(column_name = nil, options = {})
  178. column_name, options = nil, column_name if column_name.is_a?(Hash)
  179.  
  180. if @reflection.options[:counter_sql] || @reflection.options[:finder_sql]
  181. unless options.blank?
  182. raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
  183. end
  184.  
  185. @reflection.klass.count_by_sql(custom_counter_sql)
  186. else
  187.  
  188. if @reflection.options[:uniq]
  189. # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
  190. column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" unless column_name
  191. options.merge!(:distinct => true)
  192. end
  193.  
  194. value = @reflection.klass.send(:with_scope, @scope) { @reflection.klass.count(column_name, options) }
  195.  
  196. limit = @reflection.options[:limit]
  197. offset = @reflection.options[:offset]
  198.  
  199. if limit || offset
  200. [ [value - offset.to_i, 0].max, limit.to_i ].min
  201. else
  202. value
  203. end
  204. end
  205. end
  206.  
  207. # Removes +records+ from this association calling +before_remove+ and
  208. # +after_remove+ callbacks.
  209. #
  210. # This method is abstract in the sense that +delete_records+ has to be
  211. # provided by descendants. Note this method does not imply the records
  212. # are actually removed from the database, that depends precisely on
  213. # +delete_records+. They are in any case removed from the collection.
  214. def delete(*records)
  215. remove_records(records) do |_records, old_records|
  216. delete_records(old_records) if old_records.any?
  217. _records.each { |record| @target.delete(record) }
  218. end
  219. end
  220.  
  221. # Destroy +records+ and remove them from this association calling
  222. # +before_remove+ and +after_remove+ callbacks.
  223. #
  224. # Note that this method will _always_ remove records from the database
  225. # ignoring the +:dependent+ option.
  226. def destroy(*records)
  227. records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)}
  228. remove_records(records) do |_records, old_records|
  229. old_records.each { |record| record.destroy }
  230. end
  231.  
  232. load_target
  233. end
  234.  
  235. # Removes all records from this association. Returns +self+ so method calls may be chained.
  236. def clear
  237. unless length.zero? # forces load_target if it hasn't happened already
  238. if @reflection.options[:dependent] == :destroy
  239. destroy_all
  240. else
  241. delete_all
  242. end
  243. end
  244.  
  245. self
  246. end
  247.  
  248. # Destroy all the records from this association.
  249. #
  250. # See destroy for more info.
  251. def destroy_all
  252. load_target
  253. destroy(@target).tap do
  254. reset_target!
  255. reset_scopes_cache!
  256. end
  257. end
  258.  
  259. def create(attrs = {})
  260. if attrs.is_a?(Array)
  261. attrs.collect { |attr| create(attr) }
  262. else
  263. create_record(attrs) do |record|
  264. yield(record) if block_given?
  265. insert_record(record, false)
  266. end
  267. end
  268. end
  269.  
  270. def create!(attrs = {})
  271. create_record(attrs) do |record|
  272. yield(record) if block_given?
  273. insert_record(record, true)
  274. end
  275. end
  276.  
  277. # Returns the size of the collection by executing a SELECT COUNT(*)
  278. # query if the collection hasn't been loaded, and calling
  279. # <tt>collection.size</tt> if it has.
  280. #
  281. # If the collection has been already loaded +size+ and +length+ are
  282. # equivalent. If not and you are going to need the records anyway
  283. # +length+ will take one less query. Otherwise +size+ is more efficient.
  284. #
  285. # This method is abstract in the sense that it relies on
  286. # +count_records+, which is a method descendants have to provide.
  287. def size
  288. if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
  289. @target.size
  290. elsif !loaded? && @reflection.options[:group]
  291. load_target.size
  292. elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
  293. unsaved_records = @target.select { |r| r.new_record? }
  294. unsaved_records.size + count_records
  295. else
  296. count_records
  297. end
  298. end
  299.  
  300. # Returns the size of the collection calling +size+ on the target.
  301. #
  302. # If the collection has been already loaded +length+ and +size+ are
  303. # equivalent. If not and you are going to need the records anyway this
  304. # method will take one less query. Otherwise +size+ is more efficient.
  305. def length
  306. load_target.size
  307. end
  308.  
  309. # Equivalent to <tt>collection.size.zero?</tt>. If the collection has
  310. # not been already loaded and you are going to fetch the records anyway
  311. # it is better to check <tt>collection.length.zero?</tt>.
  312. def empty?
  313. size.zero?
  314. end
  315.  
  316. def any?
  317. if block_given?
  318. method_missing(:any?) { |*block_args| yield(*block_args) }
  319. else
  320. !empty?
  321. end
  322. end
  323.  
  324. # Returns true if the collection has more than 1 record. Equivalent to collection.size > 1.
  325. def many?
  326. if block_given?
  327. method_missing(:many?) { |*block_args| yield(*block_args) }
  328. else
  329. size > 1
  330. end
  331. end
  332.  
  333. def uniq(collection = self)
  334. seen = {}
  335. collection.find_all do |record|
  336. seen[record.id] = true unless seen.key?(record.id)
  337. end
  338. end
  339.  
  340. # Replace this collection with +other_array+
  341. # This will perform a diff and delete/add only records that have changed.
  342. def replace(other_array)
  343. other_array.each { |val| raise_on_type_mismatch(val) }
  344.  
  345. load_target
  346.  
  347. transaction do
  348. delete(@target - other_array)
  349. concat(other_array - @target)
  350. end
  351. end
  352.  
  353. def include?(record)
  354. return false unless record.is_a?(@reflection.klass)
  355. return include_in_memory?(record) if record.new_record?
  356. load_target if @reflection.options[:finder_sql] && !loaded?
  357. loaded? ? @target.include?(record) : exists?(record)
  358. end
  359.  
  360. def proxy_respond_to?(method, include_private = false)
  361. super || @reflection.klass.respond_to?(method, include_private)
  362. end
  363.  
  364. protected
  365. def construct_find_options!(options)
  366. end
  367.  
  368. def load_target
  369. if !@owner.new_record? || foreign_key_present
  370. begin
  371. unless loaded?
  372. if @target.is_a?(Array) && @target.any?
  373. @target = find_target.map do |f|
  374. i = @target.index(f)
  375. if i
  376. @target.delete_at(i).tap do |t|
  377. keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
  378. f.attributes.except(*keys).each do |k,v|
  379. t.send("#{k}=", v)
  380. end
  381. end
  382. else
  383. f
  384. end
  385. end + @target
  386. else
  387. @target = find_target
  388. end
  389. end
  390. rescue ActiveRecord::RecordNotFound
  391. reset
  392. end
  393. end
  394.  
  395. loaded if target
  396. target
  397. end
  398.  
  399. def method_missing(method, *args)
  400. match = DynamicFinderMatch.match(method)
  401. if match && match.creator?
  402. attributes = match.attribute_names
  403. return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)])
  404. end
  405.  
  406. if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
  407. super
  408. elsif @reflection.klass.scopes[method]
  409. @_scopes_cache ||= {}
  410. @_scopes_cache[method] ||= {}
  411. @_scopes_cache[method][args] ||= with_scope(@scope) { @reflection.klass.send(method, *args) }
  412. else
  413. with_scope(@scope) do
  414. if block_given?
  415. @reflection.klass.send(method, *args) { |*block_args| yield(*block_args) }
  416. else
  417. @reflection.klass.send(method, *args)
  418. end
  419. end
  420. end
  421. end
  422.  
  423. def custom_counter_sql
  424. if @reflection.options[:counter_sql]
  425. counter_sql = @reflection.options[:counter_sql]
  426. else
  427. # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
  428. counter_sql = @reflection.options[:finder_sql].sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
  429. end
  430.  
  431. interpolate_sql(counter_sql)
  432. end
  433.  
  434. def custom_finder_sql
  435. interpolate_sql(@reflection.options[:finder_sql])
  436. end
  437.  
  438. def reset_target!
  439. @target = Array.new
  440. end
  441.  
  442. def reset_scopes_cache!
  443. @_scopes_cache = {}
  444. end
  445.  
  446. def find_target
  447. records =
  448. if @reflection.options[:finder_sql]
  449. @reflection.klass.find_by_sql(custom_finder_sql)
  450. else
  451. find(:all)
  452. end
  453.  
  454. records = @reflection.options[:uniq] ? uniq(records) : records
  455. records.each do |record|
  456. set_inverse_instance(record, @owner)
  457. end
  458. records
  459. end
  460.  
  461. def add_record_to_target_with_callbacks(record)
  462. callback(:before_add, record)
  463. yield(record) if block_given?
  464. @target ||= [] unless loaded?
  465. if @reflection.options[:uniq] && index = @target.index(record)
  466. @target[index] = record
  467. else
  468. @target << record
  469. end
  470. callback(:after_add, record)
  471. set_inverse_instance(record, @owner)
  472. record
  473. end
  474.  
  475. private
  476. # Do the relevant stuff to insert the given record into the association collection. The
  477. # force param specifies whether or not an exception should be raised on failure. The
  478. # validate param specifies whether validation should be performed (if force is false).
  479. def insert_record(record, force = true, validate = true)
  480. raise NotImplementedError
  481. end
  482.  
  483. def save_record(record, force, validate)
  484. force ? record.save! : record.save(:validate => validate)
  485. end
  486.  
  487. def create_record(attrs, &block)
  488. ensure_owner_is_persisted!
  489.  
  490. transaction do
  491. with_scope(:create => @scope[:create].merge(scoped.where_values_hash || {})) do
  492. build_record(attrs, &block)
  493. end
  494. end
  495. end
  496.  
  497. def build_record(attrs, &block)
  498. attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
  499. record = @reflection.build_association(attrs)
  500. add_record_to_target_with_callbacks(record, &block)
  501. end
  502.  
  503. def remove_records(*records)
  504. records = flatten_deeper(records)
  505. records.each { |record| raise_on_type_mismatch(record) }
  506.  
  507. transaction do
  508. records.each { |record| callback(:before_remove, record) }
  509. old_records = records.reject { |r| r.new_record? }
  510. yield(records, old_records)
  511. records.each { |record| callback(:after_remove, record) }
  512. end
  513. end
  514.  
  515. def callback(method, record)
  516. callbacks_for(method).each do |callback|
  517. case callback
  518. when Symbol
  519. @owner.send(callback, record)
  520. when Proc
  521. callback.call(@owner, record)
  522. else
  523. callback.send(method, @owner, record)
  524. end
  525. end
  526. end
  527.  
  528. def callbacks_for(callback_name)
  529. full_callback_name = "#{callback_name}_for_#{@reflection.name}"
  530. @owner.class.send(full_callback_name.to_sym) || []
  531. end
  532.  
  533. def ensure_owner_is_persisted!
  534. unless @owner.persisted?
  535. raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
  536. end
  537. end
  538.  
  539. def fetch_first_or_last_using_find?(args)
  540. (args.first.kind_of?(Hash) && !args.first.empty?) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] ||
  541. @target.any? { |record| record.new_record? } || args.first.kind_of?(Integer))
  542. end
  543.  
  544. def include_in_memory?(record)
  545. if @reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
  546. @owner.send(proxy_reflection.through_reflection.name.to_sym).any? do |source|
  547. target = source.send(proxy_reflection.source_reflection.name)
  548. target.respond_to?(:include?) ? target.include?(record) : target == record
  549. end
  550. else
  551. @target.include?(record)
  552. end
  553. end
  554. end
  555. end
  556. end
Add Comment
Please, Sign In to add comment