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.
Files changed (85) hide show
  1. data/CHANGELOG +2 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README +1 -0
  4. data/example.rb +25 -0
  5. data/lib/data_mapper.rb +30 -0
  6. data/lib/data_mapper/adapters/abstract_adapter.rb +229 -0
  7. data/lib/data_mapper/adapters/mysql_adapter.rb +171 -0
  8. data/lib/data_mapper/adapters/sqlite3_adapter.rb +189 -0
  9. data/lib/data_mapper/associations.rb +19 -0
  10. data/lib/data_mapper/associations/belongs_to_association.rb +111 -0
  11. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +100 -0
  12. data/lib/data_mapper/associations/has_many_association.rb +101 -0
  13. data/lib/data_mapper/associations/has_one_association.rb +107 -0
  14. data/lib/data_mapper/base.rb +160 -0
  15. data/lib/data_mapper/callbacks.rb +47 -0
  16. data/lib/data_mapper/database.rb +134 -0
  17. data/lib/data_mapper/extensions/active_record_impersonation.rb +69 -0
  18. data/lib/data_mapper/extensions/callback_helpers.rb +35 -0
  19. data/lib/data_mapper/identity_map.rb +21 -0
  20. data/lib/data_mapper/loaded_set.rb +45 -0
  21. data/lib/data_mapper/mappings/column.rb +78 -0
  22. data/lib/data_mapper/mappings/schema.rb +28 -0
  23. data/lib/data_mapper/mappings/table.rb +99 -0
  24. data/lib/data_mapper/queries/conditions.rb +141 -0
  25. data/lib/data_mapper/queries/connection.rb +34 -0
  26. data/lib/data_mapper/queries/create_table_statement.rb +38 -0
  27. data/lib/data_mapper/queries/delete_statement.rb +17 -0
  28. data/lib/data_mapper/queries/drop_table_statement.rb +17 -0
  29. data/lib/data_mapper/queries/insert_statement.rb +29 -0
  30. data/lib/data_mapper/queries/reader.rb +42 -0
  31. data/lib/data_mapper/queries/result.rb +19 -0
  32. data/lib/data_mapper/queries/select_statement.rb +103 -0
  33. data/lib/data_mapper/queries/table_exists_statement.rb +17 -0
  34. data/lib/data_mapper/queries/truncate_table_statement.rb +17 -0
  35. data/lib/data_mapper/queries/update_statement.rb +25 -0
  36. data/lib/data_mapper/session.rb +240 -0
  37. data/lib/data_mapper/support/blank_slate.rb +3 -0
  38. data/lib/data_mapper/support/connection_pool.rb +117 -0
  39. data/lib/data_mapper/support/enumerable.rb +27 -0
  40. data/lib/data_mapper/support/inflector.rb +329 -0
  41. data/lib/data_mapper/support/proc.rb +69 -0
  42. data/lib/data_mapper/support/string.rb +23 -0
  43. data/lib/data_mapper/support/symbol.rb +91 -0
  44. data/lib/data_mapper/support/weak_hash.rb +46 -0
  45. data/lib/data_mapper/unit_of_work.rb +38 -0
  46. data/lib/data_mapper/validations/confirmation_validator.rb +55 -0
  47. data/lib/data_mapper/validations/contextual_validations.rb +50 -0
  48. data/lib/data_mapper/validations/format_validator.rb +85 -0
  49. data/lib/data_mapper/validations/formats/email.rb +78 -0
  50. data/lib/data_mapper/validations/generic_validator.rb +27 -0
  51. data/lib/data_mapper/validations/length_validator.rb +75 -0
  52. data/lib/data_mapper/validations/required_field_validator.rb +47 -0
  53. data/lib/data_mapper/validations/unique_validator.rb +65 -0
  54. data/lib/data_mapper/validations/validation_errors.rb +34 -0
  55. data/lib/data_mapper/validations/validation_helper.rb +60 -0
  56. data/performance.rb +156 -0
  57. data/profile_data_mapper.rb +18 -0
  58. data/rakefile.rb +80 -0
  59. data/spec/basic_finder.rb +67 -0
  60. data/spec/belongs_to.rb +47 -0
  61. data/spec/fixtures/animals.yaml +32 -0
  62. data/spec/fixtures/exhibits.yaml +90 -0
  63. data/spec/fixtures/fruit.yaml +6 -0
  64. data/spec/fixtures/people.yaml +15 -0
  65. data/spec/fixtures/zoos.yaml +20 -0
  66. data/spec/has_and_belongs_to_many.rb +25 -0
  67. data/spec/has_many.rb +34 -0
  68. data/spec/legacy.rb +14 -0
  69. data/spec/models/animal.rb +7 -0
  70. data/spec/models/exhibit.rb +6 -0
  71. data/spec/models/fruit.rb +6 -0
  72. data/spec/models/person.rb +7 -0
  73. data/spec/models/post.rb +4 -0
  74. data/spec/models/sales_person.rb +4 -0
  75. data/spec/models/zoo.rb +5 -0
  76. data/spec/new_record.rb +24 -0
  77. data/spec/spec_helper.rb +61 -0
  78. data/spec/sub_select.rb +16 -0
  79. data/spec/symbolic_operators.rb +21 -0
  80. data/spec/validates_confirmation_of.rb +36 -0
  81. data/spec/validates_format_of.rb +61 -0
  82. data/spec/validates_length_of.rb +101 -0
  83. data/spec/validates_uniqueness_of.rb +45 -0
  84. data/spec/validations.rb +63 -0
  85. metadata +134 -0
