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
@@ -2,99 +2,190 @@ module DataMapper
2
2
  module Associations
3
3
 
4
4
  class HasAndBelongsToManyAssociation
5
- include Enumerable
6
5
 
7
- def initialize(instance, association_name, options)
8
- @instance = instance
6
+ attr_reader :adapter, :table
7
+
8
+ def initialize(klass, association_name, options)
9
+ @adapter = database.adapter
10
+ @table = adapter.table(klass)
9
11
  @association_name = association_name.to_sym
10
12
  @options = options
11
13
 
12
- @associated_class = if options.has_key?(:class) || options.has_key?(:class_name)
13
- associated_class_name = (options[:class] || options[:class_name])
14
- if associated_class_name.kind_of?(String)
15
- Kernel.const_get(Inflector.classify(associated_class_name))
16
- else
17
- associated_class_name
18
- end
19
- else
20
- Kernel.const_get(Inflector.classify(association_name))
21
- end
22
-
23
- @join_table_name = @options.has_key?(:join_table_name) ? @options[:join_table_name] : [Inflector.tableize(@instance.class.name), Inflector.tableize(@associated_class.name)].sort.join('_')
14
+ define_accessor(klass)
24
15
  end
25
16
 
26
- def self.setup(klass, association_name, options)
17
+ def name
18
+ @association_name
19
+ end
20
+
21
+ def foreign_name
22
+ @foreign_name || (@foreign_name = (@options[:foreign_name] || @table.name).to_sym)
23
+ end
24
+
25
+ def constant
26
+ @associated_class || @associated_class = begin
27
27
 
28
- # Define the association instance method (i.e. Project#tasks)
29
- klass.class_eval <<-EOS
30
- def #{association_name}
31
- @#{association_name} || (@#{association_name} = HasAndBelongsToManyAssociation.new(self, "#{association_name}", #{options.inspect}))
28
+ 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))
32
37
  end
33
- EOS
34
-
38
+
39
+ end
35
40
  end
36
-
37
- def each
38
- find.each { |item| yield item }
41
+
42
+ def association_columns
43
+ association_table.columns.reject { |column| column.lazy? } + join_columns
39
44
  end
40
45
 
41
- def size
42
- entries.size
46
+ def join_columns
47
+ [ left_foreign_key, right_foreign_key ]
43
48
  end
44
- alias length size
45
49
 
46
- def [](key)
47
- entries[key]
50
+ def association_table
51
+ @association_table || (@association_table = adapter.table(constant))
48
52
  end
49
53
 
50
- def empty?
51
- entries.empty?
54
+ def join_table
55
+ @join_table || @join_table = begin
56
+ join_table_name = @options[:join_table] ||
57
+ [ table.name.to_s, database.schema[constant].name.to_s ].sort.join('_')
58
+
59
+ adapter.table(join_table_name)
60
+ end
52
61
  end
53
62
 
54
- def find
55
- return @results unless @results.nil?
56
-
57
- unless @instance.loaded_set.nil?
58
-
59
- # Temp variable for the instance variable name.
60
- instance_variable_name = "@#{foreign_key}".to_sym
61
-
62
- @results = @instance.session.find(@associated_class, :all,
63
- :id.select => { :table => @join_table_name, foreign_key.to_sym => @instance.key }
64
- ) do |animal_id, ref|
65
- @instance.load_set.find { |x| x.id == animal_id }.exhibits << ref
66
- end
67
-
63
+ def left_foreign_key
64
+ @left_foreign_key || @left_foreign_key = begin
65
+ join_table.add_column(
66
+ (@options[:left_foreign_key] || table.default_foreign_key),
67
+ :integer, {})
68
68
  end
69
-
70
- return @results || (@results = [])
71
69
  end
72
-
73
- def set(results)
74
- @results = results
70
+
71
+ def right_foreign_key
72
+ @right_foreign_key || @right_foreign_key = begin
73
+ join_table.add_column(
74
+ (@options[:right_foreign_key] || association_table.default_foreign_key),
75
+ :integer, {})
76
+ end
75
77
  end
76
78
 
77
- def inspect
78
- @results.inspect
79
+ def to_sql
80
+ <<-EOS.compress_lines
81
+ JOIN #{join_table.to_sql} ON
82
+ #{left_foreign_key.to_sql(true)} = #{table.key.to_sql(true)}
83
+ JOIN #{association_table.to_sql} ON
84
+ #{association_table.key.to_sql(true)} = #{right_foreign_key.to_sql(true)}
85
+ EOS
79
86
  end
80
87
 
81
- def foreign_key
82
- @foreign_key || (@foreign_key = (@options[:foreign_key] || @instance.session.mappings[@instance.class].default_foreign_key))
88
+ def to_shallow_sql
89
+ <<-EOS.compress_lines
90
+ JOIN #{join_table.to_sql} ON
91
+ #{left_foreign_key.to_sql(true)} = #{table.key.to_sql(true)}
92
+ EOS
83
93
  end
