datamapper 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. data/CHANGELOG +65 -0
  2. data/README +193 -1
  3. data/do_performance.rb +153 -0
  4. data/environment.rb +45 -0
  5. data/example.rb +119 -22
  6. data/lib/data_mapper.rb +36 -16
  7. data/lib/data_mapper/adapters/abstract_adapter.rb +8 -0
  8. data/lib/data_mapper/adapters/data_object_adapter.rb +360 -0
  9. data/lib/data_mapper/adapters/mysql_adapter.rb +30 -179
  10. data/lib/data_mapper/adapters/postgresql_adapter.rb +90 -199
  11. data/lib/data_mapper/adapters/sql/coersion.rb +32 -3
  12. data/lib/data_mapper/adapters/sql/commands/conditions.rb +97 -128
  13. data/lib/data_mapper/adapters/sql/commands/load_command.rb +234 -231
  14. data/lib/data_mapper/adapters/sql/commands/loader.rb +99 -0
  15. data/lib/data_mapper/adapters/sql/mappings/associations_set.rb +30 -0
  16. data/lib/data_mapper/adapters/sql/mappings/column.rb +68 -6
  17. data/lib/data_mapper/adapters/sql/mappings/schema.rb +6 -3
  18. data/lib/data_mapper/adapters/sql/mappings/table.rb +71 -42
  19. data/lib/data_mapper/adapters/sql/quoting.rb +8 -2
  20. data/lib/data_mapper/adapters/sqlite3_adapter.rb +32 -201
  21. data/lib/data_mapper/associations.rb +21 -7
  22. data/lib/data_mapper/associations/belongs_to_association.rb +96 -80
  23. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +158 -67
  24. data/lib/data_mapper/associations/has_many_association.rb +96 -78
  25. data/lib/data_mapper/associations/has_n_association.rb +64 -0
  26. data/lib/data_mapper/associations/has_one_association.rb +49 -79
  27. data/lib/data_mapper/associations/reference.rb +47 -0
  28. data/lib/data_mapper/base.rb +216 -50
  29. data/lib/data_mapper/callbacks.rb +71 -24
  30. data/lib/data_mapper/{session.rb → context.rb} +20 -8
  31. data/lib/data_mapper/database.rb +176 -45
  32. data/lib/data_mapper/embedded_value.rb +65 -0
  33. data/lib/data_mapper/identity_map.rb +12 -4
  34. data/lib/data_mapper/support/active_record_impersonation.rb +12 -8
  35. data/lib/data_mapper/support/enumerable.rb +8 -0
  36. data/lib/data_mapper/support/serialization.rb +13 -0
  37. data/lib/data_mapper/support/string.rb +1 -12
  38. data/lib/data_mapper/support/symbol.rb +3 -0
  39. data/lib/data_mapper/validations/unique_validator.rb +1 -2
  40. data/lib/data_mapper/validations/validation_helper.rb +18 -1
  41. data/performance.rb +109 -34
  42. data/plugins/can_has_sphinx/LICENSE +23 -0
  43. data/plugins/can_has_sphinx/README +4 -0
  44. data/plugins/can_has_sphinx/REVISION +1 -0
  45. data/plugins/can_has_sphinx/Rakefile +22 -0
  46. data/plugins/can_has_sphinx/init.rb +1 -0
  47. data/plugins/can_has_sphinx/install.rb +1 -0
  48. data/plugins/can_has_sphinx/lib/acts_as_sphinx.rb +123 -0
  49. data/plugins/can_has_sphinx/lib/sphinx.rb +460 -0
  50. data/plugins/can_has_sphinx/scripts/sphinx.sh +47 -0
  51. data/plugins/can_has_sphinx/tasks/acts_as_sphinx_tasks.rake +41 -0
  52. data/plugins/dataobjects/REVISION +1 -0
  53. data/plugins/dataobjects/Rakefile +7 -0
  54. data/plugins/dataobjects/do.rb +246 -0
  55. data/plugins/dataobjects/do_mysql.rb +179 -0
  56. data/plugins/dataobjects/do_postgres.rb +181 -0
  57. data/plugins/dataobjects/do_sqlite3.rb +153 -0
  58. data/plugins/dataobjects/spec/do_spec.rb +150 -0
  59. data/plugins/dataobjects/spec/spec_helper.rb +81 -0
  60. data/plugins/dataobjects/swig_mysql/do_mysql.bundle +0 -0
  61. data/plugins/dataobjects/swig_mysql/extconf.rb +33 -0
  62. data/plugins/dataobjects/swig_mysql/mysql_c.c +18800 -0
  63. data/plugins/dataobjects/swig_mysql/mysql_c.i +8 -0
  64. data/plugins/dataobjects/swig_mysql/mysql_supp.i +46 -0
  65. data/plugins/dataobjects/swig_postgres/Makefile +146 -0
  66. data/plugins/dataobjects/swig_postgres/extconf.rb +29 -0
  67. data/plugins/dataobjects/swig_postgres/postgres_c.bundle +0 -0
  68. data/plugins/dataobjects/swig_postgres/postgres_c.c +8185 -0
  69. data/plugins/dataobjects/swig_postgres/postgres_c.i +73 -0
  70. data/plugins/dataobjects/swig_sqlite/db +0 -0
  71. data/plugins/dataobjects/swig_sqlite/extconf.rb +9 -0
  72. data/plugins/dataobjects/swig_sqlite/sqlite3_c.c +4725 -0
  73. data/plugins/dataobjects/swig_sqlite/sqlite_c.i +168 -0
  74. data/rakefile.rb +45 -23
  75. data/spec/acts_as_tree_spec.rb +39 -0
  76. data/spec/associations_spec.rb +220 -0
  77. data/spec/attributes_spec.rb +15 -0
  78. data/spec/base_spec.rb +44 -0
  79. data/spec/callbacks_spec.rb +45 -0
  80. data/spec/can_has_sphinx.rb +6 -0
  81. data/spec/coersion_spec.rb +34 -0
  82. data/spec/conditions_spec.rb +49 -0
  83. data/spec/conversions_to_yaml_spec.rb +17 -0
  84. data/spec/count_command_spec.rb +11 -0
  85. data/spec/delete_command_spec.rb +1 -1
  86. data/spec/embedded_value_spec.rb +23 -0
  87. data/spec/fixtures/animals_exhibits.yaml +2 -0
  88. data/spec/fixtures/people.yaml +18 -1
  89. data/spec/{legacy.rb → legacy_spec.rb} +3 -3
  90. data/spec/load_command_spec.rb +157 -20
  91. data/spec/magic_columns_spec.rb +9 -0
  92. data/spec/mock_adapter.rb +20 -0
  93. data/spec/models/animal.rb +1 -1
  94. data/spec/models/animals_exhibit.rb +6 -0
  95. data/spec/models/exhibit.rb +2 -0
  96. data/spec/models/person.rb +26 -1
  97. data/spec/models/project.rb +19 -0
  98. data/spec/models/sales_person.rb +1 -0
  99. data/spec/models/section.rb +6 -0
  100. data/spec/models/zoo.rb +3 -1
  101. data/spec/query_spec.rb +9 -0
  102. data/spec/save_command_spec.rb +65 -1
  103. data/spec/schema_spec.rb +89 -0
  104. data/spec/single_table_inheritance_spec.rb +27 -0
  105. data/spec/spec_helper.rb +9 -55
  106. data/spec/{symbolic_operators.rb → symbolic_operators_spec.rb} +9 -5
  107. data/spec/{validates_confirmation_of.rb → validates_confirmation_of_spec.rb} +4 -3
  108. data/spec/{validates_format_of.rb → validates_format_of_spec.rb} +5 -4
  109. data/spec/{validates_length_of.rb → validates_length_of_spec.rb} +8 -7
  110. data/spec/{validates_uniqueness_of.rb → validates_uniqueness_of_spec.rb} +7 -10
  111. data/spec/{validations.rb → validations_spec.rb} +24 -6
  112. data/tasks/drivers.rb +20 -0
  113. data/tasks/fixtures.rb +42 -0
  114. metadata +181 -42
  115. data/lib/data_mapper/adapters/sql/commands/advanced_load_command.rb +0 -140
  116. data/lib/data_mapper/adapters/sql/commands/delete_command.rb +0 -113
  117. data/lib/data_mapper/adapters/sql/commands/save_command.rb +0 -141
  118. data/lib/data_mapper/adapters/sql/commands/table_exists_command.rb +0 -33
  119. data/lib/data_mapper/adapters/sql_adapter.rb +0 -163
  120. data/lib/data_mapper/associations/advanced_has_many_association.rb +0 -55
  121. data/lib/data_mapper/support/blank_slate.rb +0 -3
  122. data/lib/data_mapper/support/proc.rb +0 -69
  123. data/lib/data_mapper/support/struct.rb +0 -26
  124. data/lib/data_mapper/unit_of_work.rb +0 -38
  125. data/spec/basic_finder.rb +0 -67
  126. data/spec/belongs_to.rb +0 -47
  127. data/spec/has_and_belongs_to_many.rb +0 -25
  128. data/spec/has_many.rb +0 -34
  129. data/spec/new_record.rb +0 -24
  130. data/spec/sub_select.rb +0 -16
  131. data/spec/support/string_spec.rb +0 -7
