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.
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