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.
- data/CHANGELOG +2 -0
- data/MIT-LICENSE +22 -0
- data/README +1 -0
- data/example.rb +25 -0
- data/lib/data_mapper.rb +30 -0
- data/lib/data_mapper/adapters/abstract_adapter.rb +229 -0
- data/lib/data_mapper/adapters/mysql_adapter.rb +171 -0
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +189 -0
- data/lib/data_mapper/associations.rb +19 -0
- data/lib/data_mapper/associations/belongs_to_association.rb +111 -0
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +100 -0
- data/lib/data_mapper/associations/has_many_association.rb +101 -0
- data/lib/data_mapper/associations/has_one_association.rb +107 -0
- data/lib/data_mapper/base.rb +160 -0
- data/lib/data_mapper/callbacks.rb +47 -0
- data/lib/data_mapper/database.rb +134 -0
- data/lib/data_mapper/extensions/active_record_impersonation.rb +69 -0
- data/lib/data_mapper/extensions/callback_helpers.rb +35 -0
- data/lib/data_mapper/identity_map.rb +21 -0
- data/lib/data_mapper/loaded_set.rb +45 -0
- data/lib/data_mapper/mappings/column.rb +78 -0
- data/lib/data_mapper/mappings/schema.rb +28 -0
- data/lib/data_mapper/mappings/table.rb +99 -0
- data/lib/data_mapper/queries/conditions.rb +141 -0
- data/lib/data_mapper/queries/connection.rb +34 -0
- data/lib/data_mapper/queries/create_table_statement.rb +38 -0
- data/lib/data_mapper/queries/delete_statement.rb +17 -0
- data/lib/data_mapper/queries/drop_table_statement.rb +17 -0
- data/lib/data_mapper/queries/insert_statement.rb +29 -0
- data/lib/data_mapper/queries/reader.rb +42 -0
- data/lib/data_mapper/queries/result.rb +19 -0
- data/lib/data_mapper/queries/select_statement.rb +103 -0
- data/lib/data_mapper/queries/table_exists_statement.rb +17 -0
- data/lib/data_mapper/queries/truncate_table_statement.rb +17 -0
- data/lib/data_mapper/queries/update_statement.rb +25 -0
- data/lib/data_mapper/session.rb +240 -0
- data/lib/data_mapper/support/blank_slate.rb +3 -0
- data/lib/data_mapper/support/connection_pool.rb +117 -0
- data/lib/data_mapper/support/enumerable.rb +27 -0
- data/lib/data_mapper/support/inflector.rb +329 -0
- data/lib/data_mapper/support/proc.rb +69 -0
- data/lib/data_mapper/support/string.rb +23 -0
- data/lib/data_mapper/support/symbol.rb +91 -0
- data/lib/data_mapper/support/weak_hash.rb +46 -0
- data/lib/data_mapper/unit_of_work.rb +38 -0
- data/lib/data_mapper/validations/confirmation_validator.rb +55 -0
- data/lib/data_mapper/validations/contextual_validations.rb +50 -0
- data/lib/data_mapper/validations/format_validator.rb +85 -0
- data/lib/data_mapper/validations/formats/email.rb +78 -0
- data/lib/data_mapper/validations/generic_validator.rb +27 -0
- data/lib/data_mapper/validations/length_validator.rb +75 -0
- data/lib/data_mapper/validations/required_field_validator.rb +47 -0
- data/lib/data_mapper/validations/unique_validator.rb +65 -0
- data/lib/data_mapper/validations/validation_errors.rb +34 -0
- data/lib/data_mapper/validations/validation_helper.rb +60 -0
- data/performance.rb +156 -0
- data/profile_data_mapper.rb +18 -0
- data/rakefile.rb +80 -0
- data/spec/basic_finder.rb +67 -0
- data/spec/belongs_to.rb +47 -0
- data/spec/fixtures/animals.yaml +32 -0
- data/spec/fixtures/exhibits.yaml +90 -0
- data/spec/fixtures/fruit.yaml +6 -0
- data/spec/fixtures/people.yaml +15 -0
- data/spec/fixtures/zoos.yaml +20 -0
- data/spec/has_and_belongs_to_many.rb +25 -0
- data/spec/has_many.rb +34 -0
- data/spec/legacy.rb +14 -0
- data/spec/models/animal.rb +7 -0
- data/spec/models/exhibit.rb +6 -0
- data/spec/models/fruit.rb +6 -0
- data/spec/models/person.rb +7 -0
- data/spec/models/post.rb +4 -0
- data/spec/models/sales_person.rb +4 -0
- data/spec/models/zoo.rb +5 -0
- data/spec/new_record.rb +24 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/sub_select.rb +16 -0
- data/spec/symbolic_operators.rb +21 -0
- data/spec/validates_confirmation_of.rb +36 -0
- data/spec/validates_format_of.rb +61 -0
- data/spec/validates_length_of.rb +101 -0
- data/spec/validates_uniqueness_of.rb +45 -0
- data/spec/validations.rb +63 -0
- 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
|