ninjudd-model_set 0.9.2

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 (95) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +39 -0
  3. data/VERSION.yml +4 -0
  4. data/lib/model_set.rb +712 -0
  5. data/lib/model_set/conditioned.rb +33 -0
  6. data/lib/model_set/conditions.rb +103 -0
  7. data/lib/model_set/query.rb +128 -0
  8. data/lib/model_set/raw_query.rb +41 -0
  9. data/lib/model_set/raw_sql_query.rb +19 -0
  10. data/lib/model_set/set_query.rb +34 -0
  11. data/lib/model_set/solr_query.rb +70 -0
  12. data/lib/model_set/sphinx_query.rb +148 -0
  13. data/lib/model_set/sql_base_query.rb +52 -0
  14. data/lib/model_set/sql_query.rb +75 -0
  15. data/lib/multi_set.rb +67 -0
  16. data/test/model_set_test.rb +283 -0
  17. data/test/multi_set_test.rb +65 -0
  18. data/test/test_helper.rb +23 -0
  19. data/vendor/sphinx_client/README.rdoc +41 -0
  20. data/vendor/sphinx_client/Rakefile +21 -0
  21. data/vendor/sphinx_client/init.rb +1 -0
  22. data/vendor/sphinx_client/install.rb +5 -0
  23. data/vendor/sphinx_client/lib/sphinx.rb +6 -0
  24. data/vendor/sphinx_client/lib/sphinx/client.rb +1093 -0
  25. data/vendor/sphinx_client/lib/sphinx/request.rb +50 -0
  26. data/vendor/sphinx_client/lib/sphinx/response.rb +69 -0
  27. data/vendor/sphinx_client/spec/client_response_spec.rb +112 -0
  28. data/vendor/sphinx_client/spec/client_spec.rb +469 -0
  29. data/vendor/sphinx_client/spec/fixtures/default_search.php +8 -0
  30. data/vendor/sphinx_client/spec/fixtures/default_search_index.php +8 -0
  31. data/vendor/sphinx_client/spec/fixtures/excerpt_custom.php +11 -0
  32. data/vendor/sphinx_client/spec/fixtures/excerpt_default.php +8 -0
  33. data/vendor/sphinx_client/spec/fixtures/excerpt_flags.php +11 -0
  34. data/vendor/sphinx_client/spec/fixtures/field_weights.php +9 -0
  35. data/vendor/sphinx_client/spec/fixtures/filter.php +9 -0
  36. data/vendor/sphinx_client/spec/fixtures/filter_exclude.php +9 -0
  37. data/vendor/sphinx_client/spec/fixtures/filter_float_range.php +9 -0
  38. data/vendor/sphinx_client/spec/fixtures/filter_float_range_exclude.php +9 -0
  39. data/vendor/sphinx_client/spec/fixtures/filter_range.php +9 -0
  40. data/vendor/sphinx_client/spec/fixtures/filter_range_exclude.php +9 -0
  41. data/vendor/sphinx_client/spec/fixtures/filter_range_int64.php +10 -0
  42. data/vendor/sphinx_client/spec/fixtures/filter_ranges.php +10 -0
  43. data/vendor/sphinx_client/spec/fixtures/filters.php +10 -0
  44. data/vendor/sphinx_client/spec/fixtures/filters_different.php +13 -0
  45. data/vendor/sphinx_client/spec/fixtures/geo_anchor.php +9 -0
  46. data/vendor/sphinx_client/spec/fixtures/group_by_attr.php +9 -0
  47. data/vendor/sphinx_client/spec/fixtures/group_by_attrpair.php +9 -0
  48. data/vendor/sphinx_client/spec/fixtures/group_by_day.php +9 -0
  49. data/vendor/sphinx_client/spec/fixtures/group_by_day_sort.php +9 -0
  50. data/vendor/sphinx_client/spec/fixtures/group_by_month.php +9 -0
  51. data/vendor/sphinx_client/spec/fixtures/group_by_week.php +9 -0
  52. data/vendor/sphinx_client/spec/fixtures/group_by_year.php +9 -0
  53. data/vendor/sphinx_client/spec/fixtures/group_distinct.php +10 -0
  54. data/vendor/sphinx_client/spec/fixtures/id_range.php +9 -0
  55. data/vendor/sphinx_client/spec/fixtures/id_range64.php +9 -0
  56. data/vendor/sphinx_client/spec/fixtures/index_weights.php +9 -0
  57. data/vendor/sphinx_client/spec/fixtures/keywords.php +8 -0
  58. data/vendor/sphinx_client/spec/fixtures/limits.php +9 -0
  59. data/vendor/sphinx_client/spec/fixtures/limits_cutoff.php +9 -0
  60. data/vendor/sphinx_client/spec/fixtures/limits_max.php +9 -0
  61. data/vendor/sphinx_client/spec/fixtures/limits_max_cutoff.php +9 -0
  62. data/vendor/sphinx_client/spec/fixtures/match_all.php +9 -0
  63. data/vendor/sphinx_client/spec/fixtures/match_any.php +9 -0
  64. data/vendor/sphinx_client/spec/fixtures/match_boolean.php +9 -0
  65. data/vendor/sphinx_client/spec/fixtures/match_extended.php +9 -0
  66. data/vendor/sphinx_client/spec/fixtures/match_extended2.php +9 -0
  67. data/vendor/sphinx_client/spec/fixtures/match_fullscan.php +9 -0
  68. data/vendor/sphinx_client/spec/fixtures/match_phrase.php +9 -0
  69. data/vendor/sphinx_client/spec/fixtures/max_query_time.php +9 -0
  70. data/vendor/sphinx_client/spec/fixtures/miltiple_queries.php +12 -0
  71. data/vendor/sphinx_client/spec/fixtures/ranking_bm25.php +9 -0
  72. data/vendor/sphinx_client/spec/fixtures/ranking_none.php +9 -0
  73. data/vendor/sphinx_client/spec/fixtures/ranking_proximity.php +9 -0
  74. data/vendor/sphinx_client/spec/fixtures/ranking_proximity_bm25.php +9 -0
  75. data/vendor/sphinx_client/spec/fixtures/ranking_wordcount.php +9 -0
  76. data/vendor/sphinx_client/spec/fixtures/retries.php +9 -0
  77. data/vendor/sphinx_client/spec/fixtures/retries_delay.php +9 -0
  78. data/vendor/sphinx_client/spec/fixtures/select.php +9 -0
  79. data/vendor/sphinx_client/spec/fixtures/set_override.php +11 -0
  80. data/vendor/sphinx_client/spec/fixtures/sort_attr_asc.php +9 -0
  81. data/vendor/sphinx_client/spec/fixtures/sort_attr_desc.php +9 -0
  82. data/vendor/sphinx_client/spec/fixtures/sort_expr.php +9 -0
  83. data/vendor/sphinx_client/spec/fixtures/sort_extended.php +9 -0
  84. data/vendor/sphinx_client/spec/fixtures/sort_relevance.php +9 -0
  85. data/vendor/sphinx_client/spec/fixtures/sort_time_segments.php +9 -0
  86. data/vendor/sphinx_client/spec/fixtures/sphinxapi.php +1269 -0
  87. data/vendor/sphinx_client/spec/fixtures/update_attributes.php +8 -0
  88. data/vendor/sphinx_client/spec/fixtures/update_attributes_mva.php +8 -0
  89. data/vendor/sphinx_client/spec/fixtures/weights.php +9 -0
  90. data/vendor/sphinx_client/spec/sphinx/sphinx-id64.conf +67 -0
  91. data/vendor/sphinx_client/spec/sphinx/sphinx.conf +67 -0
  92. data/vendor/sphinx_client/spec/sphinx/sphinx_test.sql +86 -0
  93. data/vendor/sphinx_client/sphinx.yml.tpl +3 -0
  94. data/vendor/sphinx_client/tasks/sphinx.rake +75 -0
  95. metadata +154 -0
