datamapper 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,189 @@
1
+ require 'data_mapper/adapters/abstract_adapter'
2
+ require 'data_mapper/support/connection_pool'
3
+
4
+ require 'sqlite3'
5
+
6
+ module DataMapper
7
+ module Adapters
8
+
9
+ class Sqlite3Adapter < AbstractAdapter
10
+
11
+ def initialize(configuration)
12
+ super
13
+ @connections = Support::ConnectionPool.new { Queries::Connection.new(@configuration) }
14
+ end
15
+
16
+ def connection
17
+ raise ArgumentError.new('Sqlite3Adapter#connection requires a block-parameter') unless block_given?
18
+ begin
19
+ @connections.hold { |connection| yield connection }
20
+ rescue SQLite3::Exception => sle
21
+
22
+ @configuration.log.fatal(sle)
23
+
24
+ @connections.available_connections.each do |sock|
25
+ begin
26
+ sock.close
27
+ rescue => se
28
+ @configuration.log.error(se)
29
+ end
30
+ end
31
+
32
+ @connections.available_connections.clear
33
+ raise sle
34
+ end
35
+ end
36
+
37
+ TYPES.merge!({
38
+ :integer => 'INTEGER'.freeze,
39
+ :string => 'TEXT'.freeze,
40
+ :text => 'TEXT'.freeze,
41
+ :class => 'TEXT'.freeze
42
+ })
43
+
44
+ module Coersion
45
+
46
+ def type_cast_boolean(value)
47
+ case value
48
+ when TrueClass, FalseClass then value
49
+ when "1", "true", "TRUE" then true
50
+ when "0", nil then false
51
+ else "Can't type-cast #{value.inspect} to a boolean"
52
+ end
53
+ end
54
+
55
+ def type_cast_datetime(value)
56
+ case value
57
+ when DateTime then value
58
+ when Date then DateTime.new(value)
59
+ when String then DateTime::parse(value)
60
+ else "Can't type-cast #{value.inspect} to a datetime"
61
+ end
62
+ end
63
+
64
+ end # module Coersion
65
+
66
+ module Queries
67
+
68
+ class Connection
69
+
70
+ def initialize(database)
71
+ @database = database
72
+ @dbh = SQLite3::Database.new(database.database)
73
+ database.log.debug("Initializing Connection for Database[#{database.name}]")
74
+ super(database.log)
75
+ end
76
+
77
+ def execute(statement)
78
+ send_query(statement)
79
+ Result.new(@dbh.total_changes, @dbh.last_insert_row_id)
80
+ end
81
+
82
+ def query(statement)
83
+ Reader.new(send_query(statement))
84
+ end
85
+
86
+ def close
87
+ @dbh.close
88
+ end
89
+
90
+ private
91
+ def send_query(statement)
92
+ sql = statement.respond_to?(:to_sql) ? statement.to_sql : statement
93
+ log.debug("Database[#{@database.name}] => #{sql}")
94
+ @dbh.query(sql)
95
+ end
96
+
97
+ end # class Connection
98
+
99
+ class Reader
100
+
101
+ include Enumerable
102
+
103
+ def initialize(results)
104
+ @results = results
105
+ @columns = {}
106
+ @results.columns.each_with_index do |name, index|
107
+ @columns[name] = index
108
+ end
109
+ end
110
+
111
+ def eof?
112
+ @results.eof?
113
+ end
114
+
115
+ def records_affected
116
+ @results.entries.size
117
+ end
118
+
119
+ def next
120
+ @current_row = @results.next
121
+ self
122
+ end
123
+
124
+ def each
125
+ until self.next.eof?
126
+ yield(self)
127
+ end
128
+ end
129
+
130
+ def [](column)
131
+ index = @columns[column]
132
+ return nil if index.nil? || @current_row.nil?
133
+ @current_row[index]
134
+ end
135
+
136
+ def each_pair
137
+ @columns.each_pair do |column_name, index|
138
+ yield(column_name, @current_row.nil? ? nil : @current_row[index])
139
+ end
140
+ end
141
+
142
+ def close
143
+ @results.close
144
+ end
145
+
146
+ end # class Reader
147
+
148
+ class TableExistsStatement
149
+ def to_sql
150
+ "SELECT name FROM sqlite_master WHERE type = \"table\" AND name = #{@database.quote_value(@database[@klass].name)}"
151
+ end
152
+ end # class TableExistsStatement
153
+
154
+ class CreateTableStatement
155
+ def to_sql
156
+ table = @database[@klass]
157
+
158
+ sql = "CREATE TABLE " << table.to_sql
159
+
160
+ sql << " (" << table.columns.map do |column|
161
+ column_long_form(column)
162
+ end.join(', ') << ")"
163
+
164
+ return sql
165
+ end
166
+
167
+ def column_long_form(column)
168
+ long_form = "#{column.to_sql} #{@database.adapter.class::TYPES[column.type] || column.type}"
169
+
170
+ long_form << " NOT NULL" unless column.nullable?
171
+ long_form << " PRIMARY KEY" if column.key?
172
+ long_form << " default #{column.options[:default]}" if column.options.has_key?(:default)
173
+
174
+ return long_form
175
+ end
176
+ end # class CreateTableStatement
177
+
178
+ class TruncateTableStatement
179
+ def to_sql
180
+ "DELETE FROM " << @database[@klass].to_sql
181
+ end
182
+ end # class TruncateTableStatement
183
+
184
+ end # module Queries
185
+
186
+ end # class Sqlite3Adapter
187
+
188
+ end # module Adapters
189
+ end # module DataMapper
@@ -0,0 +1,19 @@
1
+ require 'data_mapper/associations/has_many_association'
2
+ require 'data_mapper/associations/belongs_to_association'
3
+ require 'data_mapper/associations/has_one_association'
4
+ require 'data_mapper/associations/has_and_belongs_to_many_association'
5
+
6
+ module DataMapper
7
+ module Associations
8
+
9
+ def self.included(base)
10
+ base.class_eval do
11
+ include DataMapper::Associations::HasMany
12
+ include DataMapper::Associations::BelongsTo
13
+ include DataMapper::Associations::HasOne
14
+ include DataMapper::Associations::HasAndBelongsToMany
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,111 @@
1
+ module DataMapper
2
+ module Associations
3
+
4
+ class BelongsToAssociation
5
+
6
+ def initialize(instance, association_name, options)
7
+ @instance = instance
8
+ @association_name = association_name
9
+ @options = options
10
+
11
+ @associated_class = if options.has_key?(:class) || options.has_key?(:class_name)
12
+ associated_class_name = (options[:class] || options[:class_name])
13
+ if associated_class_name.kind_of?(String)
14
+ Kernel.const_get(Inflector.classify(associated_class_name))
15
+ else
16
+ associated_class_name
17
+ end
18
+ else
19
+ Kernel.const_get(Inflector.classify(association_name))
20
+ end
21
+ end
22
+
23
+ def self.setup(klass, association_name, options)
24
+ foreign_key = options[:foreign_key] || ("#{association_name}_id")
25
+ klass.property foreign_key.to_sym, :integer
26
+
27
+ # Define the association instance method (i.e. Exhibit#zoo)
28
+ klass.class_eval <<-EOS
29
+ def create_#{association_name}(options = {})
30
+ #{association_name}_association.create(options)
31
+ end
32
+
33
+ def build_#{association_name}(options = {})
34
+ #{association_name}_association.build(options)
35
+ end
36
+
37
+ def #{association_name}
38
+ # Let the BelongsToAssociation do the finding, just to keep things neat around here...
39
+ #{association_name}_association.find
40
+ end
41
+
42
+ def #{association_name}=(value)
43
+ #{association_name}_association.set(value)
44
+ end
45
+
46
+ private
47
+ def #{association_name}_association
48
+ @#{association_name} || (@#{association_name} = BelongsToAssociation.new(self, "#{association_name}", #{options.inspect}))
49
+ end
50
+ EOS
51
+
52
+ end
53
+
54
+ def find
55
+ return @result unless @result.nil?
56
+
57
+ unless @instance.loaded_set.nil?
58
+
59
+ # Temp variable for the instance variable name.
60
+ setter_method = "#{@association_name}=".to_sym
61
+ instance_variable_name = "@#{foreign_key}".to_sym
62
+
63
+ set = @instance.loaded_set.instances.group_by { |instance| instance.instance_variable_get(instance_variable_name) }
64
+
65
+ # Fetch the foreign objects for all instances in the current object's loaded-set.
66
+ @instance.session.find(@associated_class, :all, :id => set.keys).each do |owner|
67
+ set[owner.key].each do |instance|
68
+ instance.send(setter_method, owner)
69
+ end
70
+ end
71
+ end
72
+
73
+ return @result
74
+ end
75
+
76
+ def create(options = {})
77
+ associated = @associated_class.new(options)
78
+ if associated.save
79
+ @instance.send("#{@associated_class.foreign_key}=", associated.id)
80
+ @result = associated
81
+ end
82
+ end
83
+
84
+ def build(options = {})
85
+ @result = @associated_class.new(options)
86
+ end
87
+
88
+ def set(val)
89
+ @result = val
90
+ end
91
+
92
+ def foreign_key
93
+ @foreign_key ||= (@options[:foreign_key] || @instance.session.schema[@associated_class].default_foreign_key)
94
+ end
95
+
96
+ end
97
+
98
+ module BelongsTo
99
+ def self.included(base)
100
+ base.extend(ClassMethods)
101
+ end
102
+
103
+ module ClassMethods
104
+ def belongs_to(association_name, options = {})
105
+ BelongsToAssociation.setup(self, association_name, options)
106
+ end
107
+ end
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,100 @@
1
+ module DataMapper
2
+ module Associations
3
+
4
+ class HasAndBelongsToManyAssociation
5
+ include Enumerable
6
+
7
+ def initialize(instance, association_name, options)
8
+ @instance = instance
9
+ @association_name = association_name.to_sym
10
+ @options = options
11
+
12
+ @associated_class = if options.has_key?(:class) || options.has_key?(:class_name)
13
+ associated_class_name = (options[:class] || options[:class_name])
14
+ if associated_class_name.kind_of?(String)
15
+ Kernel.const_get(Inflector.classify(associated_class_name))
16
+ else
17
+ associated_class_name
18
+ end
19
+ else
20
+ Kernel.const_get(Inflector.classify(association_name))
21
+ end
22
+
23
+ @join_table_name = @options.has_key?(:join_table_name) ? @options[:join_table_name] : [Inflector.tableize(@instance.class.name), Inflector.tableize(@associated_class.name)].sort.join('_')
24
+ end
25
+
26
+ def self.setup(klass, association_name, options)
27
+
28
+ # Define the association instance method (i.e. Project#tasks)
29
+ klass.class_eval <<-EOS
30
+ def #{association_name}
31
+ @#{association_name} || (@#{association_name} = HasAndBelongsToManyAssociation.new(self, "#{association_name}", #{options.inspect}))
32
+ end
33
+ EOS
34
+
35
+ end
36
+
37
+ def each
38
+ find.each { |item| yield item }
39
+ end
40
+
41
+ def size
42
+ entries.size
43
+ end
44
+ alias length size
45
+
46
+ def [](key)
47
+ entries[key]
48
+ end
49
+
50
+ def empty?
51
+ entries.empty?
52
+ end
53
+
54
+ def find
55
+ return @results unless @results.nil?
56
+
57
+ unless @instance.loaded_set.nil?
58
+
59
+ # Temp variable for the instance variable name.
60
+ instance_variable_name = "@#{foreign_key}".to_sym
61
+
62
+ @results = @instance.session.find(@associated_class, :all,
63
+ :id.select => { :table => @join_table_name, foreign_key.to_sym => @instance.key }
64
+ ) do |animal_id, ref|
65
+ @instance.load_set.find { |x| x.id == animal_id }.exhibits << ref
66
+ end
67
+
68
+ end
69
+
70
+ return @results || (@results = [])
71
+ end
72
+
73
+ def set(results)
74
+ @results = results
75
+ end
76
+
77
+ def inspect
78
+ @results.inspect
79
+ end
80
+
81
+ def foreign_key
82
+ @foreign_key || (@foreign_key = (@options[:foreign_key] || @instance.session.mappings[@instance.class].default_foreign_key))
83
+ end
84
+
85
+ end
86
+
87
+ module HasAndBelongsToMany
88
+ def self.included(base)
89
+ base.extend(ClassMethods)
90
+ end
91
+
92
+ module ClassMethods
93
+ def has_and_belongs_to_many(association_name, options = {})
94
+ HasAndBelongsToManyAssociation.setup(self, association_name, options)
95
+ end
96
+ end
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,101 @@
1
+ module DataMapper
2
+ module Associations
3
+
4
+ class HasManyAssociation
5
+ include Enumerable
6
+
7
+ def initialize(instance, association_name, options)
8
+ @instance = instance
9
+ @association_name = association_name.to_sym
10
+ @options = options
11
+
12
+ @associated_class = if options.has_key?(:class) || options.has_key?(:class_name)
13
+ associated_class_name = (options[:class] || options[:class_name])
14
+ if associated_class_name.kind_of?(String)
15
+ Kernel.const_get(Inflector.classify(associated_class_name))
16
+ else
17
+ associated_class_name
18
+ end
19
+ else
20
+ Kernel.const_get(Inflector.classify(association_name))
21
+ end
22
+ end
23
+
24
+ def self.setup(klass, association_name, options)
25
+
26
+ # Define the association instance method (i.e. Project#tasks)
27
+ klass.class_eval <<-EOS
28
+ def #{association_name}
29
+ @#{association_name} || (@#{association_name} = HasManyAssociation.new(self, "#{association_name}", #{options.inspect}))
30
+ end
31
+ EOS
32
+
33
+ end
34
+
35
+ def each
36
+ find.each { |item| yield item }
37
+ end
38
+
39
+ def size
40
+ entries.size
41
+ end
42
+ alias length size
43
+
44
+ def [](key)
45
+ entries[key]
46
+ end
47
+
48
+ def empty?
49
+ entries.empty?
50
+ end
51
+
52
+ def find
53
+ return @results unless @results.nil?
54
+
55
+ unless @instance.loaded_set.nil?
56
+
57
+ # Temp variable for the instance variable name.
58
+ instance_variable_name = "@#{foreign_key}".to_sym
59
+
60
+ set = @instance.loaded_set.instances.group_by { |instance| instance.key }
61
+
62
+ # Fetch the foreign objects for all instances in the current object's loaded-set.
63
+ @instance.session.find(@associated_class, :all, foreign_key.to_sym => set.keys).group_by do |association|
64
+ association.instance_variable_get(instance_variable_name)
65
+ end.each_pair do |id, results|
66
+ set[id].first.send(@association_name).set(results)
67
+ end
68
+
69
+ end
70
+
71
+ return @results ||= []
72
+ end
73
+
74
+ def set(results)
75
+ @results = results
76
+ end
77
+
78
+ def inspect
79
+ @results.inspect
80
+ end
81
+
82
+ def foreign_key
83
+ @foreign_key || (@foreign_key = (@options[:foreign_key] || @instance.session.schema[@instance.class].default_foreign_key))
84
+ end
85
+
86
+ end
87
+
88
+ module HasMany
89
+ def self.included(base)
90
+ base.extend(ClassMethods)
91
+ end
92
+
93
+ module ClassMethods
94
+ def has_many(association_name, options = {})
95
+ HasManyAssociation.setup(self, association_name, options)
96
+ end
97
+ end
98
+ end
99
+
100
+ end
101
+ end