datamapper 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +2 -0
- data/MIT-LICENSE +22 -0
- data/README +1 -0
- data/example.rb +25 -0
- data/lib/data_mapper.rb +30 -0
- data/lib/data_mapper/adapters/abstract_adapter.rb +229 -0
- data/lib/data_mapper/adapters/mysql_adapter.rb +171 -0
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +189 -0
- data/lib/data_mapper/associations.rb +19 -0
- data/lib/data_mapper/associations/belongs_to_association.rb +111 -0
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +100 -0
- data/lib/data_mapper/associations/has_many_association.rb +101 -0
- data/lib/data_mapper/associations/has_one_association.rb +107 -0
- data/lib/data_mapper/base.rb +160 -0
- data/lib/data_mapper/callbacks.rb +47 -0
- data/lib/data_mapper/database.rb +134 -0
- data/lib/data_mapper/extensions/active_record_impersonation.rb +69 -0
- data/lib/data_mapper/extensions/callback_helpers.rb +35 -0
- data/lib/data_mapper/identity_map.rb +21 -0
- data/lib/data_mapper/loaded_set.rb +45 -0
- data/lib/data_mapper/mappings/column.rb +78 -0
- data/lib/data_mapper/mappings/schema.rb +28 -0
- data/lib/data_mapper/mappings/table.rb +99 -0
- data/lib/data_mapper/queries/conditions.rb +141 -0
- data/lib/data_mapper/queries/connection.rb +34 -0
- data/lib/data_mapper/queries/create_table_statement.rb +38 -0
- data/lib/data_mapper/queries/delete_statement.rb +17 -0
- data/lib/data_mapper/queries/drop_table_statement.rb +17 -0
- data/lib/data_mapper/queries/insert_statement.rb +29 -0
- data/lib/data_mapper/queries/reader.rb +42 -0
- data/lib/data_mapper/queries/result.rb +19 -0
- data/lib/data_mapper/queries/select_statement.rb +103 -0
- data/lib/data_mapper/queries/table_exists_statement.rb +17 -0
- data/lib/data_mapper/queries/truncate_table_statement.rb +17 -0
- data/lib/data_mapper/queries/update_statement.rb +25 -0
- data/lib/data_mapper/session.rb +240 -0
- data/lib/data_mapper/support/blank_slate.rb +3 -0
- data/lib/data_mapper/support/connection_pool.rb +117 -0
- data/lib/data_mapper/support/enumerable.rb +27 -0
- data/lib/data_mapper/support/inflector.rb +329 -0
- data/lib/data_mapper/support/proc.rb +69 -0
- data/lib/data_mapper/support/string.rb +23 -0
- data/lib/data_mapper/support/symbol.rb +91 -0
- data/lib/data_mapper/support/weak_hash.rb +46 -0
- data/lib/data_mapper/unit_of_work.rb +38 -0
- data/lib/data_mapper/validations/confirmation_validator.rb +55 -0
- data/lib/data_mapper/validations/contextual_validations.rb +50 -0
- data/lib/data_mapper/validations/format_validator.rb +85 -0
- data/lib/data_mapper/validations/formats/email.rb +78 -0
- data/lib/data_mapper/validations/generic_validator.rb +27 -0
- data/lib/data_mapper/validations/length_validator.rb +75 -0
- data/lib/data_mapper/validations/required_field_validator.rb +47 -0
- data/lib/data_mapper/validations/unique_validator.rb +65 -0
- data/lib/data_mapper/validations/validation_errors.rb +34 -0
- data/lib/data_mapper/validations/validation_helper.rb +60 -0
- data/performance.rb +156 -0
- data/profile_data_mapper.rb +18 -0
- data/rakefile.rb +80 -0
- data/spec/basic_finder.rb +67 -0
- data/spec/belongs_to.rb +47 -0
- data/spec/fixtures/animals.yaml +32 -0
- data/spec/fixtures/exhibits.yaml +90 -0
- data/spec/fixtures/fruit.yaml +6 -0
- data/spec/fixtures/people.yaml +15 -0
- data/spec/fixtures/zoos.yaml +20 -0
- data/spec/has_and_belongs_to_many.rb +25 -0
- data/spec/has_many.rb +34 -0
- data/spec/legacy.rb +14 -0
- data/spec/models/animal.rb +7 -0
- data/spec/models/exhibit.rb +6 -0
- data/spec/models/fruit.rb +6 -0
- data/spec/models/person.rb +7 -0
- data/spec/models/post.rb +4 -0
- data/spec/models/sales_person.rb +4 -0
- data/spec/models/zoo.rb +5 -0
- data/spec/new_record.rb +24 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/sub_select.rb +16 -0
- data/spec/symbolic_operators.rb +21 -0
- data/spec/validates_confirmation_of.rb +36 -0
- data/spec/validates_format_of.rb +61 -0
- data/spec/validates_length_of.rb +101 -0
- data/spec/validates_uniqueness_of.rb +45 -0
- data/spec/validations.rb +63 -0
- metadata +134 -0
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'data_mapper/adapters/abstract_adapter'
|
2
|
+
require 'data_mapper/support/connection_pool'
|
3
|
+
|
4
|
+
require 'sqlite3'
|
5
|
+
|
6
|
+
module DataMapper
|
7
|
+
module Adapters
|
8
|
+
|
9
|
+
class Sqlite3Adapter < AbstractAdapter
|
10
|
+
|
11
|
+
def initialize(configuration)
|
12
|
+
super
|
13
|
+
@connections = Support::ConnectionPool.new { Queries::Connection.new(@configuration) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def connection
|
17
|
+
raise ArgumentError.new('Sqlite3Adapter#connection requires a block-parameter') unless block_given?
|
18
|
+
begin
|
19
|
+
@connections.hold { |connection| yield connection }
|
20
|
+
rescue SQLite3::Exception => sle
|
21
|
+
|
22
|
+
@configuration.log.fatal(sle)
|
23
|
+
|
24
|
+
@connections.available_connections.each do |sock|
|
25
|
+
begin
|
26
|
+
sock.close
|
27
|
+
rescue => se
|
28
|
+
@configuration.log.error(se)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@connections.available_connections.clear
|
33
|
+
raise sle
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
TYPES.merge!({
|
38
|
+
:integer => 'INTEGER'.freeze,
|
39
|
+
:string => 'TEXT'.freeze,
|
40
|
+
:text => 'TEXT'.freeze,
|
41
|
+
:class => 'TEXT'.freeze
|
42
|
+
})
|
43
|
+
|
44
|
+
module Coersion
|
45
|
+
|
46
|
+
def type_cast_boolean(value)
|
47
|
+
case value
|
48
|
+
when TrueClass, FalseClass then value
|
49
|
+
when "1", "true", "TRUE" then true
|
50
|
+
when "0", nil then false
|
51
|
+
else "Can't type-cast #{value.inspect} to a boolean"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def type_cast_datetime(value)
|
56
|
+
case value
|
57
|
+
when DateTime then value
|
58
|
+
when Date then DateTime.new(value)
|
59
|
+
when String then DateTime::parse(value)
|
60
|
+
else "Can't type-cast #{value.inspect} to a datetime"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end # module Coersion
|
65
|
+
|
66
|
+
module Queries
|
67
|
+
|
68
|
+
class Connection
|
69
|
+
|
70
|
+
def initialize(database)
|
71
|
+
@database = database
|
72
|
+
@dbh = SQLite3::Database.new(database.database)
|
73
|
+
database.log.debug("Initializing Connection for Database[#{database.name}]")
|
74
|
+
super(database.log)
|
75
|
+
end
|
76
|
+
|
77
|
+
def execute(statement)
|
78
|
+
send_query(statement)
|
79
|
+
Result.new(@dbh.total_changes, @dbh.last_insert_row_id)
|
80
|
+
end
|
81
|
+
|
82
|
+
def query(statement)
|
83
|
+
Reader.new(send_query(statement))
|
84
|
+
end
|
85
|
+
|
86
|
+
def close
|
87
|
+
@dbh.close
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
def send_query(statement)
|
92
|
+
sql = statement.respond_to?(:to_sql) ? statement.to_sql : statement
|
93
|
+
log.debug("Database[#{@database.name}] => #{sql}")
|
94
|
+
@dbh.query(sql)
|
95
|
+
end
|
96
|
+
|
97
|
+
end # class Connection
|
98
|
+
|
99
|
+
class Reader
|
100
|
+
|
101
|
+
include Enumerable
|
102
|
+
|
103
|
+
def initialize(results)
|
104
|
+
@results = results
|
105
|
+
@columns = {}
|
106
|
+
@results.columns.each_with_index do |name, index|
|
107
|
+
@columns[name] = index
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def eof?
|
112
|
+
@results.eof?
|
113
|
+
end
|
114
|
+
|
115
|
+
def records_affected
|
116
|
+
@results.entries.size
|
117
|
+
end
|
118
|
+
|
119
|
+
def next
|
120
|
+
@current_row = @results.next
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
def each
|
125
|
+
until self.next.eof?
|
126
|
+
yield(self)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def [](column)
|
131
|
+
index = @columns[column]
|
132
|
+
return nil if index.nil? || @current_row.nil?
|
133
|
+
@current_row[index]
|
134
|
+
end
|
135
|
+
|
136
|
+
def each_pair
|
137
|
+
@columns.each_pair do |column_name, index|
|
138
|
+
yield(column_name, @current_row.nil? ? nil : @current_row[index])
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def close
|
143
|
+
@results.close
|
144
|
+
end
|
145
|
+
|
146
|
+
end # class Reader
|
147
|
+
|
148
|
+
class TableExistsStatement
|
149
|
+
def to_sql
|
150
|
+
"SELECT name FROM sqlite_master WHERE type = \"table\" AND name = #{@database.quote_value(@database[@klass].name)}"
|
151
|
+
end
|
152
|
+
end # class TableExistsStatement
|
153
|
+
|
154
|
+
class CreateTableStatement
|
155
|
+
def to_sql
|
156
|
+
table = @database[@klass]
|
157
|
+
|
158
|
+
sql = "CREATE TABLE " << table.to_sql
|
159
|
+
|
160
|
+
sql << " (" << table.columns.map do |column|
|
161
|
+
column_long_form(column)
|
162
|
+
end.join(', ') << ")"
|
163
|
+
|
164
|
+
return sql
|
165
|
+
end
|
166
|
+
|
167
|
+
def column_long_form(column)
|
168
|
+
long_form = "#{column.to_sql} #{@database.adapter.class::TYPES[column.type] || column.type}"
|
169
|
+
|
170
|
+
long_form << " NOT NULL" unless column.nullable?
|
171
|
+
long_form << " PRIMARY KEY" if column.key?
|
172
|
+
long_form << " default #{column.options[:default]}" if column.options.has_key?(:default)
|
173
|
+
|
174
|
+
return long_form
|
175
|
+
end
|
176
|
+
end # class CreateTableStatement
|
177
|
+
|
178
|
+
class TruncateTableStatement
|
179
|
+
def to_sql
|
180
|
+
"DELETE FROM " << @database[@klass].to_sql
|
181
|
+
end
|
182
|
+
end # class TruncateTableStatement
|
183
|
+
|
184
|
+
end # module Queries
|
185
|
+
|
186
|
+
end # class Sqlite3Adapter
|
187
|
+
|
188
|
+
end # module Adapters
|
189
|
+
end # module DataMapper
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'data_mapper/associations/has_many_association'
|
2
|
+
require 'data_mapper/associations/belongs_to_association'
|
3
|
+
require 'data_mapper/associations/has_one_association'
|
4
|
+
require 'data_mapper/associations/has_and_belongs_to_many_association'
|
5
|
+
|
6
|
+
module DataMapper
|
7
|
+
module Associations
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.class_eval do
|
11
|
+
include DataMapper::Associations::HasMany
|
12
|
+
include DataMapper::Associations::BelongsTo
|
13
|
+
include DataMapper::Associations::HasOne
|
14
|
+
include DataMapper::Associations::HasAndBelongsToMany
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Associations
|
3
|
+
|
4
|
+
class BelongsToAssociation
|
5
|
+
|
6
|
+
def initialize(instance, association_name, options)
|
7
|
+
@instance = instance
|
8
|
+
@association_name = association_name
|
9
|
+
@options = options
|
10
|
+
|
11
|
+
@associated_class = if options.has_key?(:class) || options.has_key?(:class_name)
|
12
|
+
associated_class_name = (options[:class] || options[:class_name])
|
13
|
+
if associated_class_name.kind_of?(String)
|
14
|
+
Kernel.const_get(Inflector.classify(associated_class_name))
|
15
|
+
else
|
16
|
+
associated_class_name
|
17
|
+
end
|
18
|
+
else
|
19
|
+
Kernel.const_get(Inflector.classify(association_name))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.setup(klass, association_name, options)
|
24
|
+
foreign_key = options[:foreign_key] || ("#{association_name}_id")
|
25
|
+
klass.property foreign_key.to_sym, :integer
|
26
|
+
|
27
|
+
# Define the association instance method (i.e. Exhibit#zoo)
|
28
|
+
klass.class_eval <<-EOS
|
29
|
+
def create_#{association_name}(options = {})
|
30
|
+
#{association_name}_association.create(options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_#{association_name}(options = {})
|
34
|
+
#{association_name}_association.build(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
def #{association_name}
|
38
|
+
# Let the BelongsToAssociation do the finding, just to keep things neat around here...
|
39
|
+
#{association_name}_association.find
|
40
|
+
end
|
41
|
+
|
42
|
+
def #{association_name}=(value)
|
43
|
+
#{association_name}_association.set(value)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def #{association_name}_association
|
48
|
+
@#{association_name} || (@#{association_name} = BelongsToAssociation.new(self, "#{association_name}", #{options.inspect}))
|
49
|
+
end
|
50
|
+
EOS
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
def find
|
55
|
+
return @result unless @result.nil?
|
56
|
+
|
57
|
+
unless @instance.loaded_set.nil?
|
58
|
+
|
59
|
+
# Temp variable for the instance variable name.
|
60
|
+
setter_method = "#{@association_name}=".to_sym
|
61
|
+
instance_variable_name = "@#{foreign_key}".to_sym
|
62
|
+
|
63
|
+
set = @instance.loaded_set.instances.group_by { |instance| instance.instance_variable_get(instance_variable_name) }
|
64
|
+
|
65
|
+
# Fetch the foreign objects for all instances in the current object's loaded-set.
|
66
|
+
@instance.session.find(@associated_class, :all, :id => set.keys).each do |owner|
|
67
|
+
set[owner.key].each do |instance|
|
68
|
+
instance.send(setter_method, owner)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
return @result
|
74
|
+
end
|
75
|
+
|
76
|
+
def create(options = {})
|
77
|
+
associated = @associated_class.new(options)
|
78
|
+
if associated.save
|
79
|
+
@instance.send("#{@associated_class.foreign_key}=", associated.id)
|
80
|
+
@result = associated
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def build(options = {})
|
85
|
+
@result = @associated_class.new(options)
|
86
|
+
end
|
87
|
+
|
88
|
+
def set(val)
|
89
|
+
@result = val
|
90
|
+
end
|
91
|
+
|
92
|
+
def foreign_key
|
93
|
+
@foreign_key ||= (@options[:foreign_key] || @instance.session.schema[@associated_class].default_foreign_key)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
module BelongsTo
|
99
|
+
def self.included(base)
|
100
|
+
base.extend(ClassMethods)
|
101
|
+
end
|
102
|
+
|
103
|
+
module ClassMethods
|
104
|
+
def belongs_to(association_name, options = {})
|
105
|
+
BelongsToAssociation.setup(self, association_name, options)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Associations
|
3
|
+
|
4
|
+
class HasAndBelongsToManyAssociation
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(instance, association_name, options)
|
8
|
+
@instance = instance
|
9
|
+
@association_name = association_name.to_sym
|
10
|
+
@options = options
|
11
|
+
|
12
|
+
@associated_class = if options.has_key?(:class) || options.has_key?(:class_name)
|
13
|
+
associated_class_name = (options[:class] || options[:class_name])
|
14
|
+
if associated_class_name.kind_of?(String)
|
15
|
+
Kernel.const_get(Inflector.classify(associated_class_name))
|
16
|
+
else
|
17
|
+
associated_class_name
|
18
|
+
end
|
19
|
+
else
|
20
|
+
Kernel.const_get(Inflector.classify(association_name))
|
21
|
+
end
|
22
|
+
|
23
|
+
@join_table_name = @options.has_key?(:join_table_name) ? @options[:join_table_name] : [Inflector.tableize(@instance.class.name), Inflector.tableize(@associated_class.name)].sort.join('_')
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.setup(klass, association_name, options)
|
27
|
+
|
28
|
+
# Define the association instance method (i.e. Project#tasks)
|
29
|
+
klass.class_eval <<-EOS
|
30
|
+
def #{association_name}
|
31
|
+
@#{association_name} || (@#{association_name} = HasAndBelongsToManyAssociation.new(self, "#{association_name}", #{options.inspect}))
|
32
|
+
end
|
33
|
+
EOS
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def each
|
38
|
+
find.each { |item| yield item }
|
39
|
+
end
|
40
|
+
|
41
|
+
def size
|
42
|
+
entries.size
|
43
|
+
end
|
44
|
+
alias length size
|
45
|
+
|
46
|
+
def [](key)
|
47
|
+
entries[key]
|
48
|
+
end
|
49
|
+
|
50
|
+
def empty?
|
51
|
+
entries.empty?
|
52
|
+
end
|
53
|
+
|
54
|
+
def find
|
55
|
+
return @results unless @results.nil?
|
56
|
+
|
57
|
+
unless @instance.loaded_set.nil?
|
58
|
+
|
59
|
+
# Temp variable for the instance variable name.
|
60
|
+
instance_variable_name = "@#{foreign_key}".to_sym
|
61
|
+
|
62
|
+
@results = @instance.session.find(@associated_class, :all,
|
63
|
+
:id.select => { :table => @join_table_name, foreign_key.to_sym => @instance.key }
|
64
|
+
) do |animal_id, ref|
|
65
|
+
@instance.load_set.find { |x| x.id == animal_id }.exhibits << ref
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
return @results || (@results = [])
|
71
|
+
end
|
72
|
+
|
73
|
+
def set(results)
|
74
|
+
@results = results
|
75
|
+
end
|
76
|
+
|
77
|
+
def inspect
|
78
|
+
@results.inspect
|
79
|
+
end
|
80
|
+
|
81
|
+
def foreign_key
|
82
|
+
@foreign_key || (@foreign_key = (@options[:foreign_key] || @instance.session.mappings[@instance.class].default_foreign_key))
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
module HasAndBelongsToMany
|
88
|
+
def self.included(base)
|
89
|
+
base.extend(ClassMethods)
|
90
|
+
end
|
91
|
+
|
92
|
+
module ClassMethods
|
93
|
+
def has_and_belongs_to_many(association_name, options = {})
|
94
|
+
HasAndBelongsToManyAssociation.setup(self, association_name, options)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Associations
|
3
|
+
|
4
|
+
class HasManyAssociation
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(instance, association_name, options)
|
8
|
+
@instance = instance
|
9
|
+
@association_name = association_name.to_sym
|
10
|
+
@options = options
|
11
|
+
|
12
|
+
@associated_class = if options.has_key?(:class) || options.has_key?(:class_name)
|
13
|
+
associated_class_name = (options[:class] || options[:class_name])
|
14
|
+
if associated_class_name.kind_of?(String)
|
15
|
+
Kernel.const_get(Inflector.classify(associated_class_name))
|
16
|
+
else
|
17
|
+
associated_class_name
|
18
|
+
end
|
19
|
+
else
|
20
|
+
Kernel.const_get(Inflector.classify(association_name))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.setup(klass, association_name, options)
|
25
|
+
|
26
|
+
# Define the association instance method (i.e. Project#tasks)
|
27
|
+
klass.class_eval <<-EOS
|
28
|
+
def #{association_name}
|
29
|
+
@#{association_name} || (@#{association_name} = HasManyAssociation.new(self, "#{association_name}", #{options.inspect}))
|
30
|
+
end
|
31
|
+
EOS
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def each
|
36
|
+
find.each { |item| yield item }
|
37
|
+
end
|
38
|
+
|
39
|
+
def size
|
40
|
+
entries.size
|
41
|
+
end
|
42
|
+
alias length size
|
43
|
+
|
44
|
+
def [](key)
|
45
|
+
entries[key]
|
46
|
+
end
|
47
|
+
|
48
|
+
def empty?
|
49
|
+
entries.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
def find
|
53
|
+
return @results unless @results.nil?
|
54
|
+
|
55
|
+
unless @instance.loaded_set.nil?
|
56
|
+
|
57
|
+
# Temp variable for the instance variable name.
|
58
|
+
instance_variable_name = "@#{foreign_key}".to_sym
|
59
|
+
|
60
|
+
set = @instance.loaded_set.instances.group_by { |instance| instance.key }
|
61
|
+
|
62
|
+
# Fetch the foreign objects for all instances in the current object's loaded-set.
|
63
|
+
@instance.session.find(@associated_class, :all, foreign_key.to_sym => set.keys).group_by do |association|
|
64
|
+
association.instance_variable_get(instance_variable_name)
|
65
|
+
end.each_pair do |id, results|
|
66
|
+
set[id].first.send(@association_name).set(results)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
return @results ||= []
|
72
|
+
end
|
73
|
+
|
74
|
+
def set(results)
|
75
|
+
@results = results
|
76
|
+
end
|
77
|
+
|
78
|
+
def inspect
|
79
|
+
@results.inspect
|
80
|
+
end
|
81
|
+
|
82
|
+
def foreign_key
|
83
|
+
@foreign_key || (@foreign_key = (@options[:foreign_key] || @instance.session.schema[@instance.class].default_foreign_key))
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
module HasMany
|
89
|
+
def self.included(base)
|
90
|
+
base.extend(ClassMethods)
|
91
|
+
end
|
92
|
+
|
93
|
+
module ClassMethods
|
94
|
+
def has_many(association_name, options = {})
|
95
|
+
HasManyAssociation.setup(self, association_name, options)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|