@@ -0,0 +1,64 @@
1
+ module DataMapper
2
+ module Associations
3
+
4
+ class HasNAssociation
5
+
6
+ attr_reader :adapter, :table, :options
7
+
8
+ OPTIONS = [
9
+ :class,
10
+ :class_name,
11
+ :foreign_key
12
+ ]
13
+
14
+ def initialize(klass, association_name, options)
15
+ @adapter = database.adapter
16
+ @table = adapter.table(klass)
17
+ @association_name = association_name.to_sym
18
+ @options = options || Hash.new
19
+
20
+ define_accessor(klass)
21
+ end
22
+
23
+ def name
24
+ @association_name
25
+ end
26
+
27
+ def constant
28
+ @associated_class || @associated_class = if @options.has_key?(:class) || @options.has_key?(:class_name)
29
+ associated_class_name = (@options[:class] || @options[:class_name])
30
+ if associated_class_name.kind_of?(String)
31
+ Kernel.const_get(Inflector.classify(associated_class_name))
32
+ else
33
+ associated_class_name
34
+ end
35
+ else
36
+ Kernel.const_get(Inflector.classify(@association_name))
37
+ end
38
+ end
39
+
40
+ def foreign_key
41
+ @foreign_key || @foreign_key = begin
42
+ association_table[@options[:foreign_key] || table.default_foreign_key]
43
+ end
44
+ end
45
+
46
+ def association_table
47
+ @association_table || (@association_table = adapter.table(constant))
48
+ end
49
+
50
+ def to_sql
51
+ "JOIN #{association_table.to_sql} ON #{foreign_key.to_sql(true)} = #{table.key.to_sql(true)}"
52
+ end
53
+
54
+ def association_columns
55
+ association_table.columns.reject { |column| column.lazy? }
56
+ end
57
+
58
+ def finder_options
59
+ @finder_options || @finder_options = @options.reject { |k,v| self.class::OPTIONS.include?(k) }
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -1,107 +1,77 @@
1
+ require 'data_mapper/associations/has_n_association'
2
+
1
3
  module DataMapper
