datamapper 0.1.0 → 0.1.1
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 +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
|