84
-
85
- end
86
-
87
- module HasAndBelongsToMany
88
- def self.included(base)
89
- base.extend(ClassMethods)
94
+
95
+ # Define the association instance method (i.e. Project#tasks)
96
+ def define_accessor(klass)
97
+ klass.class_eval <<-EOS
98
+ def #{@association_name}
99
+ @#{@association_name} || (@#{@association_name} = HasAndBelongsToManyAssociation::Set.new(self, #{@association_name.inspect}))
100
+ end
101
+
102
+ def #{@association_name}=(value)
103
+ #{@association_name}.set(value)
104
+ end
105
+ EOS
90
106
  end
91
107
 
92
- module ClassMethods
93
- def has_and_belongs_to_many(association_name, options = {})
94
- HasAndBelongsToManyAssociation.setup(self, association_name, options)
108
+ class Set
109
+
110
+ include Enumerable
111
+
112
+ def initialize(instance, association_name)
113
+ @instance, @association_name = instance, association_name
114
+ end
115
+
116
+ def association
117
+ @association || (@association = @instance.session.schema[@instance.class].associations[@association_name])
118
+ end
119
+
120
+ def each
121
+ entries.each { |item| yield item }
122
+ end
123
+
124
+ def size
125
+ entries.size
126
+ end
127
+ alias length size
128
+
129
+ def [](key)
130
+ entries[key]
131
+ end
132
+
133
+ def empty?
134
+ entries.empty?
135
+ end
136
+
137
+ def entries
138
+ @entries || @entries = begin
139
+
140
+ if @instance.loaded_set.nil?
141
+ []
142
+ else
143
+
144
+ associated_items = Hash.new { |h,k| h[k] = [] }
145
+ left_key_index = nil
146
+ association_constant = association.constant
147
+ left_foreign_key = association.left_foreign_key
148
+
149
+ matcher = lambda do |instance,columns,row|
150
+
151
+ # Locate the column for the left-key.
152
+ unless left_key_index
153
+ left_key_index = columns.index(association.left_foreign_key)
154
+ end
155
+
156
+ if instance.kind_of?(association_constant)
157
+ associated_items[left_foreign_key.type_cast_value(row[left_key_index])] << instance
158
+ end
159
+ end
160
+
161
+ @instance.session.all(association.constant,
162
+ left_foreign_key => @instance.loaded_set.map(&:key),
163
+ :shallow_include => association.foreign_name,
164
+ :intercept_load => matcher
165
+ )
166
+
167
+ # do stsuff with associated_items hash.
168
+ setter_method = "#{@association_name}=".to_sym
169
+
170
+ @instance.loaded_set.each do |entry|
171
+ entry.send(setter_method, associated_items[entry.key])
172
+ end # @instance.loaded_set.each
173
+
174
+ @entries
175
+ end
176
+ end
177
+ end
178
+
179
+ def set(results)
180
+ @entries = results
181
+ end
182
+
183
+ def inspect
184
+ entries.inspect
95
185
  end
96
186
  end
97
- end
98
187
 
99
- end
100
- end
188
+ end # class HasAndBelongsToManyAssociation
189
+
190
+ end # module Associations
191
+ end # module DataMapper
@@ -1,100 +1,118 @@
1
+ require 'data_mapper/associations/has_n_association'
2
+
1
3
  module DataMapper
2
4
  module Associations
3
5
 
4
- class HasManyAssociation
5
- include Enumerable
6
-
7
- def initialize(instance, association_name, options)
8
- @instance = instance
9
- @association_name = association_name.to_sym
10
- @options = options
11
-
12
- @associated_class = if options.has_key?(:class) || options.has_key?(:class_name)
13
- associated_class_name = (options[:class] || options[:class_name])
14
- if associated_class_name.kind_of?(String)
15
- Kernel.const_get(Inflector.classify(associated_class_name))
16
- else
17
- associated_class_name
18
- end
19
- else
20
- Kernel.const_get(Inflector.classify(association_name))
21
- end
22
- end
6
+ class HasManyAssociation < HasNAssociation
23
7
 
24
- def self.setup(klass, association_name, options)
25
-
26
- # Define the association instance method (i.e. Project#tasks)
8
+ # Define the association instance method (i.e. Project#tasks)
9
+ def define_accessor(klass)
27
10
  klass.class_eval <<-EOS
28
- def #{association_name}
29
- @#{association_name} || (@#{association_name} = HasManyAssociation.new(self, "#{association_name}", #{options.inspect}))
11
+ def #{@association_name}
12
+ @#{@association_name} || (@#{@association_name} = DataMapper::Associations::HasManyAssociation::Set.new(self, #{@association_name.inspect}))
13
+ end
14
+
15
+ def #{@association_name}=(value)
16
+ #{@association_name}.set(value)
30
17
  end