2
4
  module Associations
3
5
 
4
- class HasOneAssociation
5
-
6
- def initialize(instance, association_name, options)
7
- @instance = instance
8
- @association_name = association_name
9
- @options = options
10
-
11
- @associated_class = if options.has_key?(:class) || options.has_key?(:class_name)
12
- associated_class_name = (options[:class] || options[:class_name])
13
- if associated_class_name.kind_of?(String)
14
- Kernel.const_get(Inflector.classify(associated_class_name))
15
- else
16
- associated_class_name
17
- end
18
- else
19
- Kernel.const_get(Inflector.classify(association_name))
20
- end
21
- end
6
+ class HasOneAssociation < HasNAssociation
22
7
 
23
- def self.setup(klass, association_name, options)
24
-
25
- # Define the association instance method (i.e. Exhibit#zoo)
8
+ # Define the association instance method (i.e. Project#tasks)
9
+ def define_accessor(klass)
26
10
  klass.class_eval <<-EOS
27
- def create_#{association_name}(options = {})
28
- #{association_name}_association.create(options)
11
+ def create_#{@association_name}(options)
12
+ #{@association_name}_association.create(options)
29
13
  end
30
14
 
31
- def build_#{association_name}(options = {})
32
- #{association_name}_association.build(options)
15
+ def build_#{@association_name}(options)
16
+ #{@association_name}_association.build(options)
33
17
  end
