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