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,107 @@
1
+ module DataMapper
2
+ module Associations
3
+
4
+ class HasOneAssociation
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
+
25
+ # Define the association instance method (i.e. Exhibit#zoo)
26
+ klass.class_eval <<-EOS
27
+ def create_#{association_name}(options = {})
28
+ #{association_name}_association.create(options)
29
+ end
30
+
31
+ def build_#{association_name}(options = {})
32
+ #{association_name}_association.build(options)
33
+ end
34
+
35
+ def #{association_name}
36
+ # Let the HasOneAssociation do the finding, just to keep things neat around here...
37
+ #{association_name}_association.find
38
+ end
39
+
40
+ def #{association_name}=(value)
41
+ #{association_name}_association.set(value)
42
+ end
43
+
44
+ private
45
+ def #{association_name}_association
46
+ @#{association_name} || (@#{association_name} = HasOneAssociation.new(self, "#{association_name}", #{options.inspect}))
47
+ end
48
+ EOS
49
+
50
+ end
51
+
52
+ def find
53
+ return @result unless @result.nil?
54
+
55
+ unless @instance.loaded_set.nil?
56
+
57
+ # Temp variable for the instance variable name.
58
+ setter_method = "#{@association_name}=".to_sym
59
+ instance_variable_name = "@#{foreign_key}".to_sym
60
+
61
+ set = @instance.loaded_set.instances.group_by { |instance| instance.key }
62
+
63
+ # Fetch the foreign objects for all instances in the current object's loaded-set.
64
+ @instance.session.find(@associated_class, :all, foreign_key => set.keys).each do |association|
65
+ set[association.instance_variable_get(instance_variable_name)].first.send(setter_method, association)
66
+ end
67
+ end
68
+
69
+ return @result
70
+ end
71
+
72
+ def create(options = {})
73
+ associated = @associated_class.new(options)
74
+ if associated.save
75
+ @instance.send("#{@associated_class.foreign_key}=", associated.id)
76
+ @result = associated
77
+ end
78
+ end
79
+
80
+ def build(options = {})
81
+ @result = @associated_class.new(options)
82
+ end
83
+
84
+ def set(val)
85
+ @result = val
86
+ end
87
+
88
+ def foreign_key
89
+ @foreign_key ||= (@options[:foreign_key] || @instance.session.mappings[@instance.class].default_foreign_key)
90
+ end
91
+
92
+ end
93
+
94
+ module HasOne
95
+ def self.included(base)
96
+ base.extend(ClassMethods)
97
+ end
98
+
99
+ module ClassMethods
100
+ def has_one(association_name, options = {})
101
+ HasOneAssociation.setup(self, association_name, options)
102
+ end
103
+ end
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,160 @@
1
+ require 'data_mapper/unit_of_work'
2
+ require 'data_mapper/extensions/active_record_impersonation'
3
+ require 'data_mapper/extensions/callback_helpers'
4
+ require 'data_mapper/validations/validation_helper'
5
+ require 'data_mapper/associations'
6
+
7
+ module DataMapper
8
+
9
+ class Base
10
+
11
+ # This probably needs to be protected
12
+ attr_accessor :loaded_set
13
+
14
+ include UnitOfWork
15
+ include Extensions::ActiveRecordImpersonation
16
+ include Extensions::CallbackHelpers
17
+ include Extensions::ValidationHelper
18
+ include Associations
19
+
20
+ def self.inherited(klass)
21
+ klass.send(:undef_method, :id)
22
+
23
+ # When this class is sub-classed, copy the declared columns.
24
+ klass.class_eval do
25
+ def self.inherited(subclass)
26
+
27
+ database.schema[subclass.superclass].columns.each do |c|
28
+ subclass.property(c.name, c.type, c.options)
29
+ subclass.before_create do
30
+ @type = self.class
31
+ end if c.name == :type
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+
38
+ def self.set_table_name(value)
39
+ database.schema[self].name = value
40
+ end
41
+
42
+ def initialize(details = nil)
43
+
44
+ unless details.nil?
45
+ details.reject do |key, value|
46
+ protected_attribute? key
47
+ end.each_pair do |key, value|
48
+ instance_variable_set("@#{key}", value)
49
+ end
50
+ end
51
+ end
52
+
53
+ def self.property(name, type, options = {})
54
+ mapping = database.schema[self].add_column(name, type, options)
55
+ property_getter(name, mapping)
56
+ property_setter(name, mapping)
57
+ return name
58
+ end
59
+
60
+ def self.property_getter(name, mapping)
61
+ if mapping.lazy?
62
+ class_eval <<-EOS
63
+ def #{name}
64
+ lazy_load!("#{name}")
65
+ end
66
+ EOS
67
+ else
68
+ class_eval("def #{name}; #{mapping.instance_variable_name} end")
69
+ end
70
+ end
71
+
72
+ def self.property_setter(name, mapping)
73
+ if mapping.lazy?
74
+ class_eval <<-EOS
75
+ def #{name.to_s.sub(/\?$/, '')}=(value)
76
+ class << self;
77
+ attr_accessor #{name.inspect}
78
+ end
79
+ @#{name} = value
80
+ end
81
+ EOS
82
+ else
83
+ class_eval("def #{name.to_s.sub(/\?$/, '')}=(value); #{mapping.instance_variable_name} = value end")
84
+ end
85
+ end
86
+
87
+ def lazy_load!(name)
88
+ (class << self; self end).send(:attr_accessor, name)
89
+
90
+ column = session.schema[self.class][name.to_sym]
91
+
92
+ # If the value is already loaded, then we don't need to do it again.
93
+ value = instance_variable_get(column.instance_variable_name)
94
+ return value unless value.nil?
95
+
96
+ session.find(self.class, :all, :select => [:id, name], :reload => true, :id => loaded_set.instances.map(&:id)).each do |instance|
97
+ (class << self; self end).send(:attr_accessor, name)
98
+ end
99
+
100
+ instance_variable_get(column.instance_variable_name)
101
+ end
102
+
103
+ def attributes
104
+ session.schema[self.class].columns.inject({}) do |values, column|
105
+ values[column.name] = instance_variable_get(column.instance_variable_name); values
106
+ end
107
+ end
108
+
109
+ def attributes=(values_hash)
110
+ values_hash.reject do |key, value|
111
+ protected_attribute? key
112
+ end.each_pair do |key, value|
113
+ symbolic_instance_variable_set(key, value)
114
+ end
115
+ end
116
+
117
+ def protected_attribute?(key)
118
+ self.class.protected_attributes.include?(key.kind_of?(Symbol) ? key : key.to_sym)
119
+ end
120
+
121
+ def self.protected_attributes
122
+ @protected_attributes ||= []
123
+ end
124
+
125
+ def self.protect(*keys)
126
+ keys.each { |key| protected_attributes << key.to_sym }
127
+ end
128
+
129
+ def self.foreign_key
130
+ Inflector.underscore(self.name) + "_id"
131
+ end
132
+
133
+ def inspect
134
+ inspected_attributes = attributes.map { |k,v| "@#{k}=#{v.inspect}" }
135
+
136
+ instance_variables.each do |name|
137
+ if instance_variable_get(name).kind_of?(Associations::HasManyAssociation)
138
+ inspected_attributes << "#{name}=#{instance_variable_get(name).inspect}"
139
+ end
140
+ end
141
+
142
+ "#<%s:0x%x @new_record=%s, %s>" % [self.class.name, (object_id * 2), new_record?, inspected_attributes.join(', ')]
143
+ end
144
+
145
+ def session=(value)
146
+ @session = value
147
+ end
148
+
149
+ def session
150
+ @session ||= database
151
+ end
152
+
153
+ def key
154
+ key_column = session.schema[self.class].key
155
+ key_column.type_cast_value(instance_variable_get(key_column.instance_variable_name))
156
+ end
157
+
158
+ end
159
+
160
+ end
@@ -0,0 +1,47 @@
1
+ module DataMapper
2
+
3
+ class Callbacks
4
+
5
+ EVENTS = [
6
+ :before_materialize, :after_materialize,
7
+ :before_save, :after_save,
8
+ :before_create, :after_create,
9
+ :before_update, :after_update,
10
+ :before_destroy, :after_destroy,
11
+ :before_validate, :after_validate
12
+ ]
13
+
14
+ def initialize
15
+ @callbacks = Hash.new { |h,k| h[k.to_sym] = [] }
16
+ end
17
+
18
+ alias ruby_method_missing method_missing
19
+ def method_missing(sym, *args)
20
+ if EVENTS.include?(sym)
21
+ self.class.send(:define_method, sym) { @callbacks[sym] }
22
+ return send(sym)
23
+ elsif sym.to_s =~ /^execute_(\w+)/ && EVENTS.include?($1.to_sym)
24
+ return execute(args.first, $1.to_sym)
25
+ end
26
+
27
+ super
28
+ end
29
+
30
+ def execute(name, instance)
31
+ @callbacks[name].each do |callback|
32
+ if callback.kind_of?(String)
33
+ instance.instance_eval(callback)
34
+ else
35
+ instance.instance_eval(&callback)
36
+ end
37
+ end
38
+ end
39
+
40
+ def add(name, string = nil, &block)
41
+ callback = send(name)
42
+ raise ArgumentError.new("You didn't specify a callback in either string or block form.") if string.nil? && block.nil?
43
+ callback << (string.nil? ? block : string)
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,134 @@
1
+ require 'logger'
2
+ require 'data_mapper/session'
3
+ require 'data_mapper/mappings/schema'
4
+
5
+ # Delegates to DataMapper::database.
6
+ # Will not overwrite if a method of the same name is pre-defined.
7
+ def database(name = :default, &block)
8
+ DataMapper::database(name, &block)
9
+ end unless methods.include?(:database)
10
+
11
+ module DataMapper
12
+
13
+ # Block Syntax:
14
+ # Pushes the named database onto the context-stack,
15
+ # yields a new session, and pops the context-stack.
16
+ # Non-Block Syntax:
17
+ # Returns the current session, or if there is none,
18
+ # a new Session.
19
+ def self.database(name = :default)
20
+ unless block_given?
21
+ Database.context.last || Session.new(Database[name])
22
+ else
23
+ Database.context.push(Session.new(Database[name]))
24
+ yield Database.context.last
25
+ Database.context.pop
26
+ end
27
+ end
28
+
29
+ class DatabaseError < StandardError
30
+ attr_accessor :options
31
+ end
32
+
33
+ class Database
34
+
35
+ @databases = {}
36
+ @context = []
37
+
38
+ def self.[](name)
39
+ @databases[name]
40
+ end
41
+
42
+ def self.context
43
+ @context
44
+ end
45
+
46
+ def self.setup(name = :default, &initializer)
47
+ current = self.new(name)
48
+ current.instance_eval(&initializer)
49
+ @databases[name] = current
50
+ end
51
+
52
+ def initialize(name)
53
+ @name = name
54
+ end
55
+
56
+ # Shortcut to adapter.class::Queries::FooStatement.new
57
+ def method_missing(sym, *args)
58
+ return super if sym.to_s !~ /_statement$/
59
+ @adapter.class::Queries.const_get(Inflector.classify(sym.to_s)).new(self, *args)
60
+ end
61
+
62
+ def syntax(token)
63
+ @adapter.class::SYNTAX[token]
64
+ end
65
+
66
+ def [](klass_or_table_name)
67
+ schema[klass_or_table_name]
68
+ end
69
+
70
+ def schema
71
+ @schema ||= Mappings::Schema.new(self)
72
+ end
73
+
74
+ class ConditionEscapeError < StandardError; end
75
+
76
+ attr_reader :name
77
+
78
+ def adapter(value = nil)
79
+ return @adapter if value.nil?
80
+
81
+ raise ArgumentError.new('The adapter is readonly after being set') unless @adapter.nil?
82
+
83
+ require "data_mapper/adapters/#{Inflector.underscore(value)}_adapter"
84
+ adapter_class = Adapters::const_get(Inflector.classify(value) + "Adapter")
85
+
86
+ (class << self; self end).send(:include, adapter_class::Quoting)
87
+ (class << self; self end).send(:include, adapter_class::Coersion)
88
+
89
+ @adapter = adapter_class.new(self)
90
+ end
91
+
92
+ def host(value = nil); value.nil? ? (@host || 'localhost') : @host = value end
93
+ def database(value = nil); value.nil? ? @database : @database = value end
94
+ def username(value = nil); value.nil? ? @username : @username = value end
95
+ def password(value = nil); value.nil? ? (@password || '') : @password = value end
96
+
97
+ def log(value = nil)
98
+ @log = value unless value.nil?
99
+
100
+ if @log.nil?
101
+ @log = log_stream.nil? ? Logger.new(nil) : Logger.new(log_stream, File::WRONLY | File::APPEND | File::CREAT)
102
+ @log.level = log_level || Logger::WARN
103
+ at_exit { @log.close }
104
+ end
105
+
106
+ @log
107
+ end
108
+
109
+ def log_level(value = nil)
110
+ return @log_level if value.nil?
111
+ @log_level = value
112
+ end
113
+
114
+ def log_stream(value = nil)
115
+ return @log_stream if value.nil?
116
+ @log_stream = value
117
+ end
118
+
119
+ def connection
120
+ @adapter.connection do |db|
121
+ results = yield(db)
122
+ end
123
+ end
124
+
125
+ def query(sql)
126
+ connection { |db| db.query(sql) }
127
+ end
128
+
129
+ def execute(sql)
130
+ connection { |db| db.execute(sql) }
131
+ end
132
+ end
133
+
134
+ end