34
18
 
35
- def #{association_name}
36
- # Let the HasOneAssociation do the finding, just to keep things neat around here...
37
- #{association_name}_association.find
19
+ def #{@association_name}
20
+ #{@association_name}_association.instance
38
21
  end
39
22
 
40
- def #{association_name}=(value)
41
- #{association_name}_association.set(value)
23
+ def #{@association_name}=(value)
24
+ #{@association_name}_association.set(value)
42
25
  end
43
26
 
44
27
  private
45
- def #{association_name}_association
46
- @#{association_name} || (@#{association_name} = HasOneAssociation.new(self, "#{association_name}", #{options.inspect}))
28
+ def #{@association_name}_association
29
+ @#{@association_name}_association || (@#{@association_name}_association = DataMapper::Associations::HasOneAssociation::Instance.new(self, #{@association_name.inspect}))
47
30
  end
48
31
  EOS
49
-
50
32
  end
51
33
 
52
- def find
53
- return @result unless @result.nil?
54
-
55
- unless @instance.loaded_set.nil?
56
-
57
- # Temp variable for the instance variable name.
58
- setter_method = "#{@association_name}=".to_sym
59
- instance_variable_name = "@#{foreign_key}".to_sym
34
+ class Instance < Associations::Reference
35
+
36
+ def instance
37
+ @associated || @associated = begin
38
+ if @instance.loaded_set.nil?
39
+ nil
40
+ else
41
+ # Temp variable for the instance variable name.
42
+ setter_method = "#{@association_name}=".to_sym
43
+ instance_variable_name = "@#{association.foreign_key}".to_sym
60
44
 
61
- set = @instance.loaded_set.group_by { |instance| instance.key }
45
+ set = @instance.loaded_set.group_by { |instance| instance.key }
62
46
 
63
- # Fetch the foreign objects for all instances in the current object's loaded-set.
64
- @instance.session.all(@associated_class, foreign_key => set.keys).each do |association|
65
- set[association.instance_variable_get(instance_variable_name)].first.send(setter_method, association)
47
+ # Fetch the foreign objects for all instances in the current object's loaded-set.
48
+ @instance.session.all(association.constant, association.foreign_key => set.keys).each do |assoc|
49
+ set[assoc.instance_variable_get(instance_variable_name)].first.send(setter_method, assoc)
50
+ end
51
+
52
+ @associated
53
+ end
54
+
66
55
  end
67
56
  end
68
-
69
- return @result
70
- end
71
57
 
72
- def create(options = {})
73
- associated = @associated_class.new(options)
74
- if associated.save
75
- @instance.send("#{@associated_class.foreign_key}=", associated.id)
76
- @result = associated
58
+ def create(options)
59
+ @associated = association.constant.new(options)
60
+ if @associated.save
61
+ @associated.send("#{@associated_class.foreign_key}=", @instance.key)
62
+ end
77
63
  end
