datamapper 0.3.2 → 0.9.3
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/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
|