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