78
- end
79
64
 
80
- def build(options = {})
81
- @result = @associated_class.new(options)
82
- end
83
-
84
- def set(val)
85
- @result = val
86
- end
65
+ def build(options)
66
+ @associated = association.constant.new(options)
67
+ end
87
68
 
88
- def foreign_key
89
- @foreign_key ||= (@options[:foreign_key] || @instance.session.mappings[@instance.class].default_foreign_key)
90
- end
69
+ def set(val)
70
+ @associated = val
71
+ end
91
72
 
92
- end
93
-
94
- module HasOne
95
- def self.included(base)
96
- base.extend(ClassMethods)
97
- end
73
+ end # class Instance
98
74
 
99
- module ClassMethods
100
- def has_one(association_name, options = {})
101
- HasOneAssociation.setup(self, association_name, options)
102
- end
103
- end
104
- end
105
-
106
- end
107
- end
75
+ end # class HasOneAssociation
76
+ end # module Associations
77
+ end # module DataMapper
@@ -0,0 +1,47 @@
1
+ module DataMapper
2
+
3
+ module Associations
4
+
5
+ # Reference is an abstract-class providing the boiler-plate for
6
+ # the association proxies (ie: HasManyAssociation::Set, or
7
+ # HasOneAssociation::Instance)
8
+ # The proxies need to access the defining Association instances
9
+ # to obtain mapping information. This class provides convenient
10
+ # access to said Association.
11
+ #
12
+ # EXAMPLE:
13
+ # class Zoo
14
+ # has_many :exhibits
15
+ # end
16
+ # The +has_many+ declaration instantiates a
17
+ # DataMapper::Associations::HasManyAssociation and adds it to the
18
+ # DataMapper::Adapters::Sql::Mappings::Table#associations array for
19
+ # the Table representing Zoo.
20
+ #
21
+ # Zoo.new.exhibits
22
+ # +exhibits+ above returns an instance of
23
+ # DataMapper::Associations::HasManyAssociation::Set. This instance
24
+ # needs to access the actual HasManyAssociation instance in order
25
+ # to access the mapping information within. The DataMapper::Associations::Reference
26
+ # abstract-class for the Set provides the Reference#association method in order to
27
+ # provide easy access to this information.
28
+ class Reference
29
+
30
+ # +instance+ is a mapped object instance. ie: #<Zoo:0x123456 ...>
31
+ # +association_name+ is the Symbol used to look up the Association
32
+ # instance within the DataMapper::Adapters::Sql::Mappings::Table
33
+ def initialize(instance, association_name)
34
+ @instance, @association_name = instance, association_name.to_sym
35
+ @instance.loaded_associations << self
36
+ end
37
+
38
+ # #association provides lazily initialized access to the declared
39
+ # Association.
40
+ def association
41
+ @association || (@association = @instance.session.table(@instance.class).associations[@association_name])
42
+ end
43
+
44
+ end
45
+ end
46
+
47
+ end
@@ -1,8 +1,14 @@
1
- require 'data_mapper/unit_of_work'
2
1
  require 'data_mapper/support/active_record_impersonation'
2
+ require 'data_mapper/support/serialization'
3
3
  require 'data_mapper/validations/validation_helper'
4
4
  require 'data_mapper/associations'
5
5
  require 'data_mapper/callbacks'
6
+ require 'data_mapper/embedded_value'
7
+
8
+ begin
9
+ require 'ferret'
10
+ rescue LoadError
11
+ end
6
12
 
7
13
  module DataMapper
8
14
 
@@ -11,56 +17,123 @@ module DataMapper
11
17
  # This probably needs to be protected
12
18
  attr_accessor :loaded_set
13
19
 
14
- include UnitOfWork
20
+ include CallbacksHelper
15
21
  include Support::ActiveRecordImpersonation
