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
@@ -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