31
18
  EOS
32
-
33
- end
34
-
35
- def each
36
- find.each { |item| yield item }
37
19
  end
38
20
 
39
- def size
40
- entries.size
41
- end
42
- alias length size
43
-
44
- def [](key)
45
- entries[key]
46
- end
47
-
48
- def empty?
49
- entries.empty?
50
- end
51
-
52
- def find
53
- return @results unless @results.nil?
21
+ class Set < Associations::Reference
22
+
23
+ include Enumerable
24
+
25
+ def dirty?
26
+ @items && @items.any? { |item| item != @instance && item.dirty? }
27
+ end
28
+
29
+ def validate_excluding_association(associated, context)
30
+ @items.blank? || @items.all? { |item| item.validate_excluding_association(associated, context) }
31
+ end
32
+
33
+ def save
34
+ unless @items.nil? || @items.empty?
35
+ setter_method = "#{@association_name}=".to_sym
36
+ ivar_name = association.foreign_key.instance_variable_name
37
+ @items.each do |item|
38
+ item.instance_variable_set(ivar_name, @instance.key)
39
+ item.save
40
+ end
41
+ end
42
+ end
43
+
44
+ def each
45
+ items.each { |item| yield item }
46
+ end
54
47
 
55
- unless @instance.loaded_set.nil?
48
+ def <<(associated_item)
49
+ items << associated_item
56
50
 
57
- # Temp variable for the instance variable name.
58
- instance_variable_name = "@#{foreign_key}".to_sym
51
+ # TODO: Optimize!
52
+ fk = association.foreign_key
53
+ foreign_association = association.association_table.associations.find do |mapping|
54
+ mapping.is_a?(BelongsToAssociation) && mapping.foreign_key == fk
55
+ end
59
56
 
60
- set = @instance.loaded_set.group_by { |instance| instance.key }
57
+ associated_item.send("#{foreign_association.name}=", @instance) if foreign_association
61
58
 
62
- # Fetch the foreign objects for all instances in the current object's loaded-set.
63
- @instance.session.all(@associated_class, foreign_key.to_sym => set.keys).group_by do |association|
64
- association.instance_variable_get(instance_variable_name)
65
- end.each_pair do |id, results|
66
- set[id].first.send(@association_name).set(results)
59
+ return @items
60
+ end
61
+
62
+ def set(items)
63
+ @items = items
64
+ end
65
+
66
+ def method_missing(symbol, *args, &block)
67
+ if items.respond_to?(symbol)
68
+ items.send(symbol, *args, &block)
69
+ elsif association.association_table.associations.any? { |assoc| assoc.name == symbol }
70
+ results = []
71
+ each do |item|
72
+ unless (val = item.send(symbol)).blank?
73
+ results << (val.is_a?(Enumerable) ? val.entries : val)
74
+ end
75
+ end
76
+ results.flatten
77
+ else
78
+ super
67
79
  end
68
-
69
80
  end
70
81
 
71
- return @results ||= []
72
- end
73
-
74
- def set(results)
75
- @results = results
76
- end
77
-
78
- def inspect
79
- @results.inspect
80
- end
81
-
82
- def foreign_key
83
- @foreign_key || (@foreign_key = (@options[:foreign_key] || @instance.session.schema[@instance.class].default_foreign_key))
84
- end
82
+ def respond_to?(symbol)
83
+ items.respond_to?(symbol) || super
84
+ end
85
85
 
86
- end
87
-
88
- module HasMany
89
- def self.included(base)
90
- base.extend(ClassMethods)
91
- end
92
-
93
- module ClassMethods
94
- def has_many(association_name, options = {})
95
- HasManyAssociation.setup(self, association_name, options)
86
+ def items
87
+ @items || begin
88
+ if @instance.loaded_set.nil?
89
+ @items = []
90
+ else
91
+ fk = association.foreign_key.to_sym
92
+
93
+ finder_options = { association.foreign_key.to_sym => @instance.loaded_set.map { |item| item.key } }
94
+ finder_options.merge!(association.finder_options)
95
+
96
+ associated_items = @instance.session.all(
97
+ association.constant,
98
+ finder_options
99
+ ).group_by { |entry| entry.send(fk) }
100
+
101
+ setter_method = "#{@association_name}=".to_sym
102
+ @instance.loaded_set.each do |entry|
103
+ entry.send(setter_method, associated_items[entry.key])
104
+ end # @instance.loaded_set.each
105
+
106
+ return @items
107
+ end # if @instance.loaded_set.nil?
108
+ end # begin
109
+ end # def items
110
+
111
+ def inspect
112
+ @entries.inspect
96
113
  end
97
114
  end
115
+
98
116
  end
99
117
 
100
118
  end