22
+ include Support::Serialization
16
23
  include Validations::ValidationHelper
17
24
  include Associations
18
25
 
26
+ # Track classes that inherit from DataMapper::Base.
27
+ def self.subclasses
28
+ @subclasses || (@subclasses = [])
29
+ end
30
+
31
+ def self.auto_migrate!
32
+ subclasses.each do |subclass|
33
+ subclass.auto_migrate!
34
+ end
35
+ end
36
+
19
37
  def self.inherited(klass)
20
- klass.send(:undef_method, :id)
38
+ DataMapper::Base::subclasses << klass
39
+ klass.send(:undef_method, :id)
21
40
 
22
41
  # When this class is sub-classed, copy the declared columns.
23
42
  klass.class_eval do
24
- def self.inherited(subclass)
25
-
26
- database.schema[subclass.superclass].columns.each do |c|
27
- subclass.property(c.name, c.type, c.options)
28
- subclass.before_create do
29
- @type = self.class
30
- end if c.name == :type
43
+
44
+ def self.auto_migrate!
45
+ if self::subclasses.empty?
46
+ database.schema[self].drop!
47
+ database.save(self)
48
+ else
49
+ schema = database.schema
50
+ columns = self::subclasses.inject(schema[self].columns) do |span, subclass|
51
+ span + schema[subclass].columns
52
+ end
53
+
54
+ table_name = schema[self].name.to_s
55
+ table = schema[table_name]
56
+ columns.each do |column|
57
+ table.add_column(column.name, column.type, column.options)
58
+ end
59
+
60
+ table.drop!
61
+ table.create!
31
62
  end
32
-
63
+ end
64
+
65
+ def self.subclasses
66
+ @subclasses || (@subclasses = [])
67
+ end
68
+
69
+ def self.inherited(subclass)
70
+ self::subclasses << subclass
33
71
  end
34
72
  end
35
73
  end
36
74
 
75
+ # Allows you to override the table name for a model.
76
+ # EXAMPLE:
77
+ # class WorkItem
78
+ # set_table_name 't_work_item_list'
79
+ # end
37
80
  def self.set_table_name(value)
38
81
  database.schema[self].name = value
39
82
  end
40
83
 
41
84
  def initialize(details = nil)
42
85
 
43
- unless details.nil?
44
- details.reject do |key, value|
45
- protected_attribute? key
46
- end.each_pair do |key, value|
47
- instance_variable_set("@#{key}", value)
48
- end
86
+ case details
87
+ when Hash then self.attributes = details
88
+ when DataMapper::Base then self.attributes = details.attributes
89
+ when NilClass then nil
49
90
  end
50
91
  end
51
92
 
93
+ # Adds property accessors for a field that you'd like to be able to modify. The DataMapper doesn't
94
+ # use the table schema to infer accessors, you must explicity call #property to add field accessors
95
+ # to your model.
96
+ # EXAMPLE:
97
+ # class CellProvider
98
+ # property :name, :string
99
+ # property :rating, :integer
100
+ # end
101
+ #
102
+ # att = CellProvider.new(:name => 'AT&T')
103
+ # att.rating = 3
104
+ # puts att.name, att.rating
105
+ #
106
+ # => AT&T
107
+ # => 3
52
108
  def self.property(name, type, options = {})
53
109
  mapping = database.schema[self].add_column(name, type, options)
54
110
  property_getter(name, mapping)
55
111
  property_setter(name, mapping)
112
+
113
+ if MAGIC_PROPERTIES.has_key?(name)
114
+ class_eval(&MAGIC_PROPERTIES[name])
115
+ end
116
+
56
117
  return name
57
118
  end
58
119
 