@@ -0,0 +1,52 @@
1
+ class ModelSet
2
+ class SQLBaseQuery < Query
3
+ # SQL methods common to SQLQuery and RawSQLQuery.
4
+ def ids
5
+ @ids ||= fetch_id_set(sql)
6
+ end
7
+
8
+ def size
9
+ @size ||= ids.size
10
+ end
11
+
12
+ private
13
+
14
+ def ids_clause(ids, field = id_field_with_prefix)
15
+ db.ids_clause(ids, field)
16
+ end
17
+
18
+ def fetch_id_set(sql)
19
+ db.select_values(sql).collect {|id| id.to_i}.to_ordered_set
20
+ end
21
+
22
+ def db
23
+ model_class.connection
24
+ end
25
+
26
+ def sanitize_condition(condition)
27
+ ActiveRecord::Base.send(:sanitize_sql, condition)
28
+ end
29
+
30
+ def limit_clause
31
+ return unless limit
32
+ limit_clause = "LIMIT #{limit}"
33
+ limit_clause << " OFFSET #{offset}" if offset > 0
34
+ limit_clause
35
+ end
36
+ end
37
+ end
38
+
39
+ class ActiveRecord::ConnectionAdapters::AbstractAdapter
40
+ def ids_clause(ids, field)
41
+ # Make sure all ids are integers to prevent SQL injection attacks.
42
+ ids = ids.collect {|id| id.to_i}
43
+
44
+ if ids.empty?
45
+ "FALSE"
46
+ elsif kind_of?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
47
+ "#{field} = ANY ('{#{ids.join(',')}}'::bigint[])"
48
+ else
49
+ "#{field} IN (#{ids.join(',')})"
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,75 @@
1
+ class ModelSet
2
+ class SQLQuery < SQLBaseQuery
3
+ include Conditioned
4
+
5
+ def anchor!(query)
6
+ if query.respond_to?(:sql)
7
+ sql = "#{id_field_with_prefix} IN (#{query.sql})"
8
+ else
9
+ sql = ids_clause(query.ids)
10
+ end
11
+ add_conditions!(sql)
12
+ end
13
+
14
+ def aggregate(query, opts = {})
15
+ sql = "SELECT #{query} #{from_clause}"
16
+ sql << " LIMIT #{opts[:limit]}" if opts[:limit]
17
+ sql << " GROUP BY #{opts[:group_by]}" if opts[:group_by]
18
+ result = db.select_rows(sql).first
19
+ result.size == 1 ? result.first : result
20
+ end
21
+
22
+ def add_joins!(*joins)
23
+ @joins ||= []
24
+
25
+ joins.each do |join|
26
+ @joins << sanitize_condition(join)
27
+ end
28
+ @joins.uniq!
29
+
30
+ clear_cache!
31
+ end
32
+
33
+ def in!(ids, field = id_field_with_prefix)
34
+ add_conditions!( ids_clause(ids, field) )
35
+ end
36
+
37
+ def order_by!(order, joins = nil)
38
+ @sort_order = order
39
+ @sort_joins = joins
40
+ clear_cache!
41
+ end
42
+
43
+ def sql
44
+ "#{select_clause} #{from_clause} #{order_clause} #{limit_clause}"
45
+ end
46
+
47
+ def count
48
+ @count ||= limit ? aggregate("COUNT(DISTINCT #{id_field_with_prefix})").to_i : size
49
+ end
50
+
51
+ private
52
+
53
+ def select_clause
54
+ "SELECT #{id_field_with_prefix}"
55
+ end
56
+
57
+ def from_clause
58
+ "FROM #{table_name} #{join_clause} WHERE #{conditions.to_s}"
59
+ end
60
+
61
+ def order_clause
62
+ return unless @sort_order
63
+ # Prevent SQL injection attacks.
64
+ "ORDER BY #{@sort_order.gsub(/[^\w_, \.\(\)'\"]/, '')}"
65
+ end
66
+
67
+ def join_clause
68
+ return unless @joins or @sort_joins
69
+ joins = []
70
+ joins << @joins if @joins
71
+ joins << @sort_joins if @sort_joins
72
+ joins.join(' ')
73
+ end
74
+ end
75
+ end
data/lib/multi_set.rb ADDED
@@ -0,0 +1,67 @@
1
+ class MultiSet
2
+ include Enumerable
3
+ deep_clonable
4
+
5
+ attr_accessor :sets
6
+
7
+ def initialize(*sets)
8
+ @sets = sets
9
+ end
10
+
11
+ def add!(other)
12
+ if other.kind_of?(MultiSet)
13
+ sets.concat(other.sets)
14
+ else
15
+ sets << other
16
+ end
17
+ self
18
+ end
19
+
20
+ alias << add!
21
+
22
+ def method_missing(method_name, *args)
23
+ method_name = method_name.to_s
24
+ if method_name =~ /\!$/
25
+ sets.each do |set|
26
+ set.send(method_name, *args)
27
+ end
28
+ self
29
+ else
30
+ sets.collect do |set|
31
+ set.send(method_name, *args)
32
+ end
33
+ end
34
+ end
35
+
36
+ def ids_by_class
37
+ ids_by_class = {}
38
+ sets.each do |set|
39
+ ids_by_class[set.model_class] ||= OrderedSet.new
40
+ ids_by_class[set.model_class].concat(set.ids)
41
+ end
42
+ ids_by_class.keys.each do |model_class|
43
+ ids_by_class[model_class] = ids_by_class[model_class].to_a
44
+ end
45
+ ids_by_class
46
+ end
47
+
48
+ def ids
49
+ ids = OrderedSet.new
50
+ sets.each do |set|
51
+ ids.concat(set.ids)
52
+ end
53
+ ids.to_a
54
+ end
55
+
56
+ def each
57
+ sets.each do |set|
58
+ set.each do |model|
59
+ yield model
60
+ end
61
+ end
62
+ end
63
+
64
+ clone_method :+, :add!
65
+ clone_method :-, :subtract!
66
+ clone_method :&, :intersect!
67
+ end
@@ -0,0 +1,283 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ModelSetTest < Test::Unit::TestCase
4
+ class CreateTables < ActiveRecord::Migration
5
+ def self.up
6
+ create_table :heroes do |t|
7
+ t.column :name, :string
8
+ t.column :universe, :string
9
+ end
10
+
11
+ create_table :superpowers do |t|
12
+ t.column :name, :string
13
+ end
14
+
15
+ create_table :mutations do |t|
16
+ t.column :name, :string
17
+ end
18
+
19
+ create_table :superpets do |t|
20
+ t.column :name, :string
21
+ t.column :species, :string
22
+ t.column :owner_id, :bigint
23
+ end
24
+
25
+ create_table :hero_superpowers do |t|
26
+ t.column :hero_id, :bigint
27
+ t.column :power_type, :string
28
+ t.column :power_id, :bigint
29
+ end
30
+
31
+ create_table :hero_birthdays do |t|
32
+ t.column :hero_id, :bigint
33
+ t.column :birthday, :date
34
+ end
35
+
36
+ create_table :robots do |t|
37
+ t.string :name
38
+ t.string :classification
39
+ end
40
+ end
41
+
42
+ def self.down
43
+ drop_table :heroes
44
+ drop_table :superpowers
45
+ drop_table :mutations
46
+ drop_table :superpets
47
+ drop_table :hero_superpowers
48
+ drop_table :hero_birthdays
49
+ drop_table :robots
50
+ end
51
+ end
52
+
53
+ class Superpower < ActiveRecord::Base
54
+ end
55
+
56
+ class Mutation < ActiveRecord::Base
57
+ end
58
+
59
+ class Superpet < ActiveRecord::Base
60
+ end
61
+
62
+ class HeroSuperpower < ActiveRecord::Base
63
+ end
64
+
65
+ class Hero < ActiveRecord::Base
66
+ set_table_name 'heroes'
67
+ has_set :superpowers, :through => :hero_superpowers, :other_key => :power_id
68
+ has_set :pets, :class_name => 'Superpet', :own_key => :owner_id do
69
+ def dogs!
70
+ add_conditions!("species = 'dog'")
71
+ end
72
+ end
73
+ end
74
+
75
+ class HeroSet < ModelSet
76
+ constructor :with_universe
77
+ clone_method :with_universe
78
+ def with_universe!(universe)
79
+ add_conditions!("universe = '#{universe}'")
80
+ end
81
+
82
+ clone_method :add_birthday
83
+ def add_birthday!
84
+ add_fields!( "hero_birthdays.birthday" => "LEFT OUTER JOIN hero_birthdays ON heroes.id = hero_birthdays.hero_id" )
85
+ end
86
+ end
87
+
88
+ context 'with a db connection' do
89
+ setup do
90
+ CreateTables.verbose = false
91
+ CreateTables.up
92
+ end
93
+
94
+ teardown do
95
+ CreateTables.down
96
+ end
97
+
98
+ should "construct a model set" do
99
+ captain = Hero.create(:name => 'Captain America', :universe => 'Marvel')
100
+ spidey = Hero.create(:name => 'Spider Man', :universe => 'Marvel')
101
+ batman = Hero.create(:name => 'Batman', :universe => 'D.C.' )
102
+ superman = Hero.create(:name => 'Superman', :universe => 'D.C.' )
103
+ ironman = Hero.create(:name => 'Iron Man', :universe => 'Marvel')
104
+
105
+ set = HeroSet.with_universe('Marvel')
106
+ assert_equal [captain.id, spidey.id, ironman.id], set.ids
107
+ end
108
+
109
+ should "have missing ids" do
110
+ missing_id = 5555
111
+ spidey = Hero.create(:name => 'Spider Man', :universe => 'Marvel')
112
+ set = HeroSet.new([spidey.id, missing_id])
113
+
114
+ # Iterate through the profiles so the missing ones will be detected.
115
+ set.each {}
116
+ assert_equal [missing_id], set.missing_ids
117
+ end
118
+
119
+ should "have missing ids with add_fields" do
120
+ missing_id = 5555
121
+ spidey = Hero.create(:name => 'Spider Man', :universe => 'Marvel')
122
+ set = HeroSet.new([spidey.id, missing_id]).add_birthday
123
+
124
+ # Iterate through the profiles so the missing ones will be detected.
125
+ set.each {}
126
+ assert_equal [missing_id], set.missing_ids
127
+ end
128
+
129
+ should "support has_set" do
130
+ hero = Hero.create(:name => 'Mr. Invisible')
131
+ mighty_mouse = Superpet.create(:name => 'Mighty Mouse', :owner_id => hero.id)
132
+ underdog = Superpet.create(:name => 'Underdog', :owner_id => hero.id)
133
+
134
+ set = hero.pets
135
+ assert_equal SuperpetSet, set.class
136
+ assert_equal [mighty_mouse.id, underdog.id], set.ids
137
+ end
138
+
139
+ should "support has_set with through" do
140
+ hero = Hero.create(:name => 'Mr. Invisible')
141
+ invisibility = Superpower.create(:name => 'Invisibility')
142
+ flying = Superpower.create(:name => 'Flying')
143
+ HeroSuperpower.create(:hero_id => hero.id, :power_id => invisibility.id)
144
+ HeroSuperpower.create(:hero_id => hero.id, :power_id => flying.id)
145
+
146
+ set = hero.superpowers
147
+ assert_equal SuperpowerSet, set.class
148
+ assert_equal [invisibility.id, flying.id], set.ids
149
+ end
150
+
151
+
152
+ should "allow set extensions" do
153
+ hero = Hero.create(:name => 'Mr. Invisible')
154
+ mighty_mouse = Superpet.create(:name => 'Mighty Mouse', :owner_id => hero.id, :species => 'mouse')
155
+ sammy = Superpet.create(:name => 'Sammy Davis Jr. Jr.', :owner_id => hero.id, :species => 'dog')
156
+ underdog = Superpet.create(:name => 'Underdog', :owner_id => hero.id, :species => 'dog')
157
+
158
+ set = hero.pets
159
+ assert_equal ['mouse', 'dog', 'dog'], set.collect {|pet| pet.species}
160
+
161
+ assert_equal [sammy.id, underdog.id], set.dogs!.ids
162
+ end
163
+
164
+ class Robot < ActiveRecord::Base
165
+ end
166
+
167
+ class RobotSet < ModelSet
168
+ end
169
+
170
+ setup do
171
+ @bender = Robot.create(:name => 'Bender', :classification => :smart_ass )
172
+ @r2d2 = Robot.create(:name => 'R2D2', :classification => :droid )
173
+ @c3po = Robot.create(:name => 'C3PO', :classification => :droid )
174
+ @rosie = Robot.create(:name => 'Rosie', :classification => :domestic )
175
+ @small_wonder = Robot.create(:name => 'Vicki', :classification => :child )
176
+ @t1000 = Robot.create(:name => 'Terminator', :classification => :assasin )
177
+ @johnny5 = Robot.create(:name => 'Johnny 5', :classification => :miltary )
178
+
179
+ @bot_set = RobotSet.new([@bender,@r2d2,@c3po,@rosie,@small_wonder,@t1000,@johnny5])
180
+
181
+ @data = Robot.create(:name => 'Data', :classification => :positronic)
182
+ @number8 = Robot.create(:name => 'Boomer', :classification => :cylon )
183
+ end
184
+
185
+ should "be empty" do
186
+ set = RobotSet.empty
187
+ assert_equal 0, set.size
188
+ assert set.empty?
189
+
190
+ set = RobotSet.new(@bender)
191
+ assert !set.empty?
192
+ end
193
+
194
+ should "create a set with single model" do
195
+ set = RobotSet.new(@bender)
196
+ assert_equal [@bender.id], set.ids
197
+ end
198
+
199
+ should "include models" do
200
+ set = RobotSet.new([@bender, @r2d2.id, @c3po.id])
201
+ assert set.include?(@bender)
202
+ assert set.include?(@r2d2.id)
203
+ assert set.include?(@c3po)
204
+ end
205
+
206
+ should "delete models from a set" do
207
+ set = RobotSet.new([@rosie, @small_wonder, @c3po])
208
+
209
+ set.delete(@c3po)
210
+ assert_equal [@rosie.id, @small_wonder.id], set.ids
211
+
212
+ set.delete(@rosie.id)
213
+ assert_equal [@small_wonder.id], set.ids
214
+
215
+ set.delete(@small_wonder)
216
+ assert_equal [], set.ids
217
+ assert set.empty?
218
+ end
219
+
220
+ should "select models from a set" do
221
+ assert_equal [@r2d2, @c3po], @bot_set.select {|bot| bot.classification == :droid}.to_a
222
+ assert_equal 7, @bot_set.size
223
+
224
+ @bot_set.select! {|bot| bot.classification == :miltary}
225
+ assert_equal [@johnny5], @bot_set.to_a
226
+ end
227
+
228
+ should "sort a set" do
229
+ assert_equal [@bender,@c3po,@johnny5,@r2d2,@rosie,@t1000,@small_wonder], @bot_set.sort {|a,b| a.name <=> b.name}.to_a
230
+ assert_equal @johnny5, @bot_set.last
231
+
232
+ @bot_set.sort! {|a,b| b.name <=> a.name}
233
+ assert_equal [@bender,@c3po,@johnny5,@r2d2,@rosie,@t1000,@small_wonder].reverse, @bot_set.to_a
234
+ end
235
+
236
+ should "sort a set by name" do
237
+ assert_equal [@bender,@c3po,@johnny5,@r2d2,@rosie,@t1000,@small_wonder], @bot_set.sort_by {|bot| bot.name}.to_a
238
+ end
239
+
240
+ should "reject models from a set" do
241
+ @bot_set.reject! {|bot| bot.classification == :domestic}
242
+ assert !@bot_set.include?(@rosie)
243
+ end
244
+
245
+ should "do set arithmetic" do
246
+ droids = RobotSet.new([@c3po, @r2d2])
247
+ womanoids = RobotSet.new([@rosie, @small_wonder, @number8])
248
+ humanoids = RobotSet.new([@small_wonder, @t1000, @data, @number8])
249
+ metalics = RobotSet.new([@r2d2, @c3po, @johnny5])
250
+ cartoons = RobotSet.new([@bender, @rosie])
251
+
252
+ assert_equal ['C3PO', 'R2D2', 'Johnny 5'], (droids + metalics).collect {|bot| bot.name}
253
+ assert_equal ['Bender', 'Rosie', 'C3PO', 'R2D2', 'Johnny 5'], (cartoons + droids + metalics).collect {|bot| bot.name}
254
+ assert_equal 5, (cartoons + droids + metalics).size
255
+ assert_equal 5, (cartoons + droids + metalics).count
256
+
257
+ assert_equal [], (droids - metalics).collect {|bot| bot.name}
258
+ assert_equal ['Johnny 5'], (metalics - droids).collect {|bot| bot.name}
259
+ assert_equal ['Terminator', 'Data'], (humanoids - womanoids).collect {|bot| bot.name}
260
+ assert_equal ['Bender'], (cartoons - womanoids).collect {|bot| bot.name}
261
+ assert_equal 2, (humanoids - womanoids).size
262
+ assert_equal 2, (humanoids - womanoids).count
263
+
264
+ assert_equal ['C3PO', 'R2D2'], (droids & metalics).collect {|bot| bot.name}
265
+ assert_equal ['R2D2', 'C3PO'], (metalics & droids).collect {|bot| bot.name}
266
+ assert_equal ['Vicki', 'Boomer'], (humanoids & womanoids).collect {|bot| bot.name}
267
+ assert_equal ['Rosie'], (cartoons & womanoids).collect {|bot| bot.name}
268
+ assert_equal 2, (humanoids & womanoids).size
269
+ assert_equal 2, (humanoids & womanoids).count
270
+
271
+ set = (droids + @johnny5)
272
+ assert_equal ['C3PO', 'R2D2', 'Johnny 5'], set.collect {|bot| bot.name}
273
+ set -= @r2d2
274
+ assert_equal ['C3PO', 'Johnny 5'], set.collect {|bot| bot.name}
275
+ end
276
+
277
+ should "clone a set" do
278
+ set = RobotSet.new([1])
279
+ new_set = set.clone
280
+ assert new_set.object_id != set.object_id
281
+ end
282
+ end
283
+ end