datamapper 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +31 -1
- data/MIT-LICENSE +1 -1
- data/README +9 -1
- data/example.rb +23 -15
- data/lib/data_mapper.rb +5 -0
- data/lib/data_mapper/adapters/abstract_adapter.rb +9 -207
- data/lib/data_mapper/adapters/mysql_adapter.rb +132 -108
- data/lib/data_mapper/adapters/postgresql_adapter.rb +242 -0
- data/lib/data_mapper/adapters/sql/coersion.rb +74 -0
- data/lib/data_mapper/adapters/sql/commands/advanced_load_command.rb +140 -0
- data/lib/data_mapper/adapters/sql/commands/conditions.rb +161 -0
- data/lib/data_mapper/adapters/sql/commands/delete_command.rb +113 -0
- data/lib/data_mapper/adapters/sql/commands/load_command.rb +296 -0
- data/lib/data_mapper/adapters/sql/commands/save_command.rb +141 -0
- data/lib/data_mapper/adapters/sql/commands/table_exists_command.rb +33 -0
- data/lib/data_mapper/adapters/sql/mappings/column.rb +91 -0
- data/lib/data_mapper/adapters/sql/mappings/schema.rb +30 -0
- data/lib/data_mapper/adapters/sql/mappings/table.rb +143 -0
- data/lib/data_mapper/adapters/sql/quoting.rb +38 -0
- data/lib/data_mapper/adapters/sql_adapter.rb +163 -0
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +155 -116
- data/lib/data_mapper/associations.rb +2 -0
- data/lib/data_mapper/associations/advanced_has_many_association.rb +55 -0
- data/lib/data_mapper/associations/belongs_to_association.rb +2 -2
- data/lib/data_mapper/associations/has_many_association.rb +3 -3
- data/lib/data_mapper/associations/has_one_association.rb +2 -2
- data/lib/data_mapper/base.rb +30 -11
- data/lib/data_mapper/callbacks.rb +4 -1
- data/lib/data_mapper/database.rb +8 -41
- data/lib/data_mapper/identity_map.rb +23 -3
- data/lib/data_mapper/session.rb +34 -186
- data/lib/data_mapper/{extensions → support}/active_record_impersonation.rb +16 -12
- data/lib/data_mapper/support/blank.rb +35 -0
- data/lib/data_mapper/support/connection_pool.rb +2 -1
- data/lib/data_mapper/support/string.rb +27 -0
- data/lib/data_mapper/support/struct.rb +26 -0
- data/lib/data_mapper/validations/unique_validator.rb +1 -3
- data/lib/data_mapper/validations/validation_helper.rb +1 -1
- data/performance.rb +24 -7
- data/profile_data_mapper.rb +24 -2
- data/rakefile.rb +2 -2
- data/spec/basic_finder.rb +2 -2
- data/spec/belongs_to.rb +1 -1
- data/spec/delete_command_spec.rb +9 -0
- data/spec/fixtures/zoos.yaml +4 -0
- data/spec/has_many.rb +1 -1
- data/spec/load_command_spec.rb +44 -0
- data/spec/models/zoo.rb +2 -0
- data/spec/save_command_spec.rb +13 -0
- data/spec/spec_helper.rb +10 -1
- data/spec/support/string_spec.rb +7 -0
- data/spec/validates_confirmation_of.rb +1 -1
- data/spec/validates_format_of.rb +1 -1
- data/spec/validates_length_of.rb +1 -1
- data/spec/validations.rb +1 -1
- metadata +23 -20
- data/lib/data_mapper/extensions/callback_helpers.rb +0 -35
- data/lib/data_mapper/loaded_set.rb +0 -45
- data/lib/data_mapper/mappings/column.rb +0 -78
- data/lib/data_mapper/mappings/schema.rb +0 -28
- data/lib/data_mapper/mappings/table.rb +0 -99
- data/lib/data_mapper/queries/conditions.rb +0 -141
- data/lib/data_mapper/queries/connection.rb +0 -34
- data/lib/data_mapper/queries/create_table_statement.rb +0 -38
- data/lib/data_mapper/queries/delete_statement.rb +0 -17
- data/lib/data_mapper/queries/drop_table_statement.rb +0 -17
- data/lib/data_mapper/queries/insert_statement.rb +0 -29
- data/lib/data_mapper/queries/reader.rb +0 -42
- data/lib/data_mapper/queries/result.rb +0 -19
- data/lib/data_mapper/queries/select_statement.rb +0 -103
- data/lib/data_mapper/queries/table_exists_statement.rb +0 -17
- data/lib/data_mapper/queries/truncate_table_statement.rb +0 -17
- data/lib/data_mapper/queries/update_statement.rb +0 -25
@@ -0,0 +1,141 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Adapters
|
3
|
+
module Sql
|
4
|
+
module Commands
|
5
|
+
|
6
|
+
class SaveCommand
|
7
|
+
|
8
|
+
def initialize(adapter, session, instance)
|
9
|
+
@adapter, @session, @instance = adapter, session, instance
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_update_sql
|
13
|
+
table = @adapter[@instance.class]
|
14
|
+
|
15
|
+
sql = "UPDATE " << table.to_sql << " SET "
|
16
|
+
|
17
|
+
@instance.dirty_attributes.map do |k, v|
|
18
|
+
sql << table[k].to_sql << " = " << @adapter.quote_value(v) << ", "
|
19
|
+
end
|
20
|
+
|
21
|
+
sql[0, sql.size - 2] << " WHERE #{table.key.to_sql} = " << @adapter.quote_value(@instance.key)
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_insert_sql
|
25
|
+
|
26
|
+
table = @adapter[@instance.class]
|
27
|
+
|
28
|
+
keys = []
|
29
|
+
values = []
|
30
|
+
|
31
|
+
@instance.dirty_attributes.each_pair { |k,v| keys << table[k].to_sql; values << v }
|
32
|
+
|
33
|
+
# Formatting is a bit off here, but it looks nicer in the log this way.
|
34
|
+
sql = "INSERT INTO #{table.to_sql} (#{keys.join(', ')}) \
|
35
|
+
VALUES (#{values.map { |v| @adapter.quote_value(v) }.join(', ')})"
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_create_table_sql
|
39
|
+
table = @adapter[@instance]
|
40
|
+
|
41
|
+
sql = "CREATE TABLE " << table.to_sql << " ("
|
42
|
+
|
43
|
+
sql << table.columns.map do |column|
|
44
|
+
column_long_form(column)
|
45
|
+
end.join(', ')
|
46
|
+
|
47
|
+
sql << ", PRIMARY KEY (#{table.key.to_sql}))"
|
48
|
+
|
49
|
+
return sql
|
50
|
+
end
|
51
|
+
|
52
|
+
def column_long_form(column)
|
53
|
+
long_form = "#{column.to_sql} #{@adapter.class::TYPES[column.type] || column.type}"
|
54
|
+
|
55
|
+
long_form << "(#{column.size})" unless column.size.nil?
|
56
|
+
long_form << " NOT NULL" unless column.nullable?
|
57
|
+
long_form << " auto_increment" if column.key?
|
58
|
+
long_form << " default #{column.options[:default]}" if column.options.has_key?(:default)
|
59
|
+
|
60
|
+
return long_form
|
61
|
+
end
|
62
|
+
|
63
|
+
def callback(name)
|
64
|
+
@instance.class.callbacks.execute(name, @instance)
|
65
|
+
end
|
66
|
+
|
67
|
+
def insert!
|
68
|
+
callback(:before_create)
|
69
|
+
|
70
|
+
result = execute_insert(to_insert_sql)
|
71
|
+
|
72
|
+
if result
|
73
|
+
@instance.instance_variable_set(:@new_record, false)
|
74
|
+
@instance.instance_variable_set(:@id, result)
|
75
|
+
calculate_original_hashes(@instance)
|
76
|
+
@session.identity_map.set(@instance)
|
77
|
+
callback(:after_create)
|
78
|
+
end
|
79
|
+
|
80
|
+
return result
|
81
|
+
rescue => error
|
82
|
+
@adapter.log.error(error)
|
83
|
+
raise error
|
84
|
+
end
|
85
|
+
|
86
|
+
def update!
|
87
|
+
callback(:before_update)
|
88
|
+
|
89
|
+
result = execute_update(to_update_sql)
|
90
|
+
|
91
|
+
calculate_original_hashes(@instance)
|
92
|
+
callback(:after_update)
|
93
|
+
return result
|
94
|
+
rescue => error
|
95
|
+
@adapter.log.error(error)
|
96
|
+
raise error
|
97
|
+
end
|
98
|
+
|
99
|
+
def call
|
100
|
+
if @instance.kind_of?(Class)
|
101
|
+
return false if @adapter.table_exists?(@instance)
|
102
|
+
execute_create_table(to_create_table_sql)
|
103
|
+
else
|
104
|
+
return false unless @instance.dirty?
|
105
|
+
callback(:before_save)
|
106
|
+
result = @instance.new_record? ? insert! : update!
|
107
|
+
@instance.session = @session
|
108
|
+
callback(:after_save)
|
109
|
+
result
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
def execute_insert(sql)
|
115
|
+
raise NotImplementedError.new
|
116
|
+
end
|
117
|
+
|
118
|
+
def execute_update(sql)
|
119
|
+
raise NotImplementedError.new
|
120
|
+
end
|
121
|
+
|
122
|
+
def execute_create_table(sql)
|
123
|
+
raise NotImplementedError.new
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
# Calculates the original hashes for each value
|
128
|
+
# in an instance's set of attributes, and adds
|
129
|
+
# them to the original_hashes hash.
|
130
|
+
def calculate_original_hashes(instance)
|
131
|
+
instance.attributes.each_pair do |name, value|
|
132
|
+
instance.original_hashes[name] = value.hash
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Adapters
|
3
|
+
module Sql
|
4
|
+
module Commands
|
5
|
+
|
6
|
+
class TableExistsCommand
|
7
|
+
|
8
|
+
def initialize(adapter, klass_or_name)
|
9
|
+
@adapter, @klass_or_name = adapter, klass_or_name
|
10
|
+
end
|
11
|
+
|
12
|
+
def table_name
|
13
|
+
@table_name || @table_name = case @klass_or_name
|
14
|
+
when String then @adapter.quote_value(@klass_or_name)
|
15
|
+
when Class then @adapter.quote_value(@adapter[@klass_or_name].name)
|
16
|
+
else raise ArgumentError.new('klass_or_name must be a mapped-class or a table name')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_sql
|
21
|
+
"SHOW TABLES LIKE #{table_name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def call
|
25
|
+
raise NotImplementedError.new
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Adapters
|
3
|
+
module Sql
|
4
|
+
module Mappings
|
5
|
+
|
6
|
+
# TODO: There are of course many more options to add here.
|
7
|
+
# Ordinal, Length/Size, Nullability are just a few.
|
8
|
+
class Column
|
9
|
+
|
10
|
+
attr_accessor :name, :type, :options
|
11
|
+
|
12
|
+
def initialize(adapter, table, name, type, options = {})
|
13
|
+
@adapter = adapter
|
14
|
+
@table = table
|
15
|
+
@name, @type, @options = name.to_sym, type, options
|
16
|
+
|
17
|
+
(class << self; self end).class_eval <<-EOS
|
18
|
+
def type_cast_value(value)
|
19
|
+
@adapter.type_cast_#{type}(value)
|
20
|
+
end
|
21
|
+
EOS
|
22
|
+
end
|
23
|
+
|
24
|
+
def lazy=(value)
|
25
|
+
@options[:lazy] = value
|
26
|
+
end
|
27
|
+
|
28
|
+
# Determines if the field should be lazy loaded.
|
29
|
+
# You can set this explicitly, or accept the default,
|
30
|
+
# which is false for all but text fields.
|
31
|
+
def lazy?
|
32
|
+
@options[:lazy] || (type == :text)
|
33
|
+
end
|
34
|
+
|
35
|
+
def nullable?
|
36
|
+
@options[:nullable] || true
|
37
|
+
end
|
38
|
+
|
39
|
+
def key?
|
40
|
+
@options[:key] || false
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_sym
|
44
|
+
@name
|
45
|
+
end
|
46
|
+
|
47
|
+
def instance_variable_name
|
48
|
+
@instance_variable_name || (@instance_variable_name = "@#{@name.to_s.gsub(/\?$/, '')}".freeze)
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
@name.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def column_name
|
56
|
+
@column_name || (@column_name = (@options.has_key?(:column) ? @options[:column].to_s : name.to_s.gsub(/\?$/, '')).freeze)
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_sql(include_table_name = false)
|
60
|
+
if include_table_name
|
61
|
+
@to_sql_with_table_name || @to_sql_with_table_name = begin
|
62
|
+
(@table.to_sql + '.' + @adapter.quote_column_name(column_name)).freeze
|
63
|
+
end
|
64
|
+
else
|
65
|
+
@to_sql || (@to_sql = @adapter.quote_column_name(column_name).freeze)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def size
|
70
|
+
@size || begin
|
71
|
+
return @size = @options[:size] if @options.has_key?(:size)
|
72
|
+
return @size = @options[:length] if @options.has_key?(:length)
|
73
|
+
|
74
|
+
@size = case type
|
75
|
+
when :integer then 4
|
76
|
+
when :string, :class then 50
|
77
|
+
else nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def inspect
|
83
|
+
"#<%s:0x%x @name=%s, @type=%s, @options=%s>" % [self.class.name, (object_id * 2), to_sql, type.inspect, options.inspect]
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/table'
|
2
|
+
|
3
|
+
module DataMapper
|
4
|
+
module Adapters
|
5
|
+
module Sql
|
6
|
+
module Mappings
|
7
|
+
|
8
|
+
class Schema
|
9
|
+
|
10
|
+
def initialize(adapter)
|
11
|
+
@adapter = adapter
|
12
|
+
@tables = Hash.new { |h,k| h[k] = Table.new(@adapter, k) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](klass)
|
16
|
+
@tables[klass]
|
17
|
+
end
|
18
|
+
|
19
|
+
def each
|
20
|
+
@tables.values.each do |table|
|
21
|
+
yield table
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/column'
|
2
|
+
|
3
|
+
module DataMapper
|
4
|
+
module Adapters
|
5
|
+
module Sql
|
6
|
+
module Mappings
|
7
|
+
|
8
|
+
class Table
|
9
|
+
|
10
|
+
class Association
|
11
|
+
def initialize(association_name, constant_name)
|
12
|
+
@association_name, @constant_name = association_name, constant_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
@association_name
|
17
|
+
end
|
18
|
+
|
19
|
+
def constant
|
20
|
+
@constant || @constant = begin
|
21
|
+
Object.const_get(@constant_name)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :klass
|
28
|
+
|
29
|
+
def initialize(adapter, setup_klass)
|
30
|
+
raise "\"setup_klass\" must not be nil!" if setup_klass.nil?
|
31
|
+
@adapter = adapter
|
32
|
+
@klass = setup_klass
|
33
|
+
@columns = []
|
34
|
+
@columns_hash = Hash.new { |h,k| h[k] = @columns.find { |c| c.name == k } }
|
35
|
+
@columns_by_column_name = Hash.new { |h,k| h[k.to_s] = @columns.find { |c| c.column_name == k.to_s } }
|
36
|
+
@has_many = []
|
37
|
+
@associations = []
|
38
|
+
end
|
39
|
+
|
40
|
+
def associations
|
41
|
+
@associations
|
42
|
+
end
|
43
|
+
|
44
|
+
# def has_many(association_name, options)
|
45
|
+
# @associtaions << Association.new(association_name, Inflector.classify(Inflector.singularize(association_name.to_s)))
|
46
|
+
# end
|
47
|
+
|
48
|
+
# def has_many_associations
|
49
|
+
# @has_many
|
50
|
+
# end
|
51
|
+
|
52
|
+
def columns
|
53
|
+
key if @key.nil?
|
54
|
+
@columns
|
55
|
+
end
|
56
|
+
|
57
|
+
def exists?
|
58
|
+
@adapter.table_exists?(name)
|
59
|
+
end
|
60
|
+
|
61
|
+
def drop!
|
62
|
+
@adapter.delete(@klass, :drop => true) if exists?
|
63
|
+
end
|
64
|
+
|
65
|
+
def create!
|
66
|
+
@adapter.save(database, @klass) unless exists?
|
67
|
+
end
|
68
|
+
|
69
|
+
def key
|
70
|
+
if @key.nil?
|
71
|
+
key_column = @columns.find { |c| c.key? }
|
72
|
+
@key = if key_column.nil?
|
73
|
+
column = add_column(:id, :integer, :key => true)
|
74
|
+
@klass.send(:attr_reader, :id) unless @klass.methods.include?(:id)
|
75
|
+
column
|
76
|
+
else
|
77
|
+
key_column
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
@key
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_column(column_name, type, options)
|
85
|
+
column = @columns.find { |c| c.name == column_name.to_sym }
|
86
|
+
|
87
|
+
if column.nil?
|
88
|
+
reset_derived_columns!
|
89
|
+
column = Column.new(@adapter, self, column_name, type, options)
|
90
|
+
@columns.send(column_name == :id ? :unshift : :push, column)
|
91
|
+
end
|
92
|
+
|
93
|
+
return column
|
94
|
+
end
|
95
|
+
|
96
|
+
def [](column_name)
|
97
|
+
return key if column_name == :id
|
98
|
+
@columns_hash[column_name.kind_of?(Symbol) ? column_name : column_name.to_sym]
|
99
|
+
end
|
100
|
+
|
101
|
+
def find_by_column_name(column_name)
|
102
|
+
@columns_by_column_name[column_name.kind_of?(String) ? column_name : column_name.to_s]
|
103
|
+
end
|
104
|
+
|
105
|
+
def name
|
106
|
+
@name || begin
|
107
|
+
@name = if @klass.superclass != DataMapper::Base && @klass.superclass != Object
|
108
|
+
@adapter[@klass.superclass].name
|
109
|
+
else
|
110
|
+
Inflector.tableize(@klass.name)
|
111
|
+
end.freeze
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def name=(value)
|
116
|
+
@name = value
|
117
|
+
end
|
118
|
+
|
119
|
+
def default_foreign_key
|
120
|
+
@default_foreign_key || (@default_foreign_key = "#{String::memoized_underscore(Inflector.singularize(name))}_id".freeze)
|
121
|
+
end
|
122
|
+
|
123
|
+
def to_sql
|
124
|
+
@to_sql || (@to_sql = @adapter.quote_table_name(name).freeze)
|
125
|
+
end
|
126
|
+
|
127
|
+
def inspect
|
128
|
+
"#<%s:0x%x @klass=%s, @name=%s, @columns=%s>" % [self.class.name, (object_id * 2), @klass.name, to_sql, @columns.inspect]
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
def reset_derived_columns!
|
133
|
+
@columns_hash.clear
|
134
|
+
@columns_by_column_name.clear
|
135
|
+
@key = nil
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Adapters
|
3
|
+
module Sql
|
4
|
+
|
5
|
+
# Quoting is a mixin that extends your DataMapper::Database singleton-class
|
6
|
+
# to allow for object-name and value quoting to be exposed to the queries.
|
7
|
+
#
|
8
|
+
# DESIGN: Is there any need for this outside of the query objects? Should
|
9
|
+
# we just include it in our query object subclasses and not rely on a Quoting
|
10
|
+
# mixin being part of the "standard" Adapter interface?
|
11
|
+
module Quoting
|
12
|
+
|
13
|
+
def quote_table_name(name)
|
14
|
+
name.ensure_wrapped_with(self.class::TABLE_QUOTING_CHARACTER)
|
15
|
+
end
|
16
|
+
|
17
|
+
def quote_column_name(name)
|
18
|
+
name.ensure_wrapped_with(self.class::COLUMN_QUOTING_CHARACTER)
|
19
|
+
end
|
20
|
+
|
21
|
+
def quote_value(value)
|
22
|
+
return 'NULL' if value.nil?
|
23
|
+
|
24
|
+
case value
|
25
|
+
when Numeric then value.to_s
|
26
|
+
when String then "'#{value.gsub("'", "''")}'"
|
27
|
+
when Class then "'#{value.name}'"
|
28
|
+
when Date then "'#{value.to_s}'"
|
29
|
+
when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
|
30
|
+
when TrueClass, FalseClass then value.to_s.upcase
|
31
|
+
else raise "Don't know how to quote #{value.inspect}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end # module Quoting
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|