120
+ MAGIC_PROPERTIES = {
121
+ :updated_at => lambda { before_save { |x| x.updated_at = Time::now } },
122
+ :updated_on => lambda { before_save { |x| x.updated_on = Date::today } },
123
+ :created_at => lambda { before_create { |x| x.created_at = Time::now } },
124
+ :created_on => lambda { before_create { |x| x.created_on = Date::today } }
125
+ }
126
+
127
+ def self.embed(class_or_name, &block)
128
+ EmbeddedValue::define(self, class_or_name, &block)
129
+ end
130
+
59
131
  def self.property_getter(name, mapping)
60
132
  if mapping.lazy?
61
133
  class_eval <<-EOS
62
134
  def #{name}
63
- lazy_load!("#{name}")
135
+ lazy_load!(#{name.inspect})
136
+ @#{name}
64
137
  end
65
138
  EOS
66
139
  else
@@ -83,34 +156,114 @@ module DataMapper
83
156
  end
84
157
  end
85
158
 
86
- def lazy_load!(name)
87
- (class << self; self end).send(:attr_accessor, name)
159
+ # Lazy-loads the attributes for a loaded_set, then overwrites the accessors
160
+ # for the named methods so that the lazy_loading is skipped the second time.
161
+ def lazy_load!(*names)
162
+
163
+ reset_attribute = lambda do |instance|
164
+ singleton_class = (class << instance; self end)
165
+ names.each do |name|
166
+ singleton_class.send(:attr_accessor, name)
167
+ end
168
+ end
88
169
 
89
- column = session.schema[self.class][name.to_sym]
170
+ unless new_record? || loaded_set.nil?
171
+ session.all(
172
+ self.class,
173
+ :select => ([:id] + names),
174
+ :reload => true,
175
+ :id => loaded_set.map(&:id)
176
+ ).each(&reset_attribute)
177
+ else
178
+ reset_attribute[self]
179
+ end
90
180
 
91
- # If the value is already loaded, then we don't need to do it again.
92
- value = instance_variable_get(column.instance_variable_name)
93
- return value unless value.nil?
181
+ end
182
+
183
+ def new_record?
184
+ @new_record.nil? || @new_record
185
+ end
186
+
187
+ def loaded_attributes
188
+ pairs = {}
94
189
 
95
- session.all(self.class, :select => [:id, name], :reload => true, :id => loaded_set.map(&:id)).each do |instance|
96
- (class << self; self end).send(:attr_accessor, name)
190
+ session.table(self).columns.each do |column|
191
+ pairs[column.name] = instance_variable_get(column.instance_variable_name)
97
192
  end
98
193
 
99
- instance_variable_get(column.instance_variable_name)
194
+ pairs
100
195
  end
101
-
196
+
102
197
  def attributes
103
- session.schema[self.class].columns.inject({}) do |values, column|
104
- values[column.name] = instance_variable_get(column.instance_variable_name); values
198
+ pairs = {}
199
+
200
+ session.table(self).columns.each do |column|
201
+ lazy_load!(column.name) if column.lazy?
202
+ value = instance_variable_get(column.instance_variable_name)
203
+ pairs[column.name] = column.type == :class ? value.to_s : value
105
204
  end
205
+
206
+ pairs
106
207
  end
107
208
 
209
+ # Mass-assign mapped fields.
108
210
  def attributes=(values_hash)
211
+ table = session.schema[self.class]
212
+
109
213
  values_hash.reject do |key, value|
110
214
  protected_attribute? key
111
215
  end.each_pair do |key, value|
112
- symbolic_instance_variable_set(key, value)
216
+ if column = table[key]
217
+ instance_variable_set(column.instance_variable_name, value)
218
+ else
219
+ send("#{key}=", value)
220
+ end
221
+ end
222
+ end
223
+
224
+ def dirty?(name = nil)
225
+ if name.nil?
226
+ session.table(self).columns.any? do |column|
227
+ if value = self.instance_variable_get(column.instance_variable_name)
228
+ value.hash != original_hashes[column.name]
229
+ else
230
+ false
231
+ end
232
+ end || loaded_associations.any? do |loaded_association|
233
+ if loaded_association.respond_to?(:dirty?)
234
+ loaded_association.dirty?
235
+ else
236
+ false
237
+ end
238
+ end
239
+ else
240
+ key = name.kind_of?(Symbol) ? name : name.to_sym
241
+ self.instance_variable_get("@#{name}").hash != original_hashes[key]
242
+ end
243
+ end
244
+
245
+ def dirty_attributes
246
+ pairs = {}
247
+
248
+ if new_record?
249
+ session.table(self).columns.each do |column|
250
+ unless (value = instance_variable_get(column.instance_variable_name)).nil?
251
+ pairs[column.name] = value
252
+ end
253
+ end
254
+ else
255
+ session.table(self).columns.each do |column|
256
+ if (value = instance_variable_get(column.instance_variable_name)).hash != original_hashes[column.name]
257
+ pairs[column.name] = value
258
+ end
259
+ end
113
260
  end
261
+
262
+ pairs
263
+ end
264
+
265
+ def original_hashes
266
+ @original_hashes || (@original_hashes = {})
114
267
  end
115
268
 
116
269
  def protected_attribute?(key)
@@ -121,12 +274,33 @@ module DataMapper
121
274
  @protected_attributes ||= []
122
275
  end
123
276
 
277
+ def self.index
278
+ @index || @index = Ferret::Index::Index.new(:path => "#{database.adapter.index_path}/#{name}")
279
+ end
280
+
281
+ def self.reindex!
282
+ all.each do |record|
283
+ index << record.attributes
284
+ end
285
+ end
286
+
287
+ def self.search(phrase)
288
+ ids = []
289
+
290
+ query = "#{database.schema[self].columns.map(&:name).join('|')}:\"#{phrase}\""
291
+
292
+ index.search_each(query) do |document_id, score|
293
+ ids << index[document_id][:id]
294
+ end
295
+ return all(:id => ids)
296
+ end
297
+
124
298
  def self.protect(*keys)
125
299
  keys.each { |key| protected_attributes << key.to_sym }
126
300
  end
127
301
 
128
302
  def self.foreign_key
129
- String::memoized_underscore(self.name) + "_id"
303
+ Inflector.underscore(self.name) + "_id"
130
304
  end
131
305
 
132
306
  def inspect
@@ -141,6 +315,10 @@ module DataMapper
141
315
  "#<%s:0x%x @new_record=%s, %s>" % [self.class.name, (object_id * 2), new_record?, inspected_attributes.join(', ')]
142
316
  end
143
317
 
318
+ def loaded_associations
319
+ @loaded_associations || @loaded_associations = []
320
+ end
321
+
144
322
  def session=(value)
145
323
  @session = value
146
324
  end
@@ -149,6 +327,12 @@ module DataMapper
149
327
  @session || ( @session = database )
150
328
  end
151
329
 
330
+ def key=(value)
331
+ key_column = session.schema[self.class].key
332
+ @__key = key_column.type_cast_value(value)
333
+ instance_variable_set(key_column.instance_variable_name, @__key)
334
+ end
335
+
152
336
  def key
153
337
  @__key || @__key = begin
154
338
  key_column = session.schema[self.class].key
@@ -156,24 +340,6 @@ module DataMapper
156
340
  end
157
341
  end
158
342
 
159
- # Callbacks associated with this class.
160
- def self.callbacks
161
- @callbacks || ( @callbacks = Callbacks.new )
162
- end
163
-
164
- # Declare helpers for the standard callbacks
165
- DataMapper::Callbacks::EVENTS.each do |name|
166
- class_eval <<-EOS
167
- def self.#{name}(string = nil, &block)
168
- if string.nil?
169
- callbacks.add(:#{name}, &block)
170
- else
171
- callbacks.add(:#{name}, string)
172
- end
173
- end
174
- EOS
175
- end
176
-
177
343
  end
178
344
 
179
345
  end