datamapper 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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