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.
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/VERSION.yml +4 -0
- data/lib/model_set.rb +712 -0
- data/lib/model_set/conditioned.rb +33 -0
- data/lib/model_set/conditions.rb +103 -0
- data/lib/model_set/query.rb +128 -0
- data/lib/model_set/raw_query.rb +41 -0
- data/lib/model_set/raw_sql_query.rb +19 -0
- data/lib/model_set/set_query.rb +34 -0
- data/lib/model_set/solr_query.rb +70 -0
- data/lib/model_set/sphinx_query.rb +148 -0
- data/lib/model_set/sql_base_query.rb +52 -0
- data/lib/model_set/sql_query.rb +75 -0
- data/lib/multi_set.rb +67 -0
- data/test/model_set_test.rb +283 -0
- data/test/multi_set_test.rb +65 -0
- data/test/test_helper.rb +23 -0
- data/vendor/sphinx_client/README.rdoc +41 -0
- data/vendor/sphinx_client/Rakefile +21 -0
- data/vendor/sphinx_client/init.rb +1 -0
- data/vendor/sphinx_client/install.rb +5 -0
- data/vendor/sphinx_client/lib/sphinx.rb +6 -0
- data/vendor/sphinx_client/lib/sphinx/client.rb +1093 -0
- data/vendor/sphinx_client/lib/sphinx/request.rb +50 -0
- data/vendor/sphinx_client/lib/sphinx/response.rb +69 -0
- data/vendor/sphinx_client/spec/client_response_spec.rb +112 -0
- data/vendor/sphinx_client/spec/client_spec.rb +469 -0
- data/vendor/sphinx_client/spec/fixtures/default_search.php +8 -0
- data/vendor/sphinx_client/spec/fixtures/default_search_index.php +8 -0
- data/vendor/sphinx_client/spec/fixtures/excerpt_custom.php +11 -0
- data/vendor/sphinx_client/spec/fixtures/excerpt_default.php +8 -0
- data/vendor/sphinx_client/spec/fixtures/excerpt_flags.php +11 -0
- data/vendor/sphinx_client/spec/fixtures/field_weights.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/filter.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/filter_exclude.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/filter_float_range.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/filter_float_range_exclude.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/filter_range.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/filter_range_exclude.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/filter_range_int64.php +10 -0
- data/vendor/sphinx_client/spec/fixtures/filter_ranges.php +10 -0
- data/vendor/sphinx_client/spec/fixtures/filters.php +10 -0
- data/vendor/sphinx_client/spec/fixtures/filters_different.php +13 -0
- data/vendor/sphinx_client/spec/fixtures/geo_anchor.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_by_attr.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_by_attrpair.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_by_day.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_by_day_sort.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_by_month.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_by_week.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_by_year.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_distinct.php +10 -0
- data/vendor/sphinx_client/spec/fixtures/id_range.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/id_range64.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/index_weights.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/keywords.php +8 -0
- data/vendor/sphinx_client/spec/fixtures/limits.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/limits_cutoff.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/limits_max.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/limits_max_cutoff.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/match_all.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/match_any.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/match_boolean.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/match_extended.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/match_extended2.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/match_fullscan.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/match_phrase.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/max_query_time.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/miltiple_queries.php +12 -0
- data/vendor/sphinx_client/spec/fixtures/ranking_bm25.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/ranking_none.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/ranking_proximity.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/ranking_proximity_bm25.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/ranking_wordcount.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/retries.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/retries_delay.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/select.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/set_override.php +11 -0
- data/vendor/sphinx_client/spec/fixtures/sort_attr_asc.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/sort_attr_desc.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/sort_expr.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/sort_extended.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/sort_relevance.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/sort_time_segments.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/sphinxapi.php +1269 -0
- data/vendor/sphinx_client/spec/fixtures/update_attributes.php +8 -0
- data/vendor/sphinx_client/spec/fixtures/update_attributes_mva.php +8 -0
- data/vendor/sphinx_client/spec/fixtures/weights.php +9 -0
- data/vendor/sphinx_client/spec/sphinx/sphinx-id64.conf +67 -0
- data/vendor/sphinx_client/spec/sphinx/sphinx.conf +67 -0
- data/vendor/sphinx_client/spec/sphinx/sphinx_test.sql +86 -0
- data/vendor/sphinx_client/sphinx.yml.tpl +3 -0
- data/vendor/sphinx_client/tasks/sphinx.rake +75 -0
- 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
|