datamapper 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/CHANGELOG +31 -1
  2. data/MIT-LICENSE +1 -1
  3. data/README +9 -1
  4. data/example.rb +23 -15
  5. data/lib/data_mapper.rb +5 -0
  6. data/lib/data_mapper/adapters/abstract_adapter.rb +9 -207
  7. data/lib/data_mapper/adapters/mysql_adapter.rb +132 -108
  8. data/lib/data_mapper/adapters/postgresql_adapter.rb +242 -0
  9. data/lib/data_mapper/adapters/sql/coersion.rb +74 -0
  10. data/lib/data_mapper/adapters/sql/commands/advanced_load_command.rb +140 -0
  11. data/lib/data_mapper/adapters/sql/commands/conditions.rb +161 -0
  12. data/lib/data_mapper/adapters/sql/commands/delete_command.rb +113 -0
  13. data/lib/data_mapper/adapters/sql/commands/load_command.rb +296 -0
  14. data/lib/data_mapper/adapters/sql/commands/save_command.rb +141 -0
  15. data/lib/data_mapper/adapters/sql/commands/table_exists_command.rb +33 -0
  16. data/lib/data_mapper/adapters/sql/mappings/column.rb +91 -0
  17. data/lib/data_mapper/adapters/sql/mappings/schema.rb +30 -0
  18. data/lib/data_mapper/adapters/sql/mappings/table.rb +143 -0
  19. data/lib/data_mapper/adapters/sql/quoting.rb +38 -0
  20. data/lib/data_mapper/adapters/sql_adapter.rb +163 -0
  21. data/lib/data_mapper/adapters/sqlite3_adapter.rb +155 -116
  22. data/lib/data_mapper/associations.rb +2 -0
  23. data/lib/data_mapper/associations/advanced_has_many_association.rb +55 -0
  24. data/lib/data_mapper/associations/belongs_to_association.rb +2 -2
  25. data/lib/data_mapper/associations/has_many_association.rb +3 -3
  26. data/lib/data_mapper/associations/has_one_association.rb +2 -2
  27. data/lib/data_mapper/base.rb +30 -11
  28. data/lib/data_mapper/callbacks.rb +4 -1
  29. data/lib/data_mapper/database.rb +8 -41
  30. data/lib/data_mapper/identity_map.rb +23 -3
  31. data/lib/data_mapper/session.rb +34 -186
  32. data/lib/data_mapper/{extensions → support}/active_record_impersonation.rb +16 -12
  33. data/lib/data_mapper/support/blank.rb +35 -0
  34. data/lib/data_mapper/support/connection_pool.rb +2 -1
  35. data/lib/data_mapper/support/string.rb +27 -0
  36. data/lib/data_mapper/support/struct.rb +26 -0
  37. data/lib/data_mapper/validations/unique_validator.rb +1 -3
  38. data/lib/data_mapper/validations/validation_helper.rb +1 -1
  39. data/performance.rb +24 -7
  40. data/profile_data_mapper.rb +24 -2
  41. data/rakefile.rb +2 -2
  42. data/spec/basic_finder.rb +2 -2
  43. data/spec/belongs_to.rb +1 -1
  44. data/spec/delete_command_spec.rb +9 -0
  45. data/spec/fixtures/zoos.yaml +4 -0
  46. data/spec/has_many.rb +1 -1
  47. data/spec/load_command_spec.rb +44 -0
  48. data/spec/models/zoo.rb +2 -0
  49. data/spec/save_command_spec.rb +13 -0
  50. data/spec/spec_helper.rb +10 -1
  51. data/spec/support/string_spec.rb +7 -0
  52. data/spec/validates_confirmation_of.rb +1 -1
  53. data/spec/validates_format_of.rb +1 -1
  54. data/spec/validates_length_of.rb +1 -1
  55. data/spec/validations.rb +1 -1
  56. metadata +23 -20
  57. data/lib/data_mapper/extensions/callback_helpers.rb +0 -35
  58. data/lib/data_mapper/loaded_set.rb +0 -45
  59. data/lib/data_mapper/mappings/column.rb +0 -78
  60. data/lib/data_mapper/mappings/schema.rb +0 -28
  61. data/lib/data_mapper/mappings/table.rb +0 -99
  62. data/lib/data_mapper/queries/conditions.rb +0 -141
  63. data/lib/data_mapper/queries/connection.rb +0 -34
  64. data/lib/data_mapper/queries/create_table_statement.rb +0 -38
  65. data/lib/data_mapper/queries/delete_statement.rb +0 -17
  66. data/lib/data_mapper/queries/drop_table_statement.rb +0 -17
  67. data/lib/data_mapper/queries/insert_statement.rb +0 -29
  68. data/lib/data_mapper/queries/reader.rb +0 -42
  69. data/lib/data_mapper/queries/result.rb +0 -19
  70. data/lib/data_mapper/queries/select_statement.rb +0 -103
  71. data/lib/data_mapper/queries/table_exists_statement.rb +0 -17
  72. data/lib/data_mapper/queries/truncate_table_statement.rb +0 -17
  73. 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