datamapper 0.3.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +0 -0
- data/Manifest.txt +5 -0
- data/README.txt +11 -0
- data/Rakefile +70 -0
- data/lib/datamapper.rb +8 -0
- metadata +152 -319
- data/CHANGELOG +0 -145
- data/FAQ +0 -96
- data/MIT-LICENSE +0 -22
- data/QUICKLINKS +0 -12
- data/README +0 -105
- data/environment.rb +0 -62
- data/example.rb +0 -156
- data/lib/data_mapper.rb +0 -88
- data/lib/data_mapper/adapters/abstract_adapter.rb +0 -43
- data/lib/data_mapper/adapters/data_object_adapter.rb +0 -480
- data/lib/data_mapper/adapters/mysql_adapter.rb +0 -72
- data/lib/data_mapper/adapters/postgresql_adapter.rb +0 -258
- data/lib/data_mapper/adapters/sql/coersion.rb +0 -134
- data/lib/data_mapper/adapters/sql/commands/load_command.rb +0 -545
- data/lib/data_mapper/adapters/sql/mappings/associations_set.rb +0 -34
- data/lib/data_mapper/adapters/sql/mappings/column.rb +0 -279
- data/lib/data_mapper/adapters/sql/mappings/conditions.rb +0 -172
- data/lib/data_mapper/adapters/sql/mappings/schema.rb +0 -60
- data/lib/data_mapper/adapters/sql/mappings/table.rb +0 -459
- data/lib/data_mapper/adapters/sql/quoting.rb +0 -24
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +0 -159
- data/lib/data_mapper/associations.rb +0 -106
- data/lib/data_mapper/associations/belongs_to_association.rb +0 -160
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +0 -437
- data/lib/data_mapper/associations/has_many_association.rb +0 -283
- data/lib/data_mapper/associations/has_n_association.rb +0 -143
- data/lib/data_mapper/associations/reference.rb +0 -47
- data/lib/data_mapper/attributes.rb +0 -73
- data/lib/data_mapper/auto_migrations.rb +0 -36
- data/lib/data_mapper/base.rb +0 -17
- data/lib/data_mapper/callbacks.rb +0 -107
- data/lib/data_mapper/context.rb +0 -112
- data/lib/data_mapper/database.rb +0 -234
- data/lib/data_mapper/dependency_queue.rb +0 -28
- data/lib/data_mapper/embedded_value.rb +0 -145
- data/lib/data_mapper/identity_map.rb +0 -47
- data/lib/data_mapper/is/tree.rb +0 -121
- data/lib/data_mapper/migration.rb +0 -155
- data/lib/data_mapper/persistence.rb +0 -852
- data/lib/data_mapper/property.rb +0 -310
- data/lib/data_mapper/query.rb +0 -164
- data/lib/data_mapper/support/blank.rb +0 -35
- data/lib/data_mapper/support/connection_pool.rb +0 -117
- data/lib/data_mapper/support/enumerable.rb +0 -35
- data/lib/data_mapper/support/errors.rb +0 -16
- data/lib/data_mapper/support/inflector.rb +0 -265
- data/lib/data_mapper/support/object.rb +0 -54
- data/lib/data_mapper/support/serialization.rb +0 -96
- data/lib/data_mapper/support/silence.rb +0 -10
- data/lib/data_mapper/support/string.rb +0 -72
- data/lib/data_mapper/support/struct.rb +0 -7
- data/lib/data_mapper/support/symbol.rb +0 -82
- data/lib/data_mapper/support/typed_set.rb +0 -65
- data/lib/data_mapper/types/base.rb +0 -44
- data/lib/data_mapper/types/string.rb +0 -34
- data/lib/data_mapper/validatable_extensions/errors.rb +0 -12
- data/lib/data_mapper/validatable_extensions/macros.rb +0 -7
- data/lib/data_mapper/validatable_extensions/validatable_instance_methods.rb +0 -62
- data/lib/data_mapper/validatable_extensions/validation_base.rb +0 -18
- data/lib/data_mapper/validatable_extensions/validations/formats/email.rb +0 -43
- data/lib/data_mapper/validatable_extensions/validations/validates_acceptance_of.rb +0 -7
- data/lib/data_mapper/validatable_extensions/validations/validates_confirmation_of.rb +0 -7
- data/lib/data_mapper/validatable_extensions/validations/validates_each.rb +0 -7
- data/lib/data_mapper/validatable_extensions/validations/validates_format_of.rb +0 -28
- data/lib/data_mapper/validatable_extensions/validations/validates_length_of.rb +0 -15
- data/lib/data_mapper/validatable_extensions/validations/validates_numericality_of.rb +0 -7
- data/lib/data_mapper/validatable_extensions/validations/validates_presence_of.rb +0 -7
- data/lib/data_mapper/validatable_extensions/validations/validates_true_for.rb +0 -7
- data/lib/data_mapper/validatable_extensions/validations/validates_uniqueness_of.rb +0 -40
- data/lib/data_mapper/validations.rb +0 -20
- data/lib/data_mapper/validations/number_validator.rb +0 -40
- data/lib/data_mapper/validations/string_validator.rb +0 -20
- data/lib/data_mapper/validations/validator.rb +0 -13
- data/performance.rb +0 -307
- data/plugins/can_has_sphinx/LICENSE +0 -23
- data/plugins/can_has_sphinx/README +0 -4
- data/plugins/can_has_sphinx/REVISION +0 -1
- data/plugins/can_has_sphinx/Rakefile +0 -22
- data/plugins/can_has_sphinx/init.rb +0 -1
- data/plugins/can_has_sphinx/install.rb +0 -1
- data/plugins/can_has_sphinx/lib/acts_as_sphinx.rb +0 -123
- data/plugins/can_has_sphinx/lib/sphinx.rb +0 -460
- data/plugins/can_has_sphinx/scripts/sphinx.sh +0 -47
- data/plugins/can_has_sphinx/tasks/acts_as_sphinx_tasks.rake +0 -41
- data/profile_data_mapper.rb +0 -40
- data/rakefile.rb +0 -159
- data/spec/acts_as_tree_spec.rb +0 -67
- data/spec/adapters/data_object_adapter_spec.rb +0 -31
- data/spec/associations/belongs_to_association_spec.rb +0 -98
- data/spec/associations/has_and_belongs_to_many_association_spec.rb +0 -377
- data/spec/associations/has_many_association_spec.rb +0 -337
- data/spec/attributes_spec.rb +0 -52
- data/spec/auto_migrations_spec.rb +0 -101
- data/spec/callbacks_spec.rb +0 -186
- data/spec/can_has_sphinx.rb +0 -5
- data/spec/coersion_spec.rb +0 -41
- data/spec/column_spec.rb +0 -114
- data/spec/count_command_spec.rb +0 -45
- data/spec/database_spec.rb +0 -18
- data/spec/dataobjects_spec.rb +0 -27
- data/spec/delete_command_spec.rb +0 -11
- data/spec/dependency_spec.rb +0 -29
- data/spec/embedded_value_spec.rb +0 -161
- data/spec/fixtures/animals.yaml +0 -33
- data/spec/fixtures/animals_exhibits.yaml +0 -2
- data/spec/fixtures/careers.yaml +0 -5
- data/spec/fixtures/comments.yaml +0 -1
- data/spec/fixtures/exhibits.yaml +0 -90
- data/spec/fixtures/fruit.yaml +0 -6
- data/spec/fixtures/people.yaml +0 -37
- data/spec/fixtures/posts.yaml +0 -3
- data/spec/fixtures/projects.yaml +0 -13
- data/spec/fixtures/sections.yaml +0 -5
- data/spec/fixtures/serializers.yaml +0 -6
- data/spec/fixtures/tasks.yaml +0 -6
- data/spec/fixtures/tasks_tasks.yaml +0 -2
- data/spec/fixtures/tomatoes.yaml +0 -1
- data/spec/fixtures/users.yaml +0 -1
- data/spec/fixtures/zoos.yaml +0 -24
- data/spec/is_a_tree_spec.rb +0 -149
- data/spec/legacy_spec.rb +0 -16
- data/spec/load_command_spec.rb +0 -322
- data/spec/magic_columns_spec.rb +0 -26
- data/spec/migration_spec.rb +0 -267
- data/spec/mock_adapter.rb +0 -20
- data/spec/models/animal.rb +0 -12
- data/spec/models/candidate.rb +0 -8
- data/spec/models/career.rb +0 -7
- data/spec/models/chain.rb +0 -8
- data/spec/models/comment.rb +0 -6
- data/spec/models/exhibit.rb +0 -14
- data/spec/models/fence.rb +0 -7
- data/spec/models/fruit.rb +0 -8
- data/spec/models/job.rb +0 -8
- data/spec/models/person.rb +0 -30
- data/spec/models/post.rb +0 -14
- data/spec/models/project.rb +0 -41
- data/spec/models/sales_person.rb +0 -5
- data/spec/models/section.rb +0 -8
- data/spec/models/serializer.rb +0 -5
- data/spec/models/task.rb +0 -9
- data/spec/models/tomato.rb +0 -27
- data/spec/models/user.rb +0 -12
- data/spec/models/zoo.rb +0 -13
- data/spec/natural_key_spec.rb +0 -36
- data/spec/paranoia_spec.rb +0 -38
- data/spec/persistence_spec.rb +0 -479
- data/spec/postgres_spec.rb +0 -96
- data/spec/property_spec.rb +0 -151
- data/spec/query_spec.rb +0 -77
- data/spec/save_command_spec.rb +0 -94
- data/spec/schema_spec.rb +0 -8
- data/spec/serialize_spec.rb +0 -19
- data/spec/single_table_inheritance_spec.rb +0 -43
- data/spec/spec_helper.rb +0 -45
- data/spec/support/blank_spec.rb +0 -8
- data/spec/support/inflector_spec.rb +0 -41
- data/spec/support/object_spec.rb +0 -9
- data/spec/support/serialization_spec.rb +0 -61
- data/spec/support/silence_spec.rb +0 -15
- data/spec/support/string_spec.rb +0 -7
- data/spec/support/struct_spec.rb +0 -12
- data/spec/support/typed_set_spec.rb +0 -66
- data/spec/symbolic_operators_spec.rb +0 -27
- data/spec/table_spec.rb +0 -79
- data/spec/types/string.rb +0 -81
- data/spec/validates_confirmation_of_spec.rb +0 -55
- data/spec/validates_format_of_spec.rb +0 -78
- data/spec/validates_length_of_spec.rb +0 -117
- data/spec/validates_uniqueness_of_spec.rb +0 -92
- data/spec/validations/number_validator.rb +0 -59
- data/spec/validations/string_validator.rb +0 -14
- data/spec/validations_spec.rb +0 -141
- data/tasks/fixtures.rb +0 -53
data/lib/data_mapper/is/tree.rb
DELETED
@@ -1,121 +0,0 @@
|
|
1
|
-
module DataMapper
|
2
|
-
module Is
|
3
|
-
module Tree
|
4
|
-
def self.included(base)
|
5
|
-
base.extend(ClassMethods)
|
6
|
-
end
|
7
|
-
|
8
|
-
# An extension to DataMapper to easily allow the creation of tree structures from your DataMapper Models.
|
9
|
-
# This requires a foreign key property for your model, which by default would be called :parent_id.
|
10
|
-
#
|
11
|
-
# Example:
|
12
|
-
#
|
13
|
-
# class Category < DataMapper::Base
|
14
|
-
# property :parent_id, :integer
|
15
|
-
# property :name, :string
|
16
|
-
#
|
17
|
-
# is_a_tree :order => "name"
|
18
|
-
# end
|
19
|
-
#
|
20
|
-
# root
|
21
|
-
# +- child
|
22
|
-
# +- grandchild1
|
23
|
-
# +- grandchild2
|
24
|
-
#
|
25
|
-
# root = Category.create("name" => "root")
|
26
|
-
# child = root.children.create("name" => "child")
|
27
|
-
# grandchild1 = child1.children.create("name" => "grandchild1")
|
28
|
-
# grandchild2 = child2.children.create("name" => "grandchild2")
|
29
|
-
#
|
30
|
-
# root.parent # => nil
|
31
|
-
# child.parent # => root
|
32
|
-
# root.children # => [child]
|
33
|
-
# root.children.first.children.first # => grandchild1
|
34
|
-
# Category.first_root # => root
|
35
|
-
# Category.roots # => [root]
|
36
|
-
#
|
37
|
-
# The following instance methods are added:
|
38
|
-
# * <tt>children</tt> - Returns all nodes with the current node as their parent, in the order specified by
|
39
|
-
# <tt>:order</tt> (<tt>[grandchild1, grandchild2]</tt> when called on <tt>child</tt>)
|
40
|
-
# * <tt>parent</tt> - Returns the node referenced by the foreign key (<tt>:parent_id</tt> by
|
41
|
-
# default) (<tt>root</tt> when called on <tt>child</tt>)
|
42
|
-
# * <tt>siblings</tt> - Returns all the children of the parent, excluding the current node
|
43
|
-
# (<tt>[grandchild2]</tt> when called on <tt>grandchild1</tt>)
|
44
|
-
# * <tt>generation</tt> - Returns all the children of the parent, including the current node (<tt>
|
45
|
-
# [grandchild1, grandchild2]</tt> when called on <tt>grandchild1</tt>)
|
46
|
-
# * <tt>ancestors</tt> - Returns all the ancestors of the current node (<tt>[root, child1]</tt>
|
47
|
-
# when called on <tt>grandchild2</tt>)
|
48
|
-
# * <tt>root</tt> - Returns the root of the current node (<tt>root</tt> when called on <tt>grandchild2</tt>)
|
49
|
-
#
|
50
|
-
# Author:: Timothy Bennett (http://lanaer.com)
|
51
|
-
module ClassMethods
|
52
|
-
# Configuration options are:
|
53
|
-
#
|
54
|
-
# * <tt>foreign_key</tt> - specifies the column name to use for tracking of the tree (default: +parent_id+)
|
55
|
-
# * <tt>order</tt> - makes it possible to sort the children according to this SQL snippet.
|
56
|
-
# * <tt>counter_cache</tt> - keeps a count in a +children_count+ column if set to +true+ (default: +false+).
|
57
|
-
def is_a_tree(options = {})
|
58
|
-
configuration = { :foreign_key => "parent_id" }
|
59
|
-
configuration.update(options) if options.is_a?(Hash)
|
60
|
-
|
61
|
-
belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
|
62
|
-
has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order]
|
63
|
-
|
64
|
-
include DataMapper::Is::Tree::InstanceMethods
|
65
|
-
|
66
|
-
class_eval <<-CLASS
|
67
|
-
def self.roots
|
68
|
-
self.all :#{configuration[:foreign_key]} => nil, :order => #{configuration[:order].inspect}
|
69
|
-
end
|
70
|
-
|
71
|
-
def self.first_root
|
72
|
-
self.first :#{configuration[:foreign_key]} => nil, :order => #{configuration[:order].inspect}
|
73
|
-
end
|
74
|
-
CLASS
|
75
|
-
|
76
|
-
class << self
|
77
|
-
alias_method :root, :first_root # for people used to the ActiveRecord acts_as_tree
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
alias_method :can_has_tree, :is_a_tree # just for fun ;)
|
82
|
-
end
|
83
|
-
|
84
|
-
module InstanceMethods
|
85
|
-
# Returns list of ancestors, starting with the root.
|
86
|
-
#
|
87
|
-
# grandchild1.ancestors # => [root, child]
|
88
|
-
def ancestors
|
89
|
-
node, nodes = self, []
|
90
|
-
nodes << node = node.parent while node.parent
|
91
|
-
nodes.reverse
|
92
|
-
end
|
93
|
-
|
94
|
-
# Returns the root node of the current node’s tree.
|
95
|
-
#
|
96
|
-
# grandchild1.root # => root
|
97
|
-
def root
|
98
|
-
node = self
|
99
|
-
node = node.parent while node.parent
|
100
|
-
node
|
101
|
-
end
|
102
|
-
|
103
|
-
# Returns all siblings of the current node.
|
104
|
-
#
|
105
|
-
# grandchild1.siblings # => [grandchild2]
|
106
|
-
def siblings
|
107
|
-
generation - [self]
|
108
|
-
end
|
109
|
-
|
110
|
-
# Returns all children of the current node’s parent.
|
111
|
-
#
|
112
|
-
# grandchild1.generation # => [grandchild1, grandchild2]
|
113
|
-
def generation
|
114
|
-
parent ? parent.children : self.class.roots
|
115
|
-
end
|
116
|
-
|
117
|
-
alias_method :self_and_siblings, :generation # for those used to the ActiveRecord acts_as_tree
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
@@ -1,155 +0,0 @@
|
|
1
|
-
module DataMapper
|
2
|
-
class Migration
|
3
|
-
class Table
|
4
|
-
|
5
|
-
MAPPINGS = DataMapper::Adapters::Sql::Mappings unless defined?(MAPPINGS)
|
6
|
-
|
7
|
-
attr_accessor :name
|
8
|
-
|
9
|
-
def initialize(table = nil, options = {})
|
10
|
-
@name, @options = table, options
|
11
|
-
@columns = []
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.create(table)
|
15
|
-
table.create!
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.drop(table_name)
|
19
|
-
database.table(klass(table_name)).drop!
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.add_column(table, column, type, options = {})
|
23
|
-
column = table.add_column column, type, options
|
24
|
-
column.create!
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.remove_column(table, column)
|
28
|
-
column = table[column]
|
29
|
-
column.drop!
|
30
|
-
end
|
31
|
-
|
32
|
-
def add(column, type, options = {})
|
33
|
-
column_data = [column, type, options]
|
34
|
-
exists? ? self.class.add_column(table, *column_data) : table.add_column(*column_data)
|
35
|
-
end
|
36
|
-
|
37
|
-
def remove(column)
|
38
|
-
self.class.remove_column table, column
|
39
|
-
end
|
40
|
-
|
41
|
-
def rename(old_column, new_column)
|
42
|
-
column = table[old_column]
|
43
|
-
column.rename!(new_column)
|
44
|
-
end
|
45
|
-
|
46
|
-
def alter(column, type, options = {})
|
47
|
-
column = table[column]
|
48
|
-
column.type = type
|
49
|
-
column.options = options
|
50
|
-
column.parse_options!
|
51
|
-
column.alter!
|
52
|
-
end
|
53
|
-
|
54
|
-
def exists?
|
55
|
-
database.table_exists?(klass)
|
56
|
-
end
|
57
|
-
|
58
|
-
def after_create!
|
59
|
-
unless exists?
|
60
|
-
table.add_column(:id, :integer, { :key => true }) unless @options[:id] == false
|
61
|
-
self.class.create(table)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# Rails Style
|
66
|
-
|
67
|
-
def column(name, type, options = {})
|
68
|
-
add(name, type, options)
|
69
|
-
end
|
70
|
-
|
71
|
-
# klass!
|
72
|
-
|
73
|
-
def table
|
74
|
-
@table ||= database.table(klass)
|
75
|
-
end
|
76
|
-
|
77
|
-
def klass
|
78
|
-
@klass ||= self.class.klass(self.name)
|
79
|
-
end
|
80
|
-
|
81
|
-
def self.klass(table)
|
82
|
-
table_name = table.to_s
|
83
|
-
class_name = Inflector::classify(table_name)
|
84
|
-
klass = Inflector::constantize(class_name)
|
85
|
-
rescue NameError
|
86
|
-
module_eval <<-classdef
|
87
|
-
class ::#{class_name} < DataMapper::Base
|
88
|
-
end
|
89
|
-
classdef
|
90
|
-
klass = eval("#{class_name}")
|
91
|
-
ensure
|
92
|
-
klass
|
93
|
-
end
|
94
|
-
|
95
|
-
end
|
96
|
-
|
97
|
-
class << self
|
98
|
-
|
99
|
-
def up; end
|
100
|
-
|
101
|
-
def down; end
|
102
|
-
|
103
|
-
def migrate(direction = :up)
|
104
|
-
send(direction)
|
105
|
-
end
|
106
|
-
|
107
|
-
def table(table = nil, options = {}, &block)
|
108
|
-
if table && block
|
109
|
-
table = DataMapper::Migration::Table.new(table, options)
|
110
|
-
table.instance_eval &block
|
111
|
-
table.after_create!
|
112
|
-
else
|
113
|
-
return DataMapper::Migration::Table
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
# Rails Style
|
118
|
-
|
119
|
-
def create_table(table_name, options = {}, &block)
|
120
|
-
new_table = table.new(table_name, options)
|
121
|
-
yield new_table
|
122
|
-
new_table.after_create!
|
123
|
-
end
|
124
|
-
|
125
|
-
def drop_table(table_name)
|
126
|
-
table.drop(table_name)
|
127
|
-
end
|
128
|
-
|
129
|
-
def add_column(table_name, column, type, options = {})
|
130
|
-
table table_name do
|
131
|
-
add column, type, options
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
def rename_column(table_name, old_column_name, new_column_name)
|
136
|
-
table table_name do
|
137
|
-
rename old_column_name, new_column_name
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
def change_column(table_name, column_name, type, options = {})
|
142
|
-
table table_name do
|
143
|
-
alter column_name, type, options
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def remove_column(table_name, column)
|
148
|
-
table table_name do
|
149
|
-
remove column
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
end
|
@@ -1,852 +0,0 @@
|
|
1
|
-
require 'data_mapper/property'
|
2
|
-
require 'data_mapper/attributes'
|
3
|
-
require 'data_mapper/support/serialization'
|
4
|
-
require 'data_mapper/validations'
|
5
|
-
require 'data_mapper/associations'
|
6
|
-
require 'data_mapper/callbacks'
|
7
|
-
require 'data_mapper/embedded_value'
|
8
|
-
require 'data_mapper/auto_migrations'
|
9
|
-
require 'data_mapper/dependency_queue'
|
10
|
-
require 'data_mapper/support/struct'
|
11
|
-
|
12
|
-
module DataMapper
|
13
|
-
# See DataMapper::Persistence::ClassMethods for DataMapper's DSL documentation.
|
14
|
-
module Persistence
|
15
|
-
|
16
|
-
class IncompleteModelDefinitionError < StandardError
|
17
|
-
end
|
18
|
-
|
19
|
-
# This probably needs to be protected
|
20
|
-
attr_accessor :loaded_set
|
21
|
-
|
22
|
-
include Comparable
|
23
|
-
|
24
|
-
def self.included(klass)
|
25
|
-
|
26
|
-
klass.extend(ClassMethods)
|
27
|
-
klass.extend(ConvenienceMethods::ClassMethods)
|
28
|
-
|
29
|
-
klass.send(:include, ConvenienceMethods::InstanceMethods)
|
30
|
-
klass.send(:include, Attributes)
|
31
|
-
klass.send(:include, Associations)
|
32
|
-
klass.send(:include, Validations)
|
33
|
-
klass.send(:include, CallbacksHelper)
|
34
|
-
klass.send(:include, Support::Serialization)
|
35
|
-
|
36
|
-
klass.instance_variable_set('@properties', [])
|
37
|
-
|
38
|
-
klass.send :extend, AutoMigrations
|
39
|
-
klass.subclasses
|
40
|
-
DataMapper::Persistence::subclasses << klass unless klass == DataMapper::Base
|
41
|
-
klass.send(:undef_method, :id) if method_defined?(:id)
|
42
|
-
|
43
|
-
# When this class is sub-classed, copy the declared columns.
|
44
|
-
klass.class_eval do
|
45
|
-
def self.subclasses
|
46
|
-
@subclasses || (@subclasses = Support::TypedSet.new(Class))
|
47
|
-
end
|
48
|
-
|
49
|
-
def self.inherited(subclass)
|
50
|
-
super_table = database.table(self)
|
51
|
-
|
52
|
-
if super_table.type_column.nil?
|
53
|
-
super_table.add_column(:type, :class, {})
|
54
|
-
end
|
55
|
-
|
56
|
-
subclass.instance_variable_set('@properties', self.instance_variable_get("@properties").dup)
|
57
|
-
subclass.instance_variable_set("@callbacks", self.callbacks.dup)
|
58
|
-
|
59
|
-
self::subclasses << subclass
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.persistent?
|
63
|
-
true
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
# Migrates the database schema based on the properties defined within
|
69
|
-
# models. This includes removing fields no longer listed in models and
|
70
|
-
# adding new ones.
|
71
|
-
#
|
72
|
-
# This is destructive. Any data stored in the database will be destroyed
|
73
|
-
# when this method is called.
|
74
|
-
#
|
75
|
-
# ==== Returns
|
76
|
-
# True:: successfully automigrated database
|
77
|
-
# False:: an error occured when automigrating the database
|
78
|
-
#
|
79
|
-
# @public
|
80
|
-
def self.auto_migrate!
|
81
|
-
subclasses.each do |subclass|
|
82
|
-
subclass.auto_migrate!
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
|
87
|
-
# Drops all tables known by the schema
|
88
|
-
#
|
89
|
-
# ==== Returns
|
90
|
-
# True:: successfully automigrated database
|
91
|
-
# False:: an error occured when automigrating the database
|
92
|
-
#
|
93
|
-
# @public
|
94
|
-
def self.drop_all_tables!
|
95
|
-
database.adapter.schema.each do |table|
|
96
|
-
table.drop!
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# Track classes that include this module.
|
101
|
-
# ==== Returns
|
102
|
-
# Support::TypedSet::
|
103
|
-
# contains classes that include or inherit from this module
|
104
|
-
#
|
105
|
-
# @semipublic
|
106
|
-
def self.subclasses
|
107
|
-
@subclasses || (@subclasses = Support::TypedSet.new(Class))
|
108
|
-
end
|
109
|
-
|
110
|
-
# Track classes that include this module.
|
111
|
-
# ==== Returns
|
112
|
-
# Support::TypedSet::
|
113
|
-
# contains classes that include or inherit from this module
|
114
|
-
#
|
115
|
-
# @semipublic
|
116
|
-
def self.dependencies
|
117
|
-
@dependency_queue || (@dependency_queue = DependencyQueue.new)
|
118
|
-
end
|
119
|
-
|
120
|
-
def initialize(details = nil)
|
121
|
-
check_for_properties!
|
122
|
-
if details
|
123
|
-
initialize_with_attributes(details)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def initialize_with_attributes(details)
|
128
|
-
case details
|
129
|
-
when Hash then self.attributes = details
|
130
|
-
when details.respond_to?(:persistent?) then self.private_attributes = details.attributes
|
131
|
-
when Struct then self.private_attributes = details.attributes
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
def check_for_properties!
|
136
|
-
raise IncompleteModelDefinitionError.new("Models must have at least one property to be initialized.") if self.class.properties.empty?
|
137
|
-
end
|
138
|
-
|
139
|
-
module ConvenienceMethods
|
140
|
-
module InstanceMethods
|
141
|
-
|
142
|
-
# Save updated properties to the database.
|
143
|
-
#
|
144
|
-
# ==== Returns
|
145
|
-
# True:: successfully saved the object to the database
|
146
|
-
# False:: an error occured when saving the object to the database.
|
147
|
-
# check valid? to see if validation error occured
|
148
|
-
#
|
149
|
-
# @public
|
150
|
-
def save
|
151
|
-
database_context.save(self)
|
152
|
-
end
|
153
|
-
|
154
|
-
# This behaves in the same way as save, but raises a ValidationError
|
155
|
-
# if the model is invalid. Successful saves return true.
|
156
|
-
#
|
157
|
-
# ==== Returns
|
158
|
-
# True:: successfully saved the object to the database
|
159
|
-
#
|
160
|
-
# ==== Raises
|
161
|
-
# ValidationError::
|
162
|
-
# The object could not be saved to the database due to validation
|
163
|
-
# errors
|
164
|
-
# @public
|
165
|
-
def save!
|
166
|
-
raise ValidationError.new(errors) unless save
|
167
|
-
return true
|
168
|
-
end
|
169
|
-
|
170
|
-
# Reloads a model's properties from the database. This also includes
|
171
|
-
# data for any associated models that have been loaded from the
|
172
|
-
# database.
|
173
|
-
#
|
174
|
-
# You can limit the properties being reloaded by passing in an array
|
175
|
-
# of symbols.
|
176
|
-
def reload!(cols = nil)
|
177
|
-
database_context.first(self.class, key, :select => ([self.class.table.key.to_sym] + (cols || original_values.keys)).uniq, :reload => true)
|
178
|
-
self.loaded_associations.each { |association| association.reload! }
|
179
|
-
self
|
180
|
-
end
|
181
|
-
alias reload reload!
|
182
|
-
|
183
|
-
# Deletes the model from the database and de-activates associations
|
184
|
-
def destroy!
|
185
|
-
database_context.destroy(self)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
module ClassMethods
|
190
|
-
|
191
|
-
# Attempts to find an object using options passed as
|
192
|
-
# search_attributes, and falls back to creating the object if it
|
193
|
-
# can't find it.
|
194
|
-
#
|
195
|
-
# ==== Parameters
|
196
|
-
# search_attributes <hash>::
|
197
|
-
# attributes used to perform the search, and which can be later
|
198
|
-
# merged with create_attributes when creating a record
|
199
|
-
# create_attributes <hash>::
|
200
|
-
# attributes which are merged into the search_attributes when a
|
201
|
-
# record is unfound and needs to be created
|
202
|
-
#
|
203
|
-
# ==== Returns
|
204
|
-
# Object:: the found or created object from the database
|
205
|
-
#
|
206
|
-
# ==== Raises
|
207
|
-
# ValidationError::
|
208
|
-
# An object was not found, and could not be created due to errors
|
209
|
-
# in validation.
|
210
|
-
# DataObject::QueryError::
|
211
|
-
# The database threw an error
|
212
|
-
# -
|
213
|
-
# @public
|
214
|
-
def find_or_create(search_attributes, create_attributes = {})
|
215
|
-
first(search_attributes) || create(search_attributes.merge(create_attributes))
|
216
|
-
end
|
217
|
-
|
218
|
-
# returns an array of objects matching <tt>options</tt>.
|
219
|
-
#
|
220
|
-
# ==== Parameters
|
221
|
-
# options <hash>::
|
222
|
-
# hash of parameters to search by
|
223
|
-
#
|
224
|
-
# ==== Returns
|
225
|
-
# Array:: contains all matched objects from the database, or an
|
226
|
-
# empty set
|
227
|
-
#
|
228
|
-
# ==== Options
|
229
|
-
# Basics:
|
230
|
-
# Widget.all # => no conditions
|
231
|
-
# Widget.all :order => 'created_at desc' # => ORDER BY created_at desc
|
232
|
-
# Widget.all :limit => 10 # => LIMIT 10
|
233
|
-
# Widget.all :offset => 100 # => OFFSET 100
|
234
|
-
# Widget.all :include => [:gadgets] # => performs the JOIN according to
|
235
|
-
# its association with Gadgets
|
236
|
-
#
|
237
|
-
# Any non-standard options are assumed to be column names and are ANDed together:
|
238
|
-
# Widget.all :age => 10 # => WHERE age = 10
|
239
|
-
# Widget.all :age => 10, :title => 'Toy' # => WHERE age = 10 AND title = 'Toy'
|
240
|
-
#
|
241
|
-
# Using Symbol Operators[link:classes/DataMapper/Support/Symbol/Operator.html]:
|
242
|
-
# Widget.all :age.gt => 20 # => WHERE age > 10
|
243
|
-
# Widget.all :age.gte => 20, :name.like => '%Toy%' # => WHERE age >= 10 and name like '%Toy%'
|
244
|
-
#
|
245
|
-
# Variations of syntax include the :conditions => {} as well as interpolated arrays
|
246
|
-
# Widget.all :conditions => {:age => 10} # => WHERE age = 10
|
247
|
-
# Widget.all :conditions => ["age = ?", 10] # => WHERE age = 10
|
248
|
-
#
|
249
|
-
# Syntaxes can be mixed-and-matched as well
|
250
|
-
# Widget.all :conditions => ["age = ?", 10], :title => 'Toy'
|
251
|
-
# # => WHERE age = 10 AND title = 'Toy'
|
252
|
-
#
|
253
|
-
# ==== Raises
|
254
|
-
# DataMapper::Adapters::Sql::Commands::LoadCommand::ConditionsError::
|
255
|
-
# A query could not be constructed from the hash passed in as
|
256
|
-
# <tt>options</tt>
|
257
|
-
# DataObject::QueryError::
|
258
|
-
# The database threw an error
|
259
|
-
# -
|
260
|
-
# @public
|
261
|
-
def all(options = {})
|
262
|
-
database.all(self, options)
|
263
|
-
end
|
264
|
-
|
265
|
-
# Allows you to iterate over a collection of matching records. The
|
266
|
-
# first argument is the find options. The second is a block that will
|
267
|
-
# be called for every matching record.
|
268
|
-
#
|
269
|
-
# The valid options are the same as those documented in #all,
|
270
|
-
# except the <tt>:offset</tt> option, which is not allowed.
|
271
|
-
def each(options = {}, &b)
|
272
|
-
raise ArgumentError.new(":offset is not supported with the #each method") if options.has_key?(:offset)
|
273
|
-
|
274
|
-
offset = 0
|
275
|
-
limit = options[:limit] || (self::const_defined?('DEFAULT_LIMIT') ? self::DEFAULT_LIMIT : 500)
|
276
|
-
|
277
|
-
until (results = all(options.merge(:limit => limit, :offset => offset))).empty?
|
278
|
-
results.each(&b)
|
279
|
-
offset += limit
|
280
|
-
end
|
281
|
-
end
|
282
|
-
|
283
|
-
# Returns the first object which matches the query generated from the arguments
|
284
|
-
#
|
285
|
-
# ==== Parameters
|
286
|
-
# see all()
|
287
|
-
#
|
288
|
-
# ==== Returns
|
289
|
-
# Object:: first object from the database which matches the query
|
290
|
-
# nil:: no object could be found which matches the query
|
291
|
-
#
|
292
|
-
# ==== Raises
|
293
|
-
# DataMapper::Adapters::Sql::Commands::LoadCommand::ConditionsError::
|
294
|
-
# A query could not be generated from the arguments passed in
|
295
|
-
# DataObject::QueryError::
|
296
|
-
# The database threw an error
|
297
|
-
# -
|
298
|
-
# @public
|
299
|
-
def first(*args)
|
300
|
-
database.first(self, *args)
|
301
|
-
end
|
302
|
-
|
303
|
-
# returns the count of rows that match the given options hash. See
|
304
|
-
# all() for a list of possible arguments.
|
305
|
-
# NOTE: discards <tt>:offset</tt>, <tt>:limit</tt>, <tt>:order</tt>
|
306
|
-
#
|
307
|
-
# ==== Parameters
|
308
|
-
# see all().
|
309
|
-
#
|
310
|
-
# ==== Returns
|
311
|
-
# Integer:: number of rows matching query
|
312
|
-
#
|
313
|
-
# ==== Raises
|
314
|
-
# DataMapper::Adapters::Sql::Commands::LoadCommand::ConditionsError::
|
315
|
-
# A query could not be generated from the arguments passed in
|
316
|
-
# DataObject::QueryError::
|
317
|
-
# The database threw an error
|
318
|
-
# -
|
319
|
-
# @public
|
320
|
-
def count(*args)
|
321
|
-
database.count(self, *args)
|
322
|
-
end
|
323
|
-
|
324
|
-
# Does what it says. Deletes all records in a model's table.
|
325
|
-
# before_destroy and after_destroy callbacks are called and
|
326
|
-
# paranoia is respected.
|
327
|
-
#
|
328
|
-
# ==== Returns
|
329
|
-
# nil:: successfully deleted all rows
|
330
|
-
#
|
331
|
-
# ==== Raises
|
332
|
-
# DataObject::QueryError::
|
333
|
-
# The database threw an error
|
334
|
-
# -
|
335
|
-
# @public
|
336
|
-
def delete_all
|
337
|
-
database.delete_all(self)
|
338
|
-
end
|
339
|
-
|
340
|
-
def truncate!
|
341
|
-
database.truncate(self)
|
342
|
-
end
|
343
|
-
|
344
|
-
# This method allows for ActiveRecord style queries. The first
|
345
|
-
# argument is a symbol indicating a search for a single record or a
|
346
|
-
# collection — <tt>:first</tt> and <tt>:all</tt> respectively. The
|
347
|
-
# second argument is the hash of options for your query. For a list
|
348
|
-
# of valid options, please refer to the #all method.
|
349
|
-
#
|
350
|
-
# Widget.find(:all, :active => true) # => An array of active widgets
|
351
|
-
# Widget.find(:first, :active => true) # => The first active widget found
|
352
|
-
def find(type_or_id, options = {})
|
353
|
-
case type_or_id
|
354
|
-
when :first then first(options)
|
355
|
-
when :all then all(options)
|
356
|
-
else first(type_or_id, options)
|
357
|
-
end
|
358
|
-
end
|
359
|
-
|
360
|
-
# supply this method with the full SQL you wish to search on, and it
|
361
|
-
# will return an array of Structs with your results set in them.
|
362
|
-
#
|
363
|
-
# NOTE: this does NOT return objects of a specific type, but rather
|
364
|
-
# Struct objects with as many attributes as what you requested in
|
365
|
-
# your full SQL query. These structs are read-only.
|
366
|
-
#
|
367
|
-
# If you only indicate you want 1 specific column, Datamapper and
|
368
|
-
# DataObjects will do their best to type-cast the result as best they
|
369
|
-
# can, rather than supplying you with an array of length 1 containing
|
370
|
-
# Structs with 1 attribute.
|
371
|
-
def find_by_sql(*args)
|
372
|
-
DataMapper::database.query(*args)
|
373
|
-
end
|
374
|
-
|
375
|
-
# finds a single row from the database by it's primary key.
|
376
|
-
# If you declared a property with <tt>:key => true</tt>, it's safe to
|
377
|
-
# use here.
|
378
|
-
# Example:
|
379
|
-
# Widget.get(100) # => widget with the primary key of 100
|
380
|
-
# Widget.get('Toy') # => widget with the primary natural key of 'Toy'
|
381
|
-
def get(*keys)
|
382
|
-
database.get(self, keys)
|
383
|
-
end
|
384
|
-
|
385
|
-
|
386
|
-
# synonym for get()
|
387
|
-
# ==== Parameters
|
388
|
-
# keys <any>:: keys which which to look up objects in the table.
|
389
|
-
#
|
390
|
-
# ==== Returns
|
391
|
-
# object :: object matching the request
|
392
|
-
#
|
393
|
-
# ==== Raises
|
394
|
-
# DataMapper::ObjectNotFoundError
|
395
|
-
# could not find the object requested
|
396
|
-
# -
|
397
|
-
# @public
|
398
|
-
def [](*keys)
|
399
|
-
# Eventually this ArgumentError should be removed. It's only here
|
400
|
-
# to help
|
401
|
-
# migrate users away from the [options_hash] syntax, which is no
|
402
|
-
# longer supported.
|
403
|
-
raise ArgumentError.new('Hash is not a valid key') if keys.size == 1 && keys.first.is_a?(Hash)
|
404
|
-
instance = database.get(self, keys)
|
405
|
-
raise ObjectNotFoundError.new() unless instance
|
406
|
-
return instance
|
407
|
-
end
|
408
|
-
|
409
|
-
# creates (and saves) a new instance of the object.
|
410
|
-
def create(attributes)
|
411
|
-
instance = self.new_with_attributes(attributes)
|
412
|
-
instance.save
|
413
|
-
instance
|
414
|
-
end
|
415
|
-
|
416
|
-
# the same as create(), though will raise an ObjectNotFoundError if
|
417
|
-
# the instance could not be saved
|
418
|
-
def create!(attributes)
|
419
|
-
instance = create(attributes)
|
420
|
-
raise ObjectNotFoundError.new(instance) if instance.new_record?
|
421
|
-
instance
|
422
|
-
end
|
423
|
-
end
|
424
|
-
end
|
425
|
-
|
426
|
-
module ClassMethods
|
427
|
-
|
428
|
-
def new_with_attributes(details)
|
429
|
-
instance = allocate
|
430
|
-
instance.initialize_with_attributes(details)
|
431
|
-
instance
|
432
|
-
end
|
433
|
-
|
434
|
-
# Track classes that include this module.
|
435
|
-
def subclasses
|
436
|
-
@subclasses || (@subclasses = [])
|
437
|
-
end
|
438
|
-
|
439
|
-
def logger
|
440
|
-
database.logger
|
441
|
-
end
|
442
|
-
|
443
|
-
def transaction
|
444
|
-
yield
|
445
|
-
end
|
446
|
-
|
447
|
-
# The foreign key for a model. It is based on the lowercased and
|
448
|
-
# underscored name of the class, suffixed with <tt>_id</tt>.
|
449
|
-
#
|
450
|
-
# Widget.foreign_key # => "widget_id"
|
451
|
-
# NewsItem.foreign_key # => "news_item_id"
|
452
|
-
def foreign_key
|
453
|
-
Inflector.underscore(self.name) + "_id"
|
454
|
-
end
|
455
|
-
|
456
|
-
def extended(klass)
|
457
|
-
end
|
458
|
-
|
459
|
-
def table
|
460
|
-
database.table(self)
|
461
|
-
end
|
462
|
-
|
463
|
-
# Adds property accessors for a field that you'd like to be able to
|
464
|
-
# modify. The DataMapper doesn't
|
465
|
-
# use the table schema to infer accessors, you must explicity call
|
466
|
-
# #property to add field accessors
|
467
|
-
# to your model.
|
468
|
-
#
|
469
|
-
# Can accept an unlimited amount of property names. Optionally, you may
|
470
|
-
# pass the property names as an
|
471
|
-
# array.
|
472
|
-
#
|
473
|
-
# For more documentation, see Property.
|
474
|
-
#
|
475
|
-
# EXAMPLE:
|
476
|
-
# class CellProvider
|
477
|
-
# property :name, :string
|
478
|
-
# property :rating_number, :rating_percent, :integer # will create two properties with same type and text
|
479
|
-
# property [:bill_to, :ship_to, :mail_to], :text, :lazy => false # will create three properties all with same type and text
|
480
|
-
# end
|
481
|
-
#
|
482
|
-
# att = CellProvider.new(:name => 'AT&T')
|
483
|
-
# att.rating = 3
|
484
|
-
# puts att.name, att.rating
|
485
|
-
#
|
486
|
-
# => AT&T
|
487
|
-
# => 3
|
488
|
-
#
|
489
|
-
# OPTIONS:
|
490
|
-
# * <tt>lazy</tt>: Lazy load the specified property (:lazy => true). False by default.
|
491
|
-
# * <tt>accessor</tt>: Set method visibility for the property accessors. Affects both
|
492
|
-
# reader and writer. Allowable values are :public, :protected, :private. Defaults to
|
493
|
-
# :public
|
494
|
-
# * <tt>reader</tt>: Like the accessor option but affects only the property reader.
|
495
|
-
# * <tt>writer</tt>: Like the accessor option but affects only the property writer.
|
496
|
-
# * <tt>protected</tt>: Alias for :reader => :public, :writer => :protected
|
497
|
-
# * <tt>private</tt>: Alias for :reader => :public, :writer => :private
|
498
|
-
|
499
|
-
def property(*columns_and_options)
|
500
|
-
columns, options = columns_and_options.partition {|item| not item.is_a?(Hash)}
|
501
|
-
options = (options.empty? ? {} : options[0])
|
502
|
-
type = columns.pop
|
503
|
-
|
504
|
-
@properties ||= []
|
505
|
-
new_properties = []
|
506
|
-
|
507
|
-
columns.flatten.each do |name|
|
508
|
-
property = DataMapper::Property.new(self, name, type, options)
|
509
|
-
new_properties << property
|
510
|
-
@properties << property
|
511
|
-
end
|
512
|
-
|
513
|
-
return (new_properties.length == 1 ? new_properties[0] : new_properties)
|
514
|
-
end
|
515
|
-
|
516
|
-
# TODO: Figure out how to make EmbeddedValue work with new property
|
517
|
-
# code. EV relies on these next two methods.
|
518
|
-
def property_getter(mapping, visibility = :public)
|
519
|
-
if mapping.lazy?
|
520
|
-
class_eval <<-EOS
|
521
|
-
#{visibility.to_s}
|
522
|
-
def #{mapping.name}
|
523
|
-
lazy_load!(#{mapping.name.inspect})
|
524
|
-
class << self;
|
525
|
-
attr_accessor #{mapping.name.inspect}
|
526
|
-
end
|
527
|
-
@#{mapping.name}
|
528
|
-
end
|
529
|
-
EOS
|
530
|
-
else
|
531
|
-
class_eval("#{visibility.to_s}; def #{mapping.name}; #{mapping.instance_variable_name} end") unless [ :public, :private, :protected ].include?(mapping.name)
|
532
|
-
end
|
533
|
-
|
534
|
-
if mapping.type == :boolean
|
535
|
-
class_eval("#{visibility.to_s}; def #{mapping.name.to_s.ensure_ends_with('?')}; #{mapping.instance_variable_name} end")
|
536
|
-
end
|
537
|
-
|
538
|
-
rescue SyntaxError
|
539
|
-
raise SyntaxError.new(mapping)
|
540
|
-
end
|
541
|
-
|
542
|
-
def property_setter(mapping, visibility = :public)
|
543
|
-
if mapping.lazy?
|
544
|
-
class_eval <<-EOS
|
545
|
-
#{visibility.to_s}
|
546
|
-
def #{mapping.name}=(value)
|
547
|
-
class << self;
|
548
|
-
attr_accessor #{mapping.name.inspect}
|
549
|
-
end
|
550
|
-
@#{mapping.name} = value
|
551
|
-
end
|
552
|
-
EOS
|
553
|
-
else
|
554
|
-
class_eval("#{visibility.to_s}; def #{mapping.name}=(value); #{mapping.instance_variable_name} = value end")
|
555
|
-
end
|
556
|
-
rescue SyntaxError
|
557
|
-
raise SyntaxError.new(mapping)
|
558
|
-
end
|
559
|
-
|
560
|
-
# Allows you to override the table name for a model.
|
561
|
-
# EXAMPLE:
|
562
|
-
# class WorkItem
|
563
|
-
# set_table_name 't_work_item_list'
|
564
|
-
# end
|
565
|
-
def set_table_name(value)
|
566
|
-
database.table(self).name = value
|
567
|
-
end
|
568
|
-
|
569
|
-
# An embedded value maps the values of an object to fields in the
|
570
|
-
# record of the object's owner.
|
571
|
-
# #embed takes a symbol to define the embedded class, options, and
|
572
|
-
# an optional block. See
|
573
|
-
# examples for use cases.
|
574
|
-
#
|
575
|
-
# EXAMPLE:
|
576
|
-
# class CellPhone < DataMapper::Base
|
577
|
-
# property :number, :string
|
578
|
-
#
|
579
|
-
# embed :owner, :prefix => true do
|
580
|
-
# property :name, :string
|
581
|
-
# property :address, :string
|
582
|
-
# end
|
583
|
-
# end
|
584
|
-
#
|
585
|
-
# my_phone = CellPhone.new
|
586
|
-
# my_phone.owner.name = "Nick"
|
587
|
-
# puts my_phone.owner.name
|
588
|
-
#
|
589
|
-
# => Nick
|
590
|
-
#
|
591
|
-
# OPTIONS:
|
592
|
-
# * <tt>prefix</tt>: define a column prefix, so instead of mapping
|
593
|
-
# :address to an 'address' column, it would map to
|
594
|
-
# 'owner_address' in the example above. If
|
595
|
-
# :prefix => true is specified, the prefix will
|
596
|
-
# be the name of the symbol given as the first
|
597
|
-
# parameter. If the prefix is a string the
|
598
|
-
# specified
|
599
|
-
# string will be used for the prefix.
|
600
|
-
# * <tt>lazy</tt>: lazy-load all embedded values at the same time.
|
601
|
-
# :lazy => true to enable. Disabled (false) by
|
602
|
-
# default.
|
603
|
-
# * <tt>accessor</tt>: Set method visibility for all embedded
|
604
|
-
# properties. Affects both reader and writer.
|
605
|
-
# Allowable values are :public, :protected,
|
606
|
-
# :private. Defaults to :public
|
607
|
-
# * <tt>reader</tt>: Like the accessor option but affects only
|
608
|
-
# embedded property readers.
|
609
|
-
# * <tt>writer</tt>: Like the accessor option but affects only
|
610
|
-
# embedded property writers.
|
611
|
-
# * <tt>protected</tt>: Alias for :reader => :public,
|
612
|
-
# :writer => :protected
|
613
|
-
# * <tt>private</tt>: Alias for :reader => :public, :writer => :private
|
614
|
-
#
|
615
|
-
def embed(name, options = {}, &block)
|
616
|
-
EmbeddedValue::define(self, name, options, &block)
|
617
|
-
end
|
618
|
-
|
619
|
-
# Returns the hash of properties for this model.
|
620
|
-
def properties
|
621
|
-
@properties
|
622
|
-
end
|
623
|
-
|
624
|
-
# Creates a composite index for an arbitrary number of database columns.
|
625
|
-
# Note that it also is possible to specify single indexes directly for
|
626
|
-
# each property.
|
627
|
-
#
|
628
|
-
# === EXAMPLE WITH COMPOSITE INDEX:
|
629
|
-
# class Person < DataMapper::Base
|
630
|
-
# property :server_id, :integer
|
631
|
-
# property :name, :string
|
632
|
-
#
|
633
|
-
# index [:server_id, :name]
|
634
|
-
# end
|
635
|
-
#
|
636
|
-
# === EXAMPLE WITH COMPOSITE UNIQUE INDEX:
|
637
|
-
# class Person < DataMapper::Base
|
638
|
-
# property :server_id, :integer
|
639
|
-
# property :name, :string
|
640
|
-
#
|
641
|
-
# index [:server_id, :name], :unique => true
|
642
|
-
# end
|
643
|
-
#
|
644
|
-
# === SINGLE INDEX EXAMPLES:
|
645
|
-
# * property :name, :index => true
|
646
|
-
# * property :name, :index => :unique
|
647
|
-
def index(indexes, unique = false)
|
648
|
-
if indexes.kind_of?(Array) # if given an index of multiple columns
|
649
|
-
database.schema[self].add_composite_index(indexes, unique)
|
650
|
-
else
|
651
|
-
raise ArgumentError.new("You must supply an array for the composite index")
|
652
|
-
end
|
653
|
-
end
|
654
|
-
|
655
|
-
end
|
656
|
-
|
657
|
-
# Lazy-loads the attributes for a loaded_set, then overwrites the
|
658
|
-
# accessors
|
659
|
-
# for the named methods so that the lazy_loading is skipped the second
|
660
|
-
# time.
|
661
|
-
def lazy_load!(*names)
|
662
|
-
|
663
|
-
names = names.map { |name| name.to_sym }.reject { |name| lazy_loaded_attributes.include?(name) }
|
664
|
-
|
665
|
-
reset_attribute = lambda do |instance|
|
666
|
-
singleton_class = (class << instance; self end)
|
667
|
-
names.each do |name|
|
668
|
-
instance.lazy_loaded_attributes << name
|
669
|
-
singleton_class.send(:attr_accessor, name)
|
670
|
-
end
|
671
|
-
end
|
672
|
-
|
673
|
-
unless names.empty? || new_record? || loaded_set.nil?
|
674
|
-
|
675
|
-
key = database_context.table(self.class).key.to_sym
|
676
|
-
keys_to_select = loaded_set.map do |instance|
|
677
|
-
instance.send(key)
|
678
|
-
end
|
679
|
-
|
680
|
-
database_context.all(
|
681
|
-
self.class,
|
682
|
-
:select => ([key] + names),
|
683
|
-
:reload => true,
|
684
|
-
key => keys_to_select
|
685
|
-
).each(&reset_attribute)
|
686
|
-
else
|
687
|
-
reset_attribute[self]
|
688
|
-
end
|
689
|
-
end
|
690
|
-
|
691
|
-
def database_context
|
692
|
-
@database_context || ( @database_context = database )
|
693
|
-
end
|
694
|
-
|
695
|
-
def database_context=(value)
|
696
|
-
@database_context = value
|
697
|
-
end
|
698
|
-
|
699
|
-
def logger
|
700
|
-
self.class.logger
|
701
|
-
end
|
702
|
-
|
703
|
-
# Returns <tt>true</tt> if this model hasn't been saved to the
|
704
|
-
# database, <tt>false</tt> otherwise.
|
705
|
-
def new_record?
|
706
|
-
@new_record.nil? || @new_record
|
707
|
-
end
|
708
|
-
|
709
|
-
# Returns a Set containing the properties that have had their
|
710
|
-
# <tt>:lazy</tt> option set to true, or are lazily loaded by
|
711
|
-
# default — i.e. text fields.
|
712
|
-
def lazy_loaded_attributes
|
713
|
-
@lazy_loaded_attributes || @lazy_loaded_attributes = Set.new
|
714
|
-
end
|
715
|
-
|
716
|
-
# Accepts a hash of properties and values to be updated and then calls #save
|
717
|
-
def update_attributes(update_hash)
|
718
|
-
self.attributes = update_hash
|
719
|
-
self.save
|
720
|
-
end
|
721
|
-
|
722
|
-
# Returns <tt>true</tt> if the unsaved model has had properties changed
|
723
|
-
# since it was loaded from the database. Returns <tt>false</tt> otherwise.
|
724
|
-
def dirty?(cleared = Set.new)
|
725
|
-
return false if cleared.include?(self)
|
726
|
-
cleared << self
|
727
|
-
|
728
|
-
result = database_context.table(self).columns.any? do |column|
|
729
|
-
if column.type == :object
|
730
|
-
Marshal.dump(self.instance_variable_get(column.instance_variable_name)) != original_values[column.name]
|
731
|
-
else
|
732
|
-
self.instance_variable_get(column.instance_variable_name) != original_values[column.name]
|
733
|
-
end
|
734
|
-
end
|
735
|
-
|
736
|
-
return true if result
|
737
|
-
|
738
|
-
loaded_associations.any? do |loaded_association|
|
739
|
-
loaded_association.dirty?(cleared)
|
740
|
-
end
|
741
|
-
end
|
742
|
-
|
743
|
-
# For unsaved models, returns a hash of properties that have had their
|
744
|
-
# values changed since it was loaded from the database.
|
745
|
-
def dirty_attributes
|
746
|
-
pairs = {}
|
747
|
-
|
748
|
-
database_context.table(self).columns.each do |column|
|
749
|
-
value = instance_variable_get(column.instance_variable_name)
|
750
|
-
if value != original_values[column.name] && (!new_record? || !column.serial?)
|
751
|
-
pairs[column.name] = column.type != :object ? value : YAML.dump(value)
|
752
|
-
end
|
753
|
-
end
|
754
|
-
|
755
|
-
pairs
|
756
|
-
end
|
757
|
-
|
758
|
-
def original_values=(values)
|
759
|
-
values.each_pair do |k,v|
|
760
|
-
original_values[k] = case v
|
761
|
-
when String, Date, Time then v.dup
|
762
|
-
# when column.type == :object then Marshal.dump(v)
|
763
|
-
else v
|
764
|
-
end
|
765
|
-
end
|
766
|
-
end
|
767
|
-
|
768
|
-
def original_values
|
769
|
-
class << self
|
770
|
-
attr_reader :original_values
|
771
|
-
end
|
772
|
-
|
773
|
-
@original_values = {}
|
774
|
-
end
|
775
|
-
|
776
|
-
def loaded_set=(value)
|
777
|
-
value << self
|
778
|
-
@loaded_set = value
|
779
|
-
end
|
780
|
-
|
781
|
-
def inspect
|
782
|
-
inspected_attributes = attributes.map { |k,v| "@#{k}=#{v.inspect}" }
|
783
|
-
|
784
|
-
instance_variables.each do |name|
|
785
|
-
if instance_variable_get(name).kind_of?(Associations::HasManyAssociation)
|
786
|
-
inspected_attributes << "#{name}=#{instance_variable_get(name).inspect}"
|
787
|
-
end
|
788
|
-
end
|
789
|
-
|
790
|
-
"#<%s:0x%x @new_record=%s, %s>" % [self.class.name, (object_id * 2), new_record?, inspected_attributes.join(', ')]
|
791
|
-
end
|
792
|
-
|
793
|
-
def loaded_associations
|
794
|
-
@loaded_associations || @loaded_associations = []
|
795
|
-
end
|
796
|
-
|
797
|
-
def key=(value)
|
798
|
-
key_column = database_context.table(self.class).key
|
799
|
-
@__key = key_column.type_cast_value(value)
|
800
|
-
instance_variable_set(key_column.instance_variable_name, @__key)
|
801
|
-
end
|
802
|
-
|
803
|
-
def key
|
804
|
-
@__key || @__key = begin
|
805
|
-
key_column = database_context.table(self.class).key
|
806
|
-
key_column.type_cast_value(instance_variable_get(key_column.instance_variable_name))
|
807
|
-
end
|
808
|
-
end
|
809
|
-
|
810
|
-
def keys
|
811
|
-
self.class.table.keys.map do |column|
|
812
|
-
column.type_cast_value(instance_variable_get(column.instance_variable_name))
|
813
|
-
end.compact
|
814
|
-
end
|
815
|
-
|
816
|
-
def <=>(other)
|
817
|
-
keys <=> other.keys
|
818
|
-
end
|
819
|
-
|
820
|
-
# Look to ::included for __hash alias
|
821
|
-
def hash
|
822
|
-
@__hash || @__hash = keys.empty? ? super : keys.hash
|
823
|
-
end
|
824
|
-
|
825
|
-
def eql?(other)
|
826
|
-
return false unless other.is_a?(self.class) || self.is_a?(other.class)
|
827
|
-
comparator = keys.empty? ? :private_attributes : :keys
|
828
|
-
send(comparator) == other.send(comparator)
|
829
|
-
end
|
830
|
-
|
831
|
-
def ==(other)
|
832
|
-
eql?(other)
|
833
|
-
end
|
834
|
-
|
835
|
-
# Returns the difference between two objects, in terms of their
|
836
|
-
# attributes.
|
837
|
-
def ^(other)
|
838
|
-
results = {}
|
839
|
-
|
840
|
-
self_attributes, other_attributes = attributes, other.attributes
|
841
|
-
|
842
|
-
self_attributes.each_pair do |k,v|
|
843
|
-
other_value = other_attributes[k]
|
844
|
-
unless v == other_value
|
845
|
-
results[k] = [v, other_value]
|
846
|
-
end
|
847
|
-
end
|
848
|
-
|
849
|
-
results
|
850
|
-
end
|
851
|
-
end
|
852
|
-
end
|