@@ -0,0 +1,69 @@
1
+ module DataMapper
2
+ module Extensions
3
+
4
+ module ActiveRecordImpersonation
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ def save
11
+ session.save(self)
12
+ end
13
+
14
+ def reload!
15
+ session.find(self.class, id, :select => session.mappings[self.class].columns.map(&:name), :reload => true)
16
+ end
17
+
18
+ def reload
19
+ reload!
20
+ end
21
+
22
+ def destroy!
23
+ session.destroy(self)
24
+ end
25
+
26
+ module ClassMethods
27
+
28
+ def all(options = {}, &b)
29
+ find(:all, options, &b)
30
+ end
31
+
32
+ def first(options = {}, &b)
33
+ find(:first, options, &b)
34
+ end
35
+
36
+ def delete_all
37
+ database.delete_all(self)
38
+ end
39
+
40
+ def truncate!
41
+ database.truncate(self)
42
+ end
43
+
44
+ def find(*args, &b)
45
+ DataMapper::database.find(self, *args, &b)
46
+ end
47
+
48
+ def find_by_sql(*args)
49
+ DataMapper::database.query(*args)
50
+ end
51
+
52
+ def [](id_or_hash)
53
+ if id_or_hash.kind_of?(Hash)
54
+ find(:first, id_or_hash)
55
+ else
56
+ find(id_or_hash)
57
+ end
58
+ end
59
+
60
+ def create(attributes)
61
+ instance = self.new(attributes)
62
+ instance.save
63
+ instance
64
+ end
65
+ end
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,35 @@
1
+ require 'data_mapper/callbacks'
2
+
3
+ module DataMapper
4
+ module Extensions
5
+
6
+ module CallbackHelpers
7
+
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+
11
+ # declare helpers for the standard callbacks
12
+ Callbacks::EVENTS.each do |name|
13
+ base.class_eval <<-EOS
14
+ def self.#{name}(string = nil, &block)
15
+ if string.nil?
16
+ callbacks.add(:#{name}, &block)
17
+ else
18
+ callbacks.add(:#{name}, string)
19
+ end
20
+ end
21
+ EOS
22
+ end
23
+ end
24
+
25
+ module ClassMethods
26
+
27
+ def callbacks
28
+ @callbacks ||= Callbacks.new
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ require 'data_mapper/support/weak_hash'
2
+
3
+ module DataMapper
4
+ class IdentityMap
5
+
6
+ def initialize
7
+ @cache = Hash.new { |h,k| h[k] = Support::WeakHash.new }
8
+ end
9
+
10
+ def get(klass, key)
11
+ @cache[klass][key]
12
+ end
13
+
14
+ def set(instance)
15
+ raise "Can't store an instance with a nil key in the IdentityMap" if instance.key == nil
16
+
17
+ @cache[instance.class][instance.key] = instance
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,45 @@
1
+ require 'data_mapper/support/enumerable'
2
+
3
+ module DataMapper
4
+
5
+ # LoadedSet's purpose is to give a common reference for the results of a query,
6
+ # so that they can be manipulated by the DataMapper, behind-the-scenes, as a set.
7
+ class LoadedSet
8
+
9
+ # Provides a reference to the database that originally loaded the instances.
10
+ attr_reader :database
11
+ # Provides an Enumerable of the instances loaded in the set.
12
+ attr_reader :instances
13
+
14
+ def initialize(database)
15
+ @database = database
16
+ @instances = [] # ObjectIdCollection.new
17
+ end
18
+
19
+ # Not sure if this is necessary yet...
20
+ # In other words: Does it make sense to allow portions of a set to be
21
+ # garbage-collected?
22
+ #
23
+ # If so, then this isn't good enough because it's likely to
24
+ # throw errors. If it's not, then letting Ruby itself track the
25
+ # references with a simple Array is probably more effecient.
26
+ class ObjectIdCollection
27
+
28
+ include Enumerable
29
+ include Support::Enumerable
30
+
31
+ def initialize
32
+ @object_ids = []
33
+ end
34
+
35
+ def each
36
+ @object_ids.map { |id| yield ObjectSpace::_id2ref(id) }
37
+ end
38
+
39
+ def <<(object)
40
+ @object_ids << object.object_id
41
+ end
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,78 @@
1
+ module DataMapper
2
+ module Mappings
3
+
4
+ # TODO: There are of course many more options to add here.
5
+ # Ordinal, Length/Size, Nullability are just a few.
6
+ class Column
7
+
8
+ attr_accessor :name, :type, :options
9
+
10
+ def initialize(database, name, type, options = {})
11
+ @database = database
12
+ @name, @type, @options = name.to_sym, type, options
13
+ end
14
+
15
+ def lazy=(value)
16
+ @options[:lazy] = value
17
+ end
18
+
19
+ # Determines if the field should be lazy loaded.
20
+ # You can set this explicitly, or accept the default,
21
+ # which is false for all but text fields.
22
+ def lazy?
23
+ @options[:lazy] || (type == :text)
24
+ end
25
+
26
+ def nullable?
27
+ @options[:nullable] || true
28
+ end
29
+
30
+ def key?
31
+ @options[:key] || false
32
+ end
33
+
34
+ def to_sym
35
+ @name
36
+ end
37
+
38
+ def instance_variable_name
39
+ @instance_variable_name || (@instance_variable_name = "@#{@name.to_s.gsub(/\?$/, '')}".freeze)
40
+ end
41
+
42
+ def to_s
43
+ @name.to_s
44
+ end
45
+
46
+ def type_cast_value(value)
47
+ @database.type_cast_value(type, value)
48
+ end
49
+
50
+ def column_name
51
+ @column_name || (@column_name = (@options.has_key?(:column) ? @options[:column].to_s : name.to_s.gsub(/\?$/, '')).freeze)
52
+ end
53
+
54
+ def to_sql
55
+ @to_sql || (@to_sql = @database.quote_column_name(column_name).freeze)
56
+ end
57
+
58
+ def size
59
+ @size || begin
60
+ return @size = @options[:size] if @options.has_key?(:size)
61
+ return @size = @options[:length] if @options.has_key?(:length)
62
+
63
+ @size = case type
64
+ when :integer then 4
65
+ when :string, :class then 50
66
+ else nil
67
+ end
68
+ end
69
+ end
70
+
71
+ def inspect
72
+ "#<%s:0x%x @name=%s, @type=%s, @options=%s>" % [self.class.name, (object_id * 2), to_sql, type.inspect, options.inspect]
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,28 @@
1
+ require 'data_mapper/mappings/table'
2
+
3
+ module DataMapper
4
+ module Mappings
5
+
6
+ class Schema
7
+
8
+ def initialize(database)
9
+ @database = database
10
+ @tables = Hash.new { |h,k| h[k] = Table.new(@database, k) }
11
+ end
12
+
13
+ def [](klass)
14
+ @tables[klass]
15
+ rescue
16
+ raise "#{klass.inspect} can't be mapped to a table"
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
@@ -0,0 +1,99 @@
1
+ require 'data_mapper/mappings/column'
2
+
3
+ module DataMapper
4
+ module Mappings
5
+
6
+ class Table
7
+
8
+ attr_reader :klass
9
+
10
+ def initialize(database, klass)
11
+ unless database.kind_of?(DataMapper::Database) && klass.kind_of?(Class)
12
+ raise "Database and klass are required: #{ { :database => database, :klass => klass }.inspect }"
13
+ end
14
+
15
+ @database = database
16
+ @klass = klass
17
+ @columns = []
18
+ @columns_hash = Hash.new { |h,k| h[k] = @columns.find { |c| c.name == k } }
19
+ @columns_by_column_name = Hash.new { |h,k| h[k.to_s] = @columns.find { |c| c.column_name == k.to_s } }
20
+ end
21
+
22
+ def columns
23
+ key if @key.nil?
24
+ @columns
25
+ end
26
+
27
+ def key
28
+ if @key.nil?
29
+ key_column = @columns.find { |c| c.key? }
30
+ @key = if key_column.nil?
31
+ add_column(:id, :integer, :key => true)
32
+ @klass.send(:attr_reader, :id) unless @klass.methods.include?(:id)
33
+ @columns.last
34
+ else
35
+ key_column
36
+ end
37
+ end
38
+
39
+ @key
40
+ end
41
+
42
+ def add_column(column_name, type, options)
43
+ column = @columns.find { |c| c.name == column_name.to_sym }
44
+
45
+ if column.nil?
46
+ reset_derived_columns!
47
+ column = Column.new(@database, column_name, type, options)
48
+ @columns << column
49
+ end
50
+
51
+ return column
52
+ end
53
+
54
+ def [](column_name)
55
+ return key if column_name == :id
56
+ @columns_hash[column_name.kind_of?(Symbol) ? column_name : column_name.to_sym]
57
+ end
58
+
59
+ def find_by_column_name(column_name)
60
+ @columns_by_column_name[column_name.kind_of?(String) ? column_name : column_name.to_s]
61
+ end
62
+
63
+ def name
64
+ @name || begin
65
+ @name = if @klass.superclass != DataMapper::Base
66
+ @database[@klass.superclass].name
67
+ else
68
+ Inflector.tableize(@klass.name)
69
+ end.freeze
70
+ end
71
+ end
72
+
73
+ def name=(value)
74
+ @name = value
75
+ end
76
+
77
+ def default_foreign_key
78
+ @default_foreign_key || (@default_foreign_key = "#{Inflector.underscore(Inflector.singularize(name))}_id".freeze)
79
+ end
80
+
81
+ def to_sql
82
+ @to_sql || (@to_sql = @database.quote_table_name(name).freeze)
83
+ end
84
+
85
+ def inspect
86
+ "#<%s:0x%x @klass=%s, @name=%s, @columns=%s>" % [self.class.name, (object_id * 2), @klass.name, to_sql, @columns.inspect]
87
+ end
88
+
89
+ private
90
+ def reset_derived_columns!
91
+ @columns_hash.clear
92
+ @columns_by_column_name.clear
93
+ @key = nil
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,141 @@
1
+ module DataMapper
2
+ module Queries
3
+
4
+ class Conditions
5
+
6
+ def initialize(database, options)
7
+ @database, @options = database, options
8
+ @has_id = false
9
+ end
10
+
11
+ class NormalizationError < StandardError
12
+
13
+ attr_reader :inner_error
14
+
15
+ def initialize(clause, inner_error = nil)
16
+ @clause = clause
17
+ @inner_error = inner_error
18
+
19
+ message = "Failed to normalize clause: #{clause.inspect}"
20
+ message << ", Error: #{inner_error.inspect}" unless inner_error.nil?
21
+
22
+ super(message)
23
+ end
24
+
25
+ end
26
+
27
+ def normalize(clause, collector)
28
+ case clause
29
+ when Hash then
30
+ clause.each_pair do |k,v|
31
+ if k.kind_of?(Symbol::Operator)
32
+ if k.type == :select
33
+ k.options[:class] ||= @options[:class]
34
+
35
+ k.options[:select] ||= if k.value.to_s == @database[k.options[:class]].default_foreign_key
36
+ @database[k.options[:class]].key.column_name
37
+ else
38
+ k.value
39
+ end
40
+
41
+ sub_select = @database.select_statement(k.options.merge(v))
42
+ normalize(["#{@database[@options[:class]][k.value.to_sym].to_sql} IN ?", sub_select], collector)
43
+ else
44
+ @has_id = true if k.value == :id
45
+ op = case k.type
46
+ when :gt then '>'
47
+ when :gte then '>='
48
+ when :lt then '<'
49
+ when :lte then '<='
50
+ when :not then v.nil? ? 'IS NOT' : (v.kind_of?(Array) ? 'NOT IN' : '<>')
51
+ when :eql then v.nil? ? 'IS' : (v.kind_of?(Array) ? 'IN' : '=')
52
+ when :like then 'LIKE'
53
+ when :in then 'IN'
54
+ else raise ArgumentError.new('Operator type not supported')
55
+ end
56
+ normalize(["#{@database[@options[:class]][k.value.to_sym].to_sql} #{op} ?", v], collector)
57
+ end
58
+ else
59
+ @has_id = true if k == :id
60
+ case v
61
+ when Array then
62
+ normalize(["#{@database[@options[:class]][k.to_sym].to_sql} IN ?", v], collector)
63
+ when SelectStatement then
64
+ normalize(["#{@database[@options[:class]][k.to_sym].to_sql} IN ?", v], collector)
65
+ else
66
+ normalize(["#{@database[@options[:class]][k.to_sym].to_sql} = ?", v], collector)
67
+ end
68
+ end
69
+ end
70
+ when Array then
71
+ return collector if clause.empty?
72
+ @has_id = true if clause.first =~ /(^|\s|\`)id(\`|\s|\=|\<)/ && !clause[1].kind_of?(SelectStatement)
73
+ collector << escape(clause)
74
+ when String then
75
+ @has_id = true if clause =~ /(^|\s|\`)id(\`|\s|\=|\<)/
76
+ collector << clause
77
+ else raise NormalizationError.new(clause)
78
+ end
79
+
80
+ return collector
81
+ end
82
+
83
+ def escape(conditions)
84
+ clause = conditions.shift
85
+
86
+ clause.gsub(/\?/) do |x|
87
+ # Check if the condition is an in, clause.
88
+ case conditions.first
89
+ when Array then
90
+ '(' << conditions.shift.map { |c| @database.quote_value(c) }.join(', ') << ')'
91
+ when SelectStatement then
92
+ '(' << conditions.shift.to_sql << ')'
93
+ else
94
+ @database.quote_value(conditions.shift)
95
+ end
96
+ end
97
+ end
98
+
99
+ def has_id?
100
+ normalized_conditions
101
+ @has_id
102
+ end
103
+
104
+ def normalized_conditions
105
+
106
+ if @normalized_conditions.nil?
107
+ @normalized_conditions = []
108
+
109
+ table = @database[@options[:class]]
110
+ invalid_keys = nil
111
+
112
+ implicits = @options.reject do |k,v|
113
+ standard_key = DataMapper::Session::FIND_OPTIONS.include?(k)
114
+ (invalid_keys ||= []) << k if !standard_key && table[k.to_sym].nil?
115
+ standard_key
116
+ end
117
+
118
+ raise "Invalid options: #{invalid_keys.inspect}" unless invalid_keys.nil?
119
+
120
+ normalize(implicits, @normalized_conditions)
121
+
122
+ if @options.has_key?(:conditions)
123
+ normalize(@options[:conditions], @normalized_conditions)
124
+ end
125
+
126
+ end
127
+
128
+ return @normalized_conditions
129
+ end
130
+
131
+ def empty?
132
+ normalized_conditions.empty?
133
+ end
134
+
135
+ def to_a
136
+ normalized_conditions
137
+ end
138
+ end
139
+
140
+ end
141
+ end