| | 1671 | |
|---|
| | 1672 | #loads the named associations for the activerecord object (or objects) given |
|---|
| | 1673 | # |
|---|
| | 1674 | # |
|---|
| | 1675 | def preload_associations(top_objects, associations) |
|---|
| | 1676 | top_objects = [top_objects].flatten.compact |
|---|
| | 1677 | return if top_objects.empty? |
|---|
| | 1678 | case associations |
|---|
| | 1679 | when Array then associations.each {|association| preload_associations(top_objects, association)} |
|---|
| | 1680 | when Symbol, String then preload_one_association(top_objects, associations.to_sym) |
|---|
| | 1681 | when Hash then |
|---|
| | 1682 | associations.each do |parent, child| |
|---|
| | 1683 | raise "parent must be an association name" unless parent.is_a?( String) || parent.is_a?( Symbol) |
|---|
| | 1684 | preload_associations(top_objects, parent) |
|---|
| | 1685 | reflection = reflections[parent] |
|---|
| | 1686 | parents = top_objects.map {|top_object| top_object.send(reflection.name)}.flatten |
|---|
| | 1687 | unless parents.empty? |
|---|
| | 1688 | parents.first.class.preload_associations(parents, child) |
|---|
| | 1689 | end |
|---|
| | 1690 | end |
|---|
| | 1691 | end |
|---|
| | 1692 | |
|---|
| | 1693 | end |
|---|
| | 1694 | |
|---|
| | 1695 | def add_preloaded_object_to_collection(parent_object, reflection_name, associated_object) |
|---|
| | 1696 | association_proxy = parent_object.send(reflection_name) |
|---|
| | 1697 | association_proxy.loaded |
|---|
| | 1698 | if associated_object.is_a? Array |
|---|
| | 1699 | association_proxy.target.push(*associated_object) |
|---|
| | 1700 | else |
|---|
| | 1701 | association_proxy.target << associated_object |
|---|
| | 1702 | end |
|---|
| | 1703 | end |
|---|
| | 1704 | |
|---|
| | 1705 | def preload_collection_object(id_to_object_map, reflection_name, associated_object, key) |
|---|
| | 1706 | mapped_objects = id_to_object_map[associated_object[key].to_i] |
|---|
| | 1707 | mapped_objects.each do |mapped_object| |
|---|
| | 1708 | add_preloaded_object_to_collection(mapped_object, reflection_name, associated_object) |
|---|
| | 1709 | end |
|---|
| | 1710 | end |
|---|
| | 1711 | |
|---|
| | 1712 | def preload_single_object(id_to_object_map, reflection_name, associated_object, key) |
|---|
| | 1713 | mapped_objects = id_to_object_map[associated_object[key].to_i] |
|---|
| | 1714 | mapped_objects.each do |mapped_object| |
|---|
| | 1715 | mapped_object.send("set_#{reflection_name}_target", associated_object) |
|---|
| | 1716 | end |
|---|
| | 1717 | end |
|---|
| | 1718 | |
|---|
| | 1719 | |
|---|
| | 1720 | def has_n_preload_conditions(reflection) |
|---|
| | 1721 | options = reflection.options |
|---|
| | 1722 | if interface = reflection.options[:as] |
|---|
| | 1723 | conditions = "#{reflection.klass.table_name}.#{interface}_id IN (?) and #{reflection.klass.table_name}.#{interface}_type = '#{self.name}'" |
|---|
| | 1724 | else |
|---|
| | 1725 | foreign_key = options[:foreign_key] || reflection.active_record.to_s.foreign_key |
|---|
| | 1726 | conditions = "#{reflection.klass.table_name}.#{foreign_key} IN (?)" |
|---|
| | 1727 | end |
|---|
| | 1728 | end |
|---|
| | 1729 | |
|---|
| | 1730 | def preload_one_association(top_objects, association) |
|---|
| | 1731 | reflection = reflections[association] |
|---|
| | 1732 | raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection |
|---|
| | 1733 | |
|---|
| | 1734 | reflection_name = reflection.name |
|---|
| | 1735 | options = reflection.options |
|---|
| | 1736 | table_name = reflection.klass.table_name unless options[:polymorphic] |
|---|
| | 1737 | primary_key_name = reflection.primary_key_name |
|---|
| | 1738 | foreign_key = options[:foreign_key] || reflection.active_record.to_s.foreign_key |
|---|
| | 1739 | if options[:as] |
|---|
| | 1740 | foreign_key = "#{options[:as]}_id" |
|---|
| | 1741 | end |
|---|
| | 1742 | #it is not enough to just do index_by &:id as there could be duplicates (see test_duplicate_middle_objects) |
|---|
| | 1743 | |
|---|
| | 1744 | id_to_object_map = {} |
|---|
| | 1745 | ids = [] |
|---|
| | 1746 | top_objects.each do |top_object| |
|---|
| | 1747 | ids << top_object.id |
|---|
| | 1748 | mapped_objects = (id_to_object_map[top_object.id] ||= []) |
|---|
| | 1749 | mapped_objects << top_object |
|---|
| | 1750 | end |
|---|
| | 1751 | ids = top_objects.uniq |
|---|
| | 1752 | |
|---|
| | 1753 | case reflection.macro |
|---|
| | 1754 | when :belongs_to |
|---|
| | 1755 | if options[:polymorphic] |
|---|
| | 1756 | polymorph_type = options[:foreign_type] |
|---|
| | 1757 | klasses = top_objects.map {|object| object.send polymorph_type}.uniq.compact.map &:constantize |
|---|
| | 1758 | else |
|---|
| | 1759 | klasses = [reflection.klass] |
|---|
| | 1760 | end |
|---|
| | 1761 | klasses.each do |klass| |
|---|
| | 1762 | table_name = klass.table_name |
|---|
| | 1763 | conditions = "t0.#{self.primary_key} IN (?)" |
|---|
| | 1764 | conditions << " AND (#{sanitize_sql options[:conditions]})" if options[:conditions] |
|---|
| | 1765 | |
|---|
| | 1766 | joins = "INNER JOIN #{self.table_name} AS t0 ON #{table_name}.#{primary_key} = t0.#{primary_key_name}" |
|---|
| | 1767 | if options[:polymorphic] |
|---|
| | 1768 | joins << " AND #{polymorph_type} = '#{klass.name}'" |
|---|
| | 1769 | end |
|---|
| | 1770 | associated_objects = klass.find(:all, :conditions => [conditions, ids], |
|---|
| | 1771 | :joins => joins, |
|---|
| | 1772 | :preload => options[:preload], |
|---|
| | 1773 | :select => "#{options[:select] || table_name+'.*'}, t0.#{self.primary_key} as _parent_object_id", |
|---|
| | 1774 | :order => options[:order]) |
|---|
| | 1775 | associated_objects.each do |associated_object| |
|---|
| | 1776 | preload_single_object(id_to_object_map, reflection_name, associated_object, '_parent_object_id') |
|---|
| | 1777 | end |
|---|
| | 1778 | end |
|---|
| | 1779 | when :has_one |
|---|
| | 1780 | top_objects.each {|object| object.send("set_#{reflection.name}_target", nil)} |
|---|
| | 1781 | conditions = has_n_preload_conditions(reflection) |
|---|
| | 1782 | conditions << " AND (#{sanitize_sql options[:conditions]})" if options[:conditions] |
|---|
| | 1783 | associated_objects = reflection.klass.find(:all, :conditions => [conditions, ids], |
|---|
| | 1784 | :select => (options[:select] || "#{table_name}.*"), |
|---|
| | 1785 | :preload => options[:preload], |
|---|
| | 1786 | :order => options[:order]) |
|---|
| | 1787 | associated_objects.each do |associated_object| |
|---|
| | 1788 | preload_single_object(id_to_object_map, reflection_name, associated_object, foreign_key) |
|---|
| | 1789 | end |
|---|
| | 1790 | |
|---|
| | 1791 | when :has_many |
|---|
| | 1792 | top_objects.each {|object| object.send(reflection.name).loaded} |
|---|
| | 1793 | |
|---|
| | 1794 | if options[:through] |
|---|
| | 1795 | through_reflection = reflections[options[:through]] |
|---|
| | 1796 | through_primary_key = through_reflection.primary_key_name |
|---|
| | 1797 | top_objects.first.class.preload_one_association(top_objects, options[:through]) |
|---|
| | 1798 | through_objects = top_objects.compact.map {|object| object.send options[:through]}.flatten |
|---|
| | 1799 | |
|---|
| | 1800 | unless through_objects.empty? |
|---|
| | 1801 | source = reflection.source_reflection.name |
|---|
| | 1802 | through_objects.first.class.preload_one_association(through_objects, source) |
|---|
| | 1803 | |
|---|
| | 1804 | through_objects.compact.each do |through_object| |
|---|
| | 1805 | associated_object = through_object.send(source) |
|---|
| | 1806 | mapped_objects = id_to_object_map[through_object[through_primary_key].to_i] |
|---|
| | 1807 | mapped_objects.each do |mapped_object| |
|---|
| | 1808 | add_preloaded_object_to_collection(mapped_object, reflection_name, associated_object) |
|---|
| | 1809 | end |
|---|
| | 1810 | end |
|---|
| | 1811 | end |
|---|
| | 1812 | |
|---|
| | 1813 | else |
|---|
| | 1814 | conditions = has_n_preload_conditions(reflection) |
|---|
| | 1815 | conditions << " AND (#{sanitize_sql options[:conditions]})" if options[:conditions] |
|---|
| | 1816 | associated_objects = reflection.klass.find(:all, :conditions => [conditions, ids], |
|---|
| | 1817 | :select => (options[:select] || "#{table_name}.*"), |
|---|
| | 1818 | :preload => options[:preload], |
|---|
| | 1819 | :order => options[:order]) |
|---|
| | 1820 | associated_objects.each do |associated_object| |
|---|
| | 1821 | preload_collection_object(id_to_object_map, reflection_name, associated_object, foreign_key) |
|---|
| | 1822 | end |
|---|
| | 1823 | end |
|---|
| | 1824 | when :has_and_belongs_to_many |
|---|
| | 1825 | top_objects.each {|object| object.send(reflection.name).loaded} |
|---|
| | 1826 | conditions = "t0.#{reflection.primary_key_name} IN (?)" |
|---|
| | 1827 | conditions << " AND (#{options[:conditions]})" if options[:conditions] |
|---|
| | 1828 | associated_objects = reflection.klass.find(:all, :conditions => [conditions, ids], |
|---|
| | 1829 | :preload => options[:preload], |
|---|
| | 1830 | :joins => "INNER JOIN #{options[:join_table]} as t0 ON #{reflection.klass.table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}", |
|---|
| | 1831 | :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as _parent_object_id", |
|---|
| | 1832 | :order => options[:order]) |
|---|
| | 1833 | associated_objects.each do |associated_object| |
|---|
| | 1834 | preload_collection_object(id_to_object_map, reflection_name, associated_object, '_parent_object_id') |
|---|
| | 1835 | end |
|---|
| | 1836 | else |
|---|
| | 1837 | raise "Unsupported association type #{reflection.name}!" |
|---|
| | 1838 | end |
|---|
| | 1839 | |
|---|
| | 1840 | end |
|---|
| | 1841 | |
|---|