activerecord 1.9.1 → 1.10.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +78 -0
- data/README +1 -1
- data/install.rb +7 -42
- data/lib/active_record.rb +2 -0
- data/lib/active_record/acts/list.rb +28 -4
- data/lib/active_record/acts/nested_set.rb +212 -0
- data/lib/active_record/associations.rb +203 -21
- data/lib/active_record/associations/association_proxy.rb +10 -2
- data/lib/active_record/associations/belongs_to_association.rb +0 -1
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -9
- data/lib/active_record/associations/has_many_association.rb +25 -25
- data/lib/active_record/associations/has_one_association.rb +2 -2
- data/lib/active_record/base.rb +134 -110
- data/lib/active_record/connection_adapters/abstract_adapter.rb +9 -9
- data/lib/active_record/connection_adapters/mysql_adapter.rb +4 -0
- data/lib/active_record/connection_adapters/oci_adapter.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1 -2
- data/lib/active_record/deprecated_associations.rb +1 -19
- data/lib/active_record/deprecated_finders.rb +41 -0
- data/lib/active_record/fixtures.rb +24 -11
- data/lib/active_record/observer.rb +17 -11
- data/lib/active_record/reflection.rb +5 -1
- data/lib/active_record/transactions.rb +7 -0
- data/lib/active_record/validations.rb +32 -33
- data/rakefile +30 -6
- data/test/associations_go_eager_test.rb +55 -0
- data/test/associations_test.rb +72 -15
- data/test/base_test.rb +15 -21
- data/test/deprecated_associations_test.rb +0 -24
- data/test/deprecated_finder_test.rb +147 -0
- data/test/finder_test.rb +37 -37
- data/test/fixtures/author.rb +3 -0
- data/test/fixtures/authors.yml +7 -0
- data/test/fixtures/categories.yml +7 -0
- data/test/fixtures/categories_posts.yml +11 -0
- data/test/fixtures/category.rb +3 -0
- data/test/fixtures/comment.rb +5 -0
- data/test/fixtures/comments.yml +17 -0
- data/test/fixtures/company.rb +3 -0
- data/test/fixtures/courses.yml +4 -4
- data/test/fixtures/db_definitions/db2.drop.sql +6 -0
- data/test/fixtures/db_definitions/db2.sql +46 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +6 -1
- data/test/fixtures/db_definitions/mysql.sql +60 -12
- data/test/fixtures/db_definitions/mysql2.sql +1 -1
- data/test/fixtures/db_definitions/oci.drop.sql +5 -0
- data/test/fixtures/db_definitions/oci.sql +45 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +6 -0
- data/test/fixtures/db_definitions/postgresql.sql +45 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +6 -1
- data/test/fixtures/db_definitions/sqlite.sql +46 -0
- data/test/fixtures/db_definitions/sqlserver.drop.sql +7 -1
- data/test/fixtures/db_definitions/sqlserver.sql +46 -0
- data/test/fixtures/fk_test_has_fk.yml +3 -0
- data/test/fixtures/fk_test_has_pk.yml +2 -0
- data/test/fixtures/mixin.rb +18 -0
- data/test/fixtures/mixins.yml +30 -0
- data/test/fixtures/post.rb +8 -0
- data/test/fixtures/posts.yml +20 -0
- data/test/fixtures/task.rb +3 -0
- data/test/fixtures/tasks.yml +7 -0
- data/test/fixtures_test.rb +34 -2
- data/test/mixin_nested_set_test.rb +184 -0
- data/test/mixin_test.rb +28 -3
- data/test/validations_test.rb +16 -0
- metadata +21 -5
- data/test/fixtures/db_definitions/drop_oracle_tables.sql +0 -35
- data/test/fixtures/db_definitions/drop_oracle_tables2.sql +0 -3
data/CHANGELOG
CHANGED
@@ -1,3 +1,81 @@
|
|
1
|
+
*1.10.0* (19th April, 2005)
|
2
|
+
|
3
|
+
* Added eager loading of associations as a way to solve the N+1 problem more gracefully without piggy-back queries. Example:
|
4
|
+
|
5
|
+
for post in Post.find(:all, :limit => 100)
|
6
|
+
puts "Post: " + post.title
|
7
|
+
puts "Written by: " + post.author.name
|
8
|
+
puts "Last comment on: " + post.comments.first.created_on
|
9
|
+
end
|
10
|
+
|
11
|
+
This used to generate 301 database queries if all 100 posts had both author and comments. It can now be written as:
|
12
|
+
|
13
|
+
for post in Post.find(:all, :limit => 100, :include => [ :author, :comments ])
|
14
|
+
|
15
|
+
...and the number of database queries needed is now 1.
|
16
|
+
|
17
|
+
* Added new unified Base.find API and deprecated the use of find_first and find_all. See the documentation for Base.find. Examples:
|
18
|
+
|
19
|
+
Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
|
20
|
+
Person.find(1, 5, 6, :conditions => "administrator = 1", :order => "created_on DESC")
|
21
|
+
Person.find(:first, :order => "created_on DESC", :offset => 5)
|
22
|
+
Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
|
23
|
+
Person.find(:all, :offset => 10, :limit => 10)
|
24
|
+
|
25
|
+
* Added acts_as_nested_set #1000 [wschenk]. Introduction:
|
26
|
+
|
27
|
+
This acts provides Nested Set functionality. Nested Set is similiar to Tree, but with
|
28
|
+
the added feature that you can select the children and all of it's descendants with
|
29
|
+
a single query. A good use case for this is a threaded post system, where you want
|
30
|
+
to display every reply to a comment without multiple selects.
|
31
|
+
|
32
|
+
* Added Base.save! that attempts to save the record just like Base.save but will raise a RecordInvalid exception instead of returning false if the record is not valid [After much pestering from Dave Thomas]
|
33
|
+
|
34
|
+
* Fixed PostgreSQL usage of fixtures with regards to public schemas and table names with dots #962 [gnuman1@gmail.com]
|
35
|
+
|
36
|
+
* Fixed that fixtures were being deleted in the same order as inserts causing FK errors #890 [andrew.john.peters@gmail.com]
|
37
|
+
|
38
|
+
* Fixed loading of fixtures in to be in the right order (or PostgreSQL would bark) #1047 [stephenh@chase3000.com]
|
39
|
+
|
40
|
+
* Fixed page caching for non-vhost applications living underneath the root #1004 [Ben Schumacher]
|
41
|
+
|
42
|
+
* Fixes a problem with the SQL Adapter which was resulting in IDENTITY_INSERT not being set to ON when it should be #1104 [adelle]
|
43
|
+
|
44
|
+
* Added the option to specify the acceptance string in validates_acceptance_of #1106 [caleb@aei-tech.com]
|
45
|
+
|
46
|
+
* Added insert_at(position) to acts_as_list #1083 [DeLynnB]
|
47
|
+
|
48
|
+
* Removed the default order by id on has_and_belongs_to_many queries as it could kill performance on large sets (you can still specify by hand with :order)
|
49
|
+
|
50
|
+
* Fixed that Base.silence should restore the old logger level when done, not just set it to DEBUG #1084 [yon@milliped.com]
|
51
|
+
|
52
|
+
* Fixed boolean saving on Oracle #1093 [mparrish@pearware.org]
|
53
|
+
|
54
|
+
* Moved build_association and create_association for has_one and belongs_to out of deprecation as they work when the association is nil unlike association.build and association.create, which require the association to be already in place #864
|
55
|
+
|
56
|
+
* Added rollbacks of transactions if they're active as the dispatcher is killed gracefully (TERM signal) #1054 [Leon Bredt]
|
57
|
+
|
58
|
+
* Added quoting of column names for fixtures #997 [jcfischer@gmail.com]
|
59
|
+
|
60
|
+
* Fixed counter_sql when no records exist in database for PostgreSQL (would give error, not 0) #1039 [Caleb Tennis]
|
61
|
+
|
62
|
+
* Fixed that benchmarking times for rendering included db runtimes #987 [skaes@web.de]
|
63
|
+
|
64
|
+
* Fixed boolean queries for t/f fields in PostgreSQL #995 [dave@cherryville.org]
|
65
|
+
|
66
|
+
* Added that model.items.delete(child) will delete the child, not just set the foreign key to nil, if the child is dependent on the model #978 [bitsweat]
|
67
|
+
|
68
|
+
* Fixed auto-stamping of dates (created_on/updated_on) for PostgreSQL #985 [dave@cherryville.org]
|
69
|
+
|
70
|
+
* Fixed Base.silence/benchmark to only log if a logger has been configured #986 [skaes@web.de]
|
71
|
+
|
72
|
+
* Added a join parameter as the third argument to Base.find_first and as the second to Base.count #426, #988 [skaes@web.de]
|
73
|
+
|
74
|
+
* Fixed bug in Base#hash method that would treat records with the same string-based id as different [Dave Thomas]
|
75
|
+
|
76
|
+
* Renamed DateHelper#distance_of_time_in_words_to_now to DateHelper#time_ago_in_words (old method name is still available as a deprecated alias)
|
77
|
+
|
78
|
+
|
1
79
|
*1.9.1* (27th March, 2005)
|
2
80
|
|
3
81
|
* Fixed that Active Record objects with float attribute could not be cloned #808
|
data/README
CHANGED
@@ -333,7 +333,7 @@ The prefered method of installing Active Record is through its GEM file. You'll
|
|
333
333
|
RubyGems[http://rubygems.rubyforge.org/wiki/wiki.pl] installed for that, though. If you have,
|
334
334
|
then use:
|
335
335
|
|
336
|
-
% [sudo] gem install activerecord-1.
|
336
|
+
% [sudo] gem install activerecord-1.10.0.gem
|
337
337
|
|
338
338
|
You can also install Active Record the old-fashion way with the following command:
|
339
339
|
|
data/install.rb
CHANGED
@@ -18,48 +18,13 @@ unless $sitedir
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
makedirs = %w{ active_record/associations active_record/connection_adapters active_record/support active_record/vendor active_record/acts }
|
22
|
-
makedirs.each {|f| File::makedirs(File.join($sitedir, *f.split(/\//)))}
|
23
|
-
|
24
|
-
# deprecated files that should be removed
|
25
|
-
# deprecated = %w{ }
|
26
|
-
|
27
|
-
# files to install in library path
|
28
|
-
files = %w-
|
29
|
-
active_record.rb
|
30
|
-
active_record/aggregations.rb
|
31
|
-
active_record/associations.rb
|
32
|
-
active_record/associations/association_collection.rb
|
33
|
-
active_record/associations/has_and_belongs_to_many_association.rb
|
34
|
-
active_record/associations/has_many_association.rb
|
35
|
-
active_record/base.rb
|
36
|
-
active_record/callbacks.rb
|
37
|
-
active_record/connection_adapters/abstract_adapter.rb
|
38
|
-
active_record/connection_adapters/db2_adapter.rb
|
39
|
-
active_record/connection_adapters/mysql_adapter.rb
|
40
|
-
active_record/connection_adapters/oracle_adapter.rb
|
41
|
-
active_record/connection_adapters/postgresql_adapter.rb
|
42
|
-
active_record/connection_adapters/sqlite_adapter.rb
|
43
|
-
active_record/connection_adapters/sqlserver_adapter.rb
|
44
|
-
active_record/deprecated_associations.rb
|
45
|
-
active_record/fixtures.rb
|
46
|
-
active_record/locking.rb
|
47
|
-
active_record/observer.rb
|
48
|
-
active_record/reflection.rb
|
49
|
-
active_record/acts/list.rb
|
50
|
-
active_record/acts/tree.rb
|
51
|
-
active_record/timestamp.rb
|
52
|
-
active_record/transactions.rb
|
53
|
-
active_record/validations.rb
|
54
|
-
active_record/vendor/db2.rb
|
55
|
-
active_record/vendor/mysql.rb
|
56
|
-
active_record/vendor/mysql411.rb
|
57
|
-
active_record/vendor/simple.rb
|
58
|
-
-
|
59
|
-
|
60
21
|
# the acual gruntwork
|
61
22
|
Dir.chdir("lib")
|
62
|
-
|
63
|
-
|
64
|
-
|
23
|
+
|
24
|
+
Find.find("active_record", "active_record.rb") { |f|
|
25
|
+
if f[-3..-1] == ".rb"
|
26
|
+
File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
|
27
|
+
else
|
28
|
+
File::makedirs(File.join($sitedir, *f.split(/\//)))
|
29
|
+
end
|
65
30
|
}
|
data/lib/active_record.rb
CHANGED
@@ -43,6 +43,7 @@ require 'active_record/reflection'
|
|
43
43
|
require 'active_record/timestamp'
|
44
44
|
require 'active_record/acts/list'
|
45
45
|
require 'active_record/acts/tree'
|
46
|
+
require 'active_record/acts/nested_set'
|
46
47
|
require 'active_record/locking'
|
47
48
|
require 'active_record/migration'
|
48
49
|
|
@@ -57,6 +58,7 @@ ActiveRecord::Base.class_eval do
|
|
57
58
|
include ActiveRecord::Reflection
|
58
59
|
include ActiveRecord::Acts::Tree
|
59
60
|
include ActiveRecord::Acts::List
|
61
|
+
include ActiveRecord::Acts::NestedSet
|
60
62
|
end
|
61
63
|
|
62
64
|
require 'active_record/connection_adapters/mysql_adapter'
|
@@ -71,6 +71,10 @@ module ActiveRecord
|
|
71
71
|
# lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return true if that chapter is
|
72
72
|
# the first in the list of all chapters.
|
73
73
|
module InstanceMethods
|
74
|
+
def insert_at(position = 1)
|
75
|
+
position == 1 ? add_to_list_top : insert_at_position(position)
|
76
|
+
end
|
77
|
+
|
74
78
|
def move_lower
|
75
79
|
return unless lower_item
|
76
80
|
|
@@ -107,7 +111,6 @@ module ActiveRecord
|
|
107
111
|
decrement_positions_on_lower_items
|
108
112
|
end
|
109
113
|
|
110
|
-
|
111
114
|
def increment_position
|
112
115
|
update_attribute position_column, self.send(position_column).to_i + 1
|
113
116
|
end
|
@@ -168,24 +171,45 @@ module ActiveRecord
|
|
168
171
|
update_attribute(position_column, 1)
|
169
172
|
end
|
170
173
|
|
174
|
+
# This has the effect of moving all the higher items up one.
|
175
|
+
def decrement_positions_on_higher_items(position)
|
176
|
+
self.class.update_all(
|
177
|
+
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}"
|
178
|
+
)
|
179
|
+
end
|
180
|
+
|
171
181
|
# This has the effect of moving all the lower items up one.
|
172
182
|
def decrement_positions_on_lower_items
|
173
183
|
self.class.update_all(
|
174
|
-
"#{position_column} = (#{position_column} - 1)",
|
184
|
+
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
|
175
185
|
)
|
176
186
|
end
|
177
|
-
|
187
|
+
|
188
|
+
# This has the effect of moving all the higher items down one.
|
178
189
|
def increment_positions_on_higher_items
|
179
190
|
self.class.update_all(
|
180
|
-
"#{position_column} = (#{position_column} + 1)",
|
191
|
+
"#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
|
181
192
|
)
|
182
193
|
end
|
183
194
|
|
195
|
+
# This has the effect of moving all the lower items down one.
|
196
|
+
def increment_positions_on_lower_items(position)
|
197
|
+
self.class.update_all(
|
198
|
+
"#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}"
|
199
|
+
)
|
200
|
+
end
|
201
|
+
|
184
202
|
def increment_positions_on_all_items
|
185
203
|
self.class.update_all(
|
186
204
|
"#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
|
187
205
|
)
|
188
206
|
end
|
207
|
+
|
208
|
+
def insert_at_position(position)
|
209
|
+
remove_from_list
|
210
|
+
increment_positions_on_lower_items(position)
|
211
|
+
self.update_attribute(position_column, position)
|
212
|
+
end
|
189
213
|
end
|
190
214
|
end
|
191
215
|
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Acts #:nodoc:
|
3
|
+
module NestedSet #:nodoc:
|
4
|
+
def self.append_features(base)
|
5
|
+
super
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
# This acts provides Nested Set functionality. Nested Set is similiar to Tree, but with
|
10
|
+
# the added feature that you can select the children and all of it's descendants with
|
11
|
+
# a single query. A good use case for this is a threaded post system, where you want
|
12
|
+
# to display every reply to a comment without multiple selects.
|
13
|
+
#
|
14
|
+
# A google search for "Nested Set" should point you in the direction to explain the
|
15
|
+
# data base theory. I figured a bunch of this from
|
16
|
+
# http://threebit.net/tutorials/nestedset/tutorial1.html
|
17
|
+
#
|
18
|
+
# Instead of picturing a leaf node structure with child pointing back to their parent,
|
19
|
+
# the best way to imagine how this works is to think of the parent entity surrounding all
|
20
|
+
# of it's children, and it's parent surrounding it, etc. Assuming that they are lined up
|
21
|
+
# horizontally, we store the left and right boundries in the database.
|
22
|
+
#
|
23
|
+
# Imagine:
|
24
|
+
# root
|
25
|
+
# |_ Child 1
|
26
|
+
# |_ Child 1.1
|
27
|
+
# |_ Child 1.2
|
28
|
+
# |_ Child 2
|
29
|
+
# |_ Child 2.1
|
30
|
+
# |_ Child 2.2
|
31
|
+
#
|
32
|
+
# If my cirlces in circles description didn't make sense, check out this sweet
|
33
|
+
# ASCII art:
|
34
|
+
#
|
35
|
+
# ___________________________________________________________________
|
36
|
+
# | Root |
|
37
|
+
# | ____________________________ ____________________________ |
|
38
|
+
# | | Child 1 | | Child 2 | |
|
39
|
+
# | | __________ _________ | | __________ _________ | |
|
40
|
+
# | | | C 1.1 | | C 1.2 | | | | C 2.1 | | C 2.2 | | |
|
41
|
+
# 1 2 3_________4 5________6 7 8 9_________10 11_______12 13 14
|
42
|
+
# | |___________________________| |___________________________| |
|
43
|
+
# |___________________________________________________________________|
|
44
|
+
#
|
45
|
+
# The numbers represent the left and right boundries. The table them might
|
46
|
+
# look like this:
|
47
|
+
# ID | PARENT | LEFT | RIGHT | DATA
|
48
|
+
# 1 | 0 | 1 | 14 | root
|
49
|
+
# 2 | 1 | 2 | 7 | Child 1
|
50
|
+
# 3 | 2 | 3 | 4 | Child 1.1
|
51
|
+
# 4 | 2 | 5 | 6 | Child 1.2
|
52
|
+
# 5 | 1 | 8 | 13 | Child 2
|
53
|
+
# 6 | 5 | 9 | 10 | Child 2.1
|
54
|
+
# 7 | 5 | 11 | 12 | Child 2.2
|
55
|
+
#
|
56
|
+
# So, to get all children of an entry, you
|
57
|
+
# SELECT * WHERE CHILD.LEFT IS BETWEEN PARENT.LEFT AND PARENT.RIGHT
|
58
|
+
#
|
59
|
+
# To get the count, it's (LEFT - RIGHT + 1)/2, etc.
|
60
|
+
#
|
61
|
+
# To get the direct parent, it falls back to using the PARENT_ID field.
|
62
|
+
#
|
63
|
+
# There are instance methods for all of these.
|
64
|
+
#
|
65
|
+
# The structure is good if you need to group things together; the downside is that
|
66
|
+
# keeping data integrity is a pain, and both adding and removing and entry
|
67
|
+
# require a full table write.
|
68
|
+
#
|
69
|
+
# This sets up a before_destroy trigger to prune the tree correctly if one of it's
|
70
|
+
# elements gets deleted.
|
71
|
+
#
|
72
|
+
module ClassMethods
|
73
|
+
# Configuration options are:
|
74
|
+
#
|
75
|
+
# * +parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id)
|
76
|
+
# * +left_column+ - column name for left boundry data, default "lft"
|
77
|
+
# * +right_column+ - column name for right boundry data, default "rgt"
|
78
|
+
# * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
|
79
|
+
# (if that hasn't been already) and use that as the foreign key restriction. It's also possible
|
80
|
+
# to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
|
81
|
+
# Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
|
82
|
+
def acts_as_nested_set(options = {})
|
83
|
+
configuration = { :parent_column => "parent_id", :left_column => "lft", :right_column => "rgt", :scope => "1 = 1" }
|
84
|
+
|
85
|
+
configuration.update(options) if options.is_a?(Hash)
|
86
|
+
|
87
|
+
configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
|
88
|
+
|
89
|
+
if configuration[:scope].is_a?(Symbol)
|
90
|
+
scope_condition_method = %(
|
91
|
+
def scope_condition
|
92
|
+
if #{configuration[:scope].to_s}.nil?
|
93
|
+
"#{configuration[:scope].to_s} IS NULL"
|
94
|
+
else
|
95
|
+
"#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
)
|
99
|
+
else
|
100
|
+
scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
|
101
|
+
end
|
102
|
+
|
103
|
+
class_eval <<-EOV
|
104
|
+
include ActiveRecord::Acts::NestedSet::InstanceMethods
|
105
|
+
|
106
|
+
#{scope_condition_method}
|
107
|
+
|
108
|
+
def left_col_name() "#{configuration[:left_column]}" end
|
109
|
+
|
110
|
+
def right_col_name() "#{configuration[:right_column]}" end
|
111
|
+
|
112
|
+
def parent_column() "#{configuration[:parent_column]}" end
|
113
|
+
|
114
|
+
EOV
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
module InstanceMethods
|
119
|
+
# Returns true is this is a root node.
|
120
|
+
def root?
|
121
|
+
parent_id = self[parent_column]
|
122
|
+
(parent_id == 0 || parent_id.nil?) && (self[left_col_name] == 1) && (self[right_col_name] > self[left_col_name])
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns true is this is a child node
|
126
|
+
def child?
|
127
|
+
parent_id = self[parent_column]
|
128
|
+
!(parent_id == 0 || parent_id.nil?) && (self[left_col_name] > 1) && (self[right_col_name] > self[left_col_name])
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns true if we have no idea what this is
|
132
|
+
def unknown?
|
133
|
+
!root? && !child?
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
# Added a child to this object in the tree. If this object hasn't been initialized,
|
138
|
+
# it gets set up as a root node. Otherwise, this method will update all of the
|
139
|
+
# other elements in the tree and shift them to the right. Keeping everything
|
140
|
+
# balanaced.
|
141
|
+
def add_child( child )
|
142
|
+
self.reload
|
143
|
+
child.reload
|
144
|
+
|
145
|
+
if child.root?
|
146
|
+
raise "Adding sub-tree isn\'t currently supported"
|
147
|
+
else
|
148
|
+
if ( (self[left_col_name] == nil) || (self[right_col_name] == nil) )
|
149
|
+
# Looks like we're now the root node! Woo
|
150
|
+
self[left_col_name] = 1
|
151
|
+
self[right_col_name] = 4
|
152
|
+
|
153
|
+
# What do to do about validation?
|
154
|
+
return nil unless self.save
|
155
|
+
|
156
|
+
child[parent_column] = self.id
|
157
|
+
child[left_col_name] = 2
|
158
|
+
child[right_col_name]= 3
|
159
|
+
return child.save
|
160
|
+
else
|
161
|
+
# OK, we need to add and shift everything else to the right
|
162
|
+
child[parent_column] = self.id
|
163
|
+
right_bound = self[right_col_name]
|
164
|
+
child[left_col_name] = right_bound
|
165
|
+
child[right_col_name] = right_bound + 1
|
166
|
+
self[right_col_name] += 2
|
167
|
+
self.class.transaction {
|
168
|
+
self.class.update_all( "#{left_col_name} = (#{left_col_name} + 2)", "#{scope_condition} AND #{left_col_name} >= #{right_bound}" )
|
169
|
+
self.class.update_all( "#{right_col_name} = (#{right_col_name} + 2)", "#{scope_condition} AND #{right_col_name} >= #{right_bound}" )
|
170
|
+
self.save
|
171
|
+
child.save
|
172
|
+
}
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Returns the number of nested children of this object.
|
178
|
+
def children_count
|
179
|
+
return (self[right_col_name] - self[left_col_name] - 1)/2
|
180
|
+
end
|
181
|
+
|
182
|
+
# Returns a set of itself and all of it's nested children
|
183
|
+
def full_set
|
184
|
+
self.class.find_all( "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} and #{self[right_col_name]})" )
|
185
|
+
end
|
186
|
+
|
187
|
+
# Returns a set of all of it's children and nested children
|
188
|
+
def all_children
|
189
|
+
self.class.find_all( "#{scope_condition} AND (#{left_col_name} > #{self[left_col_name]}) and (#{right_col_name} < #{self[right_col_name]})" )
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns a set of only this entries immediate children
|
193
|
+
def direct_children
|
194
|
+
self.class.find_all( "#{scope_condition} and #{parent_column} = #{self.id}")
|
195
|
+
end
|
196
|
+
|
197
|
+
# Prunes a branch off of the tree, shifting all of the elements on the right
|
198
|
+
# back to the left so the counts still work.
|
199
|
+
def before_destroy
|
200
|
+
return if self[right_col_name].nil? || self[left_col_name].nil?
|
201
|
+
dif = self[right_col_name] - self[left_col_name] + 1
|
202
|
+
|
203
|
+
self.class.transaction {
|
204
|
+
self.class.delete_all( "#{scope_condition} and #{left_col_name} > #{self[left_col_name]} and #{right_col_name} < #{self[right_col_name]}" )
|
205
|
+
self.class.update_all( "#{left_col_name} = (#{left_col_name} - #{dif})", "#{scope_condition} AND #{left_col_name} >= #{self[right_col_name]}" )
|
206
|
+
self.class.update_all( "#{right_col_name} = (#{right_col_name} - #{dif} )", "#{scope_condition} AND #{right_col_name} >= #{self[right_col_name]}" )
|
207
|
+
}
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -19,7 +19,7 @@ module ActiveRecord
|
|
19
19
|
instance_variable_set "@#{assoc.name}", nil
|
20
20
|
end
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
# Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
|
24
24
|
# "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
|
25
25
|
# specialized according to the collection or association symbol and the options hash. It works much the same was as Ruby's own attr*
|
@@ -108,6 +108,41 @@ module ActiveRecord
|
|
108
108
|
# project.milestones(true).size # fetches milestones from the database
|
109
109
|
# project.milestones # uses the milestone cache
|
110
110
|
#
|
111
|
+
# == Eager loading of associations
|
112
|
+
#
|
113
|
+
# Eager loading is a way to find objects of a certain class and a number of named associations along with it in a single SQL call. This is
|
114
|
+
# one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each needs to display their author
|
115
|
+
# triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 1. Example:
|
116
|
+
#
|
117
|
+
# class Post < ActiveRecord::Base
|
118
|
+
# belongs_to :author
|
119
|
+
# has_many :comments
|
120
|
+
# end
|
121
|
+
#
|
122
|
+
# Consider the following loop using the class above:
|
123
|
+
#
|
124
|
+
# for post in Post.find(:all, :limit => 100)
|
125
|
+
# puts "Post: " + post.title
|
126
|
+
# puts "Written by: " + post.author.name
|
127
|
+
# puts "Last comment on: " + post.comments.first.created_on
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# To iterate over these one hundred posts, we'll generate 201 database queries. Let's first just optimize it for retrieving the author:
|
131
|
+
#
|
132
|
+
# for post in Post.find(:all, :limit => 100, :include => :author)
|
133
|
+
#
|
134
|
+
# This references the name of the belongs_to association that also used the :author symbol, so the find will now weave in a join something
|
135
|
+
# like this: LEFT OUTER JOIN authors ON authors.id = posts.author_id. Doing so will cut down the number of queries from 201 to 101.
|
136
|
+
#
|
137
|
+
# We can improve upon the situation further by referencing both associations in the finder with:
|
138
|
+
#
|
139
|
+
# for post in Post.find(:all, :limit => 100, :include => [ :author, :comments ])
|
140
|
+
#
|
141
|
+
# That'll add another join along the lines of: LEFT OUTER JOIN comments ON comments.post_id = posts.id. And we'll be down to 1 query.
|
142
|
+
# But that shouldn't fool you to think that you can pull out huge amounts of data with no performance penalty just because you've reduced
|
143
|
+
# the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So its no
|
144
|
+
# catch-all for performance problems, but its a great way to cut down on the number of queries in a situation as the one described above.
|
145
|
+
#
|
111
146
|
# == Modules
|
112
147
|
#
|
113
148
|
# By default, associations will look for objects within the current module scope. Consider:
|
@@ -153,28 +188,27 @@ module ActiveRecord
|
|
153
188
|
# * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
|
154
189
|
# An empty array is returned if none are found.
|
155
190
|
# * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
|
156
|
-
# * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL.
|
191
|
+
# * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL.
|
192
|
+
# This will also destroy the objects if they're declared as belongs_to and dependent on this model.
|
157
193
|
# * <tt>collection.clear</tt> - removes every object from the collection. This does not destroy the objects.
|
158
194
|
# * <tt>collection.empty?</tt> - returns true if there are no associated objects.
|
159
195
|
# * <tt>collection.size</tt> - returns the number of associated objects.
|
160
|
-
# * <tt>collection.find
|
161
|
-
# meets the condition that it has to be associated with this object.
|
162
|
-
# * <tt>collection.find_all(conditions = nil, orderings = nil, limit = nil, joins = nil)</tt> - finds all associated objects responding
|
163
|
-
# criteria mentioned (like in the standard find_all) and that meets the condition that it has to be associated with this object.
|
196
|
+
# * <tt>collection.find</tt> - finds an associated object according to the same rules as Base.find.
|
164
197
|
# * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
|
165
|
-
# with +attributes+ and linked to this object through a foreign key but has not yet been saved.
|
198
|
+
# with +attributes+ and linked to this object through a foreign key but has not yet been saved. *Note:* This only works if an
|
199
|
+
# associated object already exists, not if its nil!
|
166
200
|
# * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
|
167
201
|
# with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
|
202
|
+
# *Note:* This only works if an associated object already exists, not if its nil!
|
168
203
|
#
|
169
204
|
# Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
|
170
|
-
# * <tt>Firm#clients</tt> (similar to <tt>Clients.
|
205
|
+
# * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>)
|
171
206
|
# * <tt>Firm#clients<<</tt>
|
172
207
|
# * <tt>Firm#clients.delete</tt>
|
173
208
|
# * <tt>Firm#clients.clear</tt>
|
174
209
|
# * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
|
175
210
|
# * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
|
176
|
-
# * <tt>Firm#clients.find</tt> (similar to <tt>Client.
|
177
|
-
# * <tt>Firm#clients.find_all</tt> (similar to <tt>Client.find_all "firm_id = #{id}"</tt>)
|
211
|
+
# * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>)
|
178
212
|
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
|
179
213
|
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("client_id" => id); c.save; c</tt>)
|
180
214
|
# The declaration can also include an options hash to specialize the behavior of the association.
|
@@ -219,6 +253,8 @@ module ActiveRecord
|
|
219
253
|
|
220
254
|
if options[:dependent] and options[:exclusively_dependent]
|
221
255
|
raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options. You may specify one or the other.' # ' ruby-mode
|
256
|
+
# See HasManyAssociation#delete_records. Dependent associations
|
257
|
+
# delete children, otherwise foreign key is set to NULL.
|
222
258
|
elsif options[:dependent]
|
223
259
|
module_eval "before_destroy '#{association_name}.each { |o| o.destroy }'"
|
224
260
|
elsif options[:exclusively_dependent]
|
@@ -247,19 +283,19 @@ module ActiveRecord
|
|
247
283
|
# * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key,
|
248
284
|
# and saves the associate object.
|
249
285
|
# * <tt>association.nil?</tt> - returns true if there is no associated object.
|
250
|
-
# * <tt>
|
286
|
+
# * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
|
251
287
|
# with +attributes+ and linked to this object through a foreign key but has not yet been saved. Note: This ONLY works if
|
252
288
|
# an association already exists. It will NOT work if the association is nil.
|
253
|
-
# * <tt>
|
289
|
+
# * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
|
254
290
|
# with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
|
255
|
-
# Note: This ONLY works if an association already exists. It will NOT work if the association is nil.
|
256
291
|
#
|
257
292
|
# Example: An Account class declares <tt>has_one :beneficiary</tt>, which will add:
|
258
293
|
# * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find_first "account_id = #{id}"</tt>)
|
259
294
|
# * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
|
260
295
|
# * <tt>Account#beneficiary.nil?</tt>
|
261
|
-
# * <tt>Account#
|
262
|
-
# * <tt>Account#
|
296
|
+
# * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
|
297
|
+
# * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
|
298
|
+
#
|
263
299
|
# The declaration can also include an options hash to specialize the behavior of the association.
|
264
300
|
#
|
265
301
|
# Options are:
|
@@ -300,13 +336,13 @@ module ActiveRecord
|
|
300
336
|
end
|
301
337
|
|
302
338
|
association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasOneAssociation)
|
339
|
+
association_constructor_method(:build, association_name, association_class_name, association_class_primary_key_name, options, HasOneAssociation)
|
340
|
+
association_constructor_method(:create, association_name, association_class_name, association_class_primary_key_name, options, HasOneAssociation)
|
303
341
|
|
304
342
|
module_eval "before_destroy '#{association_name}.destroy unless #{association_name}.nil?'" if options[:dependent]
|
305
343
|
|
306
344
|
# deprecated api
|
307
345
|
deprecated_has_association_method(association_name)
|
308
|
-
deprecated_build_method("build_", association_name, association_class_name, association_class_primary_key_name)
|
309
|
-
deprecated_create_method("create_", association_name, association_class_name, association_class_primary_key_name)
|
310
346
|
deprecated_association_comparison_method(association_name, association_class_name)
|
311
347
|
end
|
312
348
|
|
@@ -316,9 +352,9 @@ module ActiveRecord
|
|
316
352
|
# * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
|
317
353
|
# * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, and sets it as the foreign key.
|
318
354
|
# * <tt>association.nil?</tt> - returns true if there is no associated object.
|
319
|
-
# * <tt>
|
355
|
+
# * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
|
320
356
|
# with +attributes+ and linked to this object through a foreign key but has not yet been saved.
|
321
|
-
# * <tt>
|
357
|
+
# * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
|
322
358
|
# with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
|
323
359
|
#
|
324
360
|
# Example: A Post class declares <tt>belongs_to :author</tt>, which will add:
|
@@ -326,8 +362,8 @@ module ActiveRecord
|
|
326
362
|
# * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
|
327
363
|
# * <tt>Post#author?</tt> (similar to <tt>post.author == some_author</tt>)
|
328
364
|
# * <tt>Post#author.nil?</tt>
|
329
|
-
# * <tt>Post#
|
330
|
-
# * <tt>Post#
|
365
|
+
# * <tt>Post#build_author</tt> (similar to <tt>Author.new("post_id" => id)</tt>)
|
366
|
+
# * <tt>Post#create_author</tt> (similar to <tt>b = Author.new("post_id" => id); b.save; b</tt>)
|
331
367
|
# The declaration can also include an options hash to specialize the behavior of the association.
|
332
368
|
#
|
333
369
|
# Options are:
|
@@ -362,6 +398,8 @@ module ActiveRecord
|
|
362
398
|
association_class_primary_key_name = options[:foreign_key] || Inflector.underscore(Inflector.demodulize(association_class_name)) + "_id"
|
363
399
|
|
364
400
|
association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, BelongsToAssociation)
|
401
|
+
association_constructor_method(:build, association_name, association_class_name, association_class_primary_key_name, options, BelongsToAssociation)
|
402
|
+
association_constructor_method(:create, association_name, association_class_name, association_class_primary_key_name, options, BelongsToAssociation)
|
365
403
|
|
366
404
|
module_eval do
|
367
405
|
before_save <<-EOF
|
@@ -548,6 +586,15 @@ module ActiveRecord
|
|
548
586
|
end
|
549
587
|
association
|
550
588
|
end
|
589
|
+
|
590
|
+
define_method("set_#{association_name}_target") do |target|
|
591
|
+
return if target.nil?
|
592
|
+
association = association_proxy_class.new(self,
|
593
|
+
association_name, association_class_name,
|
594
|
+
association_class_primary_key_name, options)
|
595
|
+
association.target = target
|
596
|
+
instance_variable_set("@#{association_name}", association)
|
597
|
+
end
|
551
598
|
end
|
552
599
|
|
553
600
|
def collection_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, association_proxy_class)
|
@@ -614,6 +661,141 @@ module ActiveRecord
|
|
614
661
|
end_eval
|
615
662
|
end
|
616
663
|
end
|
664
|
+
|
665
|
+
def association_constructor_method(constructor, association_name, association_class_name, association_class_primary_key_name, options, association_proxy_class)
|
666
|
+
define_method("#{constructor}_#{association_name}") do |*params|
|
667
|
+
attributees = params.first unless params.empty?
|
668
|
+
association = instance_variable_get("@#{association_name}")
|
669
|
+
|
670
|
+
if association.nil?
|
671
|
+
association = association_proxy_class.new(self,
|
672
|
+
association_name, association_class_name,
|
673
|
+
association_class_primary_key_name, options)
|
674
|
+
instance_variable_set("@#{association_name}", association)
|
675
|
+
end
|
676
|
+
|
677
|
+
association.send(constructor, attributees)
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
def find_with_associations(options = {})
|
682
|
+
reflections = reflect_on_included_associations(options[:include])
|
683
|
+
schema_abbreviations = generate_schema_abbreviations(reflections)
|
684
|
+
primary_key_table = generate_primary_key_table(reflections, schema_abbreviations)
|
685
|
+
|
686
|
+
rows = select_all_rows(options, schema_abbreviations, reflections)
|
687
|
+
records = { }
|
688
|
+
primary_key = primary_key_table[table_name]
|
689
|
+
|
690
|
+
for row in rows
|
691
|
+
id = row[primary_key]
|
692
|
+
records[id] ||= instantiate(extract_record(schema_abbreviations, table_name, row))
|
693
|
+
|
694
|
+
reflections.each do |reflection|
|
695
|
+
next unless row[primary_key_table[reflection.table_name]]
|
696
|
+
|
697
|
+
case reflection.macro
|
698
|
+
when :has_many, :has_and_belongs_to_many
|
699
|
+
records[id].send(reflection.name)
|
700
|
+
records[id].instance_variable_get("@#{reflection.name}").target.push(
|
701
|
+
reflection.klass.send(:instantiate, extract_record(schema_abbreviations, reflection.table_name, row))
|
702
|
+
)
|
703
|
+
when :has_one, :belongs_to
|
704
|
+
records[id].send(
|
705
|
+
"#{reflection.name}=",
|
706
|
+
reflection.klass.send(:instantiate, extract_record(schema_abbreviations, reflection.table_name, row))
|
707
|
+
)
|
708
|
+
end
|
709
|
+
end
|
710
|
+
end
|
711
|
+
|
712
|
+
return records.values
|
713
|
+
end
|
714
|
+
|
715
|
+
def reflect_on_included_associations(associations)
|
716
|
+
[ associations ].flatten.collect { |association| reflect_on_association(association) }
|
717
|
+
end
|
718
|
+
|
719
|
+
def generate_schema_abbreviations(reflections)
|
720
|
+
schema = [ [ table_name, column_names ] ]
|
721
|
+
schema += reflections.collect { |r| [ r.table_name, r.klass.column_names ] }
|
722
|
+
|
723
|
+
schema_abbreviations = {}
|
724
|
+
schema.each_with_index do |table_and_columns, i|
|
725
|
+
table, columns = table_and_columns
|
726
|
+
columns.each_with_index { |column, j| schema_abbreviations["t#{i}_r#{j}"] = [ table, column ] }
|
727
|
+
end
|
728
|
+
|
729
|
+
return schema_abbreviations
|
730
|
+
end
|
731
|
+
|
732
|
+
def generate_primary_key_table(reflections, schema_abbreviations)
|
733
|
+
primary_key_lookup_table = {}
|
734
|
+
primary_key_lookup_table[table_name] =
|
735
|
+
schema_abbreviations.find { |cn, tc| tc == [ table_name, primary_key ] }.first
|
736
|
+
|
737
|
+
reflections.collect do |reflection|
|
738
|
+
primary_key_lookup_table[reflection.klass.table_name] = schema_abbreviations.find { |cn, tc|
|
739
|
+
tc == [ reflection.klass.table_name, reflection.klass.primary_key ]
|
740
|
+
}.first
|
741
|
+
end
|
742
|
+
|
743
|
+
return primary_key_lookup_table
|
744
|
+
end
|
745
|
+
|
746
|
+
|
747
|
+
def select_all_rows(options, schema_abbreviations, reflections)
|
748
|
+
connection.select_all(
|
749
|
+
construct_finder_sql_with_included_associations(options, schema_abbreviations, reflections),
|
750
|
+
"#{name} Load Including Associations"
|
751
|
+
)
|
752
|
+
end
|
753
|
+
|
754
|
+
def construct_finder_sql_with_included_associations(options, schema_abbreviations, reflections)
|
755
|
+
sql = "SELECT #{column_aliases(schema_abbreviations)} FROM #{table_name} "
|
756
|
+
sql << reflections.collect { |reflection| association_join(reflection) }.to_s
|
757
|
+
sql << "#{options[:joins]} " if options[:joins]
|
758
|
+
add_conditions!(sql, options[:conditions])
|
759
|
+
sql << "ORDER BY #{options[:order]} " if options[:order]
|
760
|
+
|
761
|
+
return sanitize_sql(sql)
|
762
|
+
end
|
763
|
+
|
764
|
+
def column_aliases(schema_abbreviations)
|
765
|
+
schema_abbreviations.collect { |cn, tc| "#{tc.join(".")} AS #{cn}" }.join(", ")
|
766
|
+
end
|
767
|
+
|
768
|
+
def association_join(reflection)
|
769
|
+
case reflection.macro
|
770
|
+
when :has_and_belongs_to_many
|
771
|
+
" LEFT OUTER JOIN #{reflection.options[:join_table]} ON " +
|
772
|
+
"#{reflection.options[:join_table]}.#{reflection.options[:foreign_key] || table_name.classify.foreign_key} = " +
|
773
|
+
"#{table_name}.#{primary_key} " +
|
774
|
+
" LEFT OUTER JOIN #{reflection.klass.table_name} ON " +
|
775
|
+
"#{reflection.options[:join_table]}.#{reflection.options[:associated_foreign_key] || reflection.klass.table_name.classify.foreign_key} = " +
|
776
|
+
"#{reflection.klass.table_name}.#{reflection.klass.primary_key} "
|
777
|
+
when :has_many, :has_one
|
778
|
+
" LEFT OUTER JOIN #{reflection.klass.table_name} ON " +
|
779
|
+
"#{reflection.klass.table_name}.#{reflection.options[:foreign_key] || table_name.classify.foreign_key} = " +
|
780
|
+
"#{table_name}.#{primary_key} "
|
781
|
+
when :belongs_to
|
782
|
+
" LEFT OUTER JOIN #{reflection.klass.table_name} ON " +
|
783
|
+
"#{reflection.klass.table_name}.#{reflection.klass.primary_key} = " +
|
784
|
+
"#{table_name}.#{reflection.options[:foreign_key] || reflection.klass.table_name.classify.foreign_key} "
|
785
|
+
else
|
786
|
+
""
|
787
|
+
end
|
788
|
+
end
|
789
|
+
|
790
|
+
|
791
|
+
def extract_record(schema_abbreviations, table_name, row)
|
792
|
+
record = {}
|
793
|
+
row.each do |column, value|
|
794
|
+
prefix, column_name = schema_abbreviations[column]
|
795
|
+
record[column_name] = value if prefix == table_name
|
796
|
+
end
|
797
|
+
return record
|
798
|
+
end
|
617
799
|
end
|
618
800
|
end
|
619
801
|
end
|