model_set 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,29 +4,33 @@ class ModelSet
4
4
  def ids
5
5
  @ids ||= fetch_id_set(sql)
6
6
  end
7
-
7
+
8
8
  def size
9
9
  @size ||= ids.size
10
10
  end
11
11
 
12
12
  private
13
-
13
+
14
14
  def ids_clause(ids, field = id_field_with_prefix)
15
- db.ids_clause(ids, field)
15
+ db.ids_clause(ids, field, set_class.id_type)
16
16
  end
17
17
 
18
18
  def fetch_id_set(sql)
19
- db.select_values(sql).collect {|id| id.to_i}.to_ordered_set
19
+ db.select_values(sql).collect { |id| set_class.id_type == :integer ? id.to_i : id }.to_ordered_set
20
20
  end
21
21
 
22
22
  def db
23
23
  model_class.connection
24
24
  end
25
-
25
+
26
26
  def sanitize_condition(condition)
27
27
  model_class.send(:sanitize_sql, condition)
28
28
  end
29
-
29
+
30
+ def transform_condition(condition)
31
+ [sanitize_condition(condition)]
32
+ end
33
+
30
34
  def limit_clause
31
35
  return unless limit
32
36
  limit_clause = "LIMIT #{limit}"
@@ -37,15 +41,23 @@ class ModelSet
37
41
  end
38
42
 
39
43
  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
44
+ def ids_clause(ids, field, id_type)
45
+ return "FALSE" if ids.empty?
46
+
47
+ if id_type == :integer
48
+ # Make sure all ids are integers to prevent SQL injection attacks.
49
+ ids = ids.collect {|id| id.to_i}
50
+
51
+ if kind_of?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
52
+ "#{field} = ANY ('{#{ids.join(',')}}'::bigint[])"
53
+ else
54
+ "#{field} IN (#{ids.join(',')})"
55
+ end
56
+ elsif id_type == :string
57
+ ids = ids.collect do |id|
58
+ raise ArgumentError.new('Invalid id: #{id}') if id =~ /'/
59
+ "'#{id}'"
60
+ end
49
61
  "#{field} IN (#{ids.join(',')})"
50
62
  end
51
63
  end
@@ -22,7 +22,7 @@ class ModelSet
22
22
  base_conditions = conditions
23
23
  @ids = [].to_ordered_set
24
24
  @reorder.each_slice(@limit_fetch) do |ids|
25
- self.conditions = Conditions.new(:and, ids_clause(ids), *base_conditions)
25
+ self.conditions = to_conditions(:and, ids_clause(ids), *base_conditions)
26
26
  @ids.concat fetch_id_set(sql)
27
27
  if limit and @ids.size >= limit
28
28
  @ids.reorder!(@reorder).limit!(limit)
@@ -54,12 +54,7 @@ class ModelSet
54
54
 
55
55
  def add_joins!(*joins)
56
56
  @joins ||= []
57
-
58
- joins.each do |join|
59
- @joins << sanitize_condition(join)
60
- end
61
- @joins.uniq!
62
-
57
+ @joins.concat(joins)
63
58
  clear_cache!
64
59
  end
65
60
 
@@ -70,7 +65,7 @@ class ModelSet
70
65
  def order_by!(*args)
71
66
  opts = args.last.kind_of?(Hash) ? args.pop : {}
72
67
 
73
- @sort_join = sanitize_condition(opts[:join])
68
+ @sort_join = opts[:join]
74
69
  @sort_order = args
75
70
  @reorder = nil
76
71
  clear_cache!
@@ -120,9 +115,12 @@ class ModelSet
120
115
  def join_clause
121
116
  return unless @joins or @sort_join
122
117
  joins = []
123
- joins << @joins if @joins
118
+ joins << @joins if @joins
124
119
  joins << @sort_join if @sort_join
125
- joins.join(' ')
120
+
121
+ joins.flatten.collect do |join|
122
+ sanitize_condition(join)
123
+ end.uniq.join(' ')
126
124
  end
127
125
  end
128
126
  end
data/lib/model_set.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'rubygems'
2
1
  require 'active_record'
3
2
  require 'deep_clonable'
4
3
  require 'ordered_set'
@@ -52,7 +51,7 @@ class ModelSet
52
51
 
53
52
  [:add!, :unshift!, :subtract!, :intersect!, :reorder!, :reverse_reorder!].each do |action|
54
53
  define_method(action) do |models|
55
- anchor!(:set)
54
+ reanchor!(:set)
56
55
  query.send(action, as_ids(models))
57
56
  self
58
57
  end
@@ -67,6 +66,8 @@ class ModelSet
67
66
  alias delete subtract!
68
67
  alias without! subtract!
69
68
  clone_method :without
69
+ clone_method :reorder
70
+ clone_method :reverse_reorder
70
71
 
71
72
  clone_method :shuffle
72
73
  def shuffle!
@@ -228,6 +229,7 @@ class ModelSet
228
229
  def reject_raw!(&block)
229
230
  anchor!(:raw)
230
231
  query.reject!(&block)
232
+ self
231
233
  end
232
234
 
233
235
  def select_raw(&block)
@@ -372,7 +374,7 @@ class ModelSet
372
374
  :sql
373
375
  end
374
376
 
375
- [:add_conditions!, :add_joins!, :in!, :invert!, :order_by!].each do |method_name|
377
+ [:add_conditions!, :add_joins!, :in!, :invert!, :order_by!, :to_conditions].each do |method_name|
376
378
  clone_method method_name
377
379
  define_method(method_name) do |*args|
378
380
  # Use the default query engine if none is specified.
@@ -383,6 +385,11 @@ class ModelSet
383
385
  end
384
386
  end
385
387
 
388
+ def to_conditions(*args)
389
+ anchor!( extract_opt(:query_type, args) || default_query_type )
390
+ query.to_conditions(*args)
391
+ end
392
+
386
393
  [:unsorted!, :limit!, :page!, :unlimited!, :reverse!].each do |method_name|
387
394
  clone_method method_name
388
395
  define_method(method_name) do |*args|
@@ -473,7 +480,19 @@ class ModelSet
473
480
  models.ids
474
481
  else
475
482
  models = [models] if not models.kind_of?(Enumerable)
476
- models.collect {|model| model.kind_of?(ActiveRecord::Base) ? model.id : model.to_i }
483
+ models.collect {|model| as_id(model)}
484
+ end
485
+ end
486
+
487
+ def self.as_id(model)
488
+ id = model_class.as_id(model) if model_class.respond_to?(:as_id)
489
+ return id if id
490
+
491
+ case model
492
+ when model_class then model.send(id_field)
493
+ when ActiveRecord::Base then model.id
494
+ when String then model.split('-').last.to_i
495
+ else model.to_i
477
496
  end
478
497
  end
479
498
 
@@ -550,14 +569,22 @@ class ModelSet
550
569
 
551
570
  def self.id_field(id_field = nil)
552
571
  if id_field.nil?
553
- @id_field ||= 'id'
572
+ @id_field ||= :id
554
573
  else
555
574
  @id_field = id_field
556
575
  end
557
576
  end
558
577
 
578
+ def self.id_type(id_type = nil)
579
+ if id_type.nil?
580
+ @id_type ||= :integer
581
+ else
582
+ @id_type = id_type.to_sym
583
+ end
584
+ end
585
+
559
586
  def self.id_field_with_prefix
560
- "#{self.table_name}.#{self.id_field}"
587
+ self.id_field.is_a?(String) ? self.id_field : "#{self.table_name}.#{self.id_field}"
561
588
  end
562
589
 
563
590
  # Define instance methods based on class methods.
@@ -609,7 +636,7 @@ private
609
636
  models = model_class.find(:all,
610
637
  :select => fields.compact.join(','),
611
638
  :joins => joins.compact.join(' '),
612
- :conditions => db.ids_clause(ids_to_fetch, id_field_with_prefix),
639
+ :conditions => db.ids_clause(ids_to_fetch, id_field_with_prefix, self.class.id_type),
613
640
  :include => @included_models
614
641
  )
615
642
  end
@@ -625,15 +652,10 @@ private
625
652
  end
626
653
 
627
654
  def as_id(model)
628
- case model
629
- when model_class
655
+ id = self.class.as_id(model)
656
+ if model.kind_of?(model_class)
630
657
  # Save the model object if it is of the same type as our models.
631
- id = model.send(id_field)
632
658
  models_by_id[id] ||= model
633
- when ActiveRecord::Base
634
- id = model.id
635
- else
636
- id = model.to_i
637
659
  end
638
660
  raise "id not found for model: #{model.inspect}" if id.nil?
639
661
  id
@@ -741,7 +763,9 @@ class ActiveRecord::Base
741
763
  if options[:clone] == false or args.include?(:no_clone)
742
764
  @model_set_cache[name]
743
765
  else
744
- @model_set_cache[name].clone
766
+ set = @model_set_cache[name].clone
767
+ set.extend(extension_module) if extension_module
768
+ set
745
769
  end
746
770
  end
747
771
 
data/model_set.gemspec CHANGED
@@ -1,141 +1,29 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
- # -*- encoding: utf-8 -*-
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
3
 
6
- Gem::Specification.new do |s|
7
- s.name = %q{model_set}
8
- s.version = "1.0.0"
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "model_set"
6
+ gem.version = IO.read('VERSION')
7
+ gem.authors = ["Justin Balthrop"]
8
+ gem.email = ["git@justinbalthrop.com"]
9
+ gem.description = %q{Easy manipulation of sets of ActiveRecord models}
10
+ gem.summary = gem.description
11
+ gem.homepage = "https://github.com/ninjudd/model_set"
9
12
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Justin Balthrop"]
12
- s.date = %q{2011-06-02}
13
- s.description = %q{Easy manipulation of sets of ActiveRecord models}
14
- s.email = %q{code@justinbalthrop.com}
15
- s.extra_rdoc_files = [
16
- "LICENSE",
17
- "README.rdoc"
18
- ]
19
- s.files = [
20
- "LICENSE",
21
- "README.rdoc",
22
- "Rakefile",
23
- "VERSION",
24
- "lib/model_set.rb",
25
- "lib/model_set/conditioned.rb",
26
- "lib/model_set/conditions.rb",
27
- "lib/model_set/query.rb",
28
- "lib/model_set/raw_query.rb",
29
- "lib/model_set/raw_sql_query.rb",
30
- "lib/model_set/set_query.rb",
31
- "lib/model_set/solr_query.rb",
32
- "lib/model_set/sphinx_query.rb",
33
- "lib/model_set/sql_base_query.rb",
34
- "lib/model_set/sql_query.rb",
35
- "lib/multi_set.rb",
36
- "model_set.gemspec",
37
- "test/model_set_test.rb",
38
- "test/multi_set_test.rb",
39
- "test/test_helper.rb",
40
- "vendor/sphinx_client/README.rdoc",
41
- "vendor/sphinx_client/Rakefile",
42
- "vendor/sphinx_client/init.rb",
43
- "vendor/sphinx_client/install.rb",
44
- "vendor/sphinx_client/lib/sphinx.rb",
45
- "vendor/sphinx_client/lib/sphinx/client.rb",
46
- "vendor/sphinx_client/lib/sphinx/request.rb",
47
- "vendor/sphinx_client/lib/sphinx/response.rb",
48
- "vendor/sphinx_client/spec/client_response_spec.rb",
49
- "vendor/sphinx_client/spec/client_spec.rb",
50
- "vendor/sphinx_client/spec/fixtures/default_search.php",
51
- "vendor/sphinx_client/spec/fixtures/default_search_index.php",
52
- "vendor/sphinx_client/spec/fixtures/excerpt_custom.php",
53
- "vendor/sphinx_client/spec/fixtures/excerpt_default.php",
54
- "vendor/sphinx_client/spec/fixtures/excerpt_flags.php",
55
- "vendor/sphinx_client/spec/fixtures/field_weights.php",
56
- "vendor/sphinx_client/spec/fixtures/filter.php",
57
- "vendor/sphinx_client/spec/fixtures/filter_exclude.php",
58
- "vendor/sphinx_client/spec/fixtures/filter_float_range.php",
59
- "vendor/sphinx_client/spec/fixtures/filter_float_range_exclude.php",
60
- "vendor/sphinx_client/spec/fixtures/filter_range.php",
61
- "vendor/sphinx_client/spec/fixtures/filter_range_exclude.php",
62
- "vendor/sphinx_client/spec/fixtures/filter_range_int64.php",
63
- "vendor/sphinx_client/spec/fixtures/filter_ranges.php",
64
- "vendor/sphinx_client/spec/fixtures/filters.php",
65
- "vendor/sphinx_client/spec/fixtures/filters_different.php",
66
- "vendor/sphinx_client/spec/fixtures/geo_anchor.php",
67
- "vendor/sphinx_client/spec/fixtures/group_by_attr.php",
68
- "vendor/sphinx_client/spec/fixtures/group_by_attrpair.php",
69
- "vendor/sphinx_client/spec/fixtures/group_by_day.php",
70
- "vendor/sphinx_client/spec/fixtures/group_by_day_sort.php",
71
- "vendor/sphinx_client/spec/fixtures/group_by_month.php",
72
- "vendor/sphinx_client/spec/fixtures/group_by_week.php",
73
- "vendor/sphinx_client/spec/fixtures/group_by_year.php",
74
- "vendor/sphinx_client/spec/fixtures/group_distinct.php",
75
- "vendor/sphinx_client/spec/fixtures/id_range.php",
76
- "vendor/sphinx_client/spec/fixtures/id_range64.php",
77
- "vendor/sphinx_client/spec/fixtures/index_weights.php",
78
- "vendor/sphinx_client/spec/fixtures/keywords.php",
79
- "vendor/sphinx_client/spec/fixtures/limits.php",
80
- "vendor/sphinx_client/spec/fixtures/limits_cutoff.php",
81
- "vendor/sphinx_client/spec/fixtures/limits_max.php",
82
- "vendor/sphinx_client/spec/fixtures/limits_max_cutoff.php",
83
- "vendor/sphinx_client/spec/fixtures/match_all.php",
84
- "vendor/sphinx_client/spec/fixtures/match_any.php",
85
- "vendor/sphinx_client/spec/fixtures/match_boolean.php",
86
- "vendor/sphinx_client/spec/fixtures/match_extended.php",
87
- "vendor/sphinx_client/spec/fixtures/match_extended2.php",
88
- "vendor/sphinx_client/spec/fixtures/match_fullscan.php",
89
- "vendor/sphinx_client/spec/fixtures/match_phrase.php",
90
- "vendor/sphinx_client/spec/fixtures/max_query_time.php",
91
- "vendor/sphinx_client/spec/fixtures/miltiple_queries.php",
92
- "vendor/sphinx_client/spec/fixtures/ranking_bm25.php",
93
- "vendor/sphinx_client/spec/fixtures/ranking_none.php",
94
- "vendor/sphinx_client/spec/fixtures/ranking_proximity.php",
95
- "vendor/sphinx_client/spec/fixtures/ranking_proximity_bm25.php",
96
- "vendor/sphinx_client/spec/fixtures/ranking_wordcount.php",
97
- "vendor/sphinx_client/spec/fixtures/retries.php",
98
- "vendor/sphinx_client/spec/fixtures/retries_delay.php",
99
- "vendor/sphinx_client/spec/fixtures/select.php",
100
- "vendor/sphinx_client/spec/fixtures/set_override.php",
101
- "vendor/sphinx_client/spec/fixtures/sort_attr_asc.php",
102
- "vendor/sphinx_client/spec/fixtures/sort_attr_desc.php",
103
- "vendor/sphinx_client/spec/fixtures/sort_expr.php",
104
- "vendor/sphinx_client/spec/fixtures/sort_extended.php",
105
- "vendor/sphinx_client/spec/fixtures/sort_relevance.php",
106
- "vendor/sphinx_client/spec/fixtures/sort_time_segments.php",
107
- "vendor/sphinx_client/spec/fixtures/sphinxapi.php",
108
- "vendor/sphinx_client/spec/fixtures/update_attributes.php",
109
- "vendor/sphinx_client/spec/fixtures/update_attributes_mva.php",
110
- "vendor/sphinx_client/spec/fixtures/weights.php",
111
- "vendor/sphinx_client/spec/sphinx/sphinx-id64.conf",
112
- "vendor/sphinx_client/spec/sphinx/sphinx.conf",
113
- "vendor/sphinx_client/spec/sphinx/sphinx_test.sql",
114
- "vendor/sphinx_client/sphinx.yml.tpl",
115
- "vendor/sphinx_client/tasks/sphinx.rake"
116
- ]
117
- s.homepage = %q{http://github.com/ninjudd/model_set}
118
- s.require_paths = ["lib"]
119
- s.rubygems_version = %q{1.3.7}
120
- s.summary = %q{Easy manipulation of sets of ActiveRecord models}
13
+ gem.add_development_dependency 'shoulda', '3.0.1'
14
+ gem.add_development_dependency 'mocha'
15
+ gem.add_development_dependency 'rsolr'
16
+ gem.add_development_dependency 'json'
17
+ gem.add_development_dependency 'rake'
18
+ gem.add_development_dependency 'system_timer'
19
+ gem.add_development_dependency 'activerecord-postgresql-adapter'
121
20
 
122
- if s.respond_to? :specification_version then
123
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
124
- s.specification_version = 3
21
+ gem.add_dependency 'ordered_set', '>= 1.0.1'
22
+ gem.add_dependency 'deep_clonable', '>= 1.1.0'
23
+ gem.add_dependency 'activerecord', '~> 2.3.9'
125
24
 
126
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
127
- s.add_runtime_dependency(%q<ordered_set>, [">= 1.0.1"])
128
- s.add_runtime_dependency(%q<deep_clonable>, [">= 1.1.0"])
129
- s.add_runtime_dependency(%q<activerecord>, [">= 2.0.0"])
130
- else
131
- s.add_dependency(%q<ordered_set>, [">= 1.0.1"])
132
- s.add_dependency(%q<deep_clonable>, [">= 1.1.0"])
133
- s.add_dependency(%q<activerecord>, [">= 2.0.0"])
134
- end
135
- else
136
- s.add_dependency(%q<ordered_set>, [">= 1.0.1"])
137
- s.add_dependency(%q<deep_clonable>, [">= 1.1.0"])
138
- s.add_dependency(%q<activerecord>, [">= 2.0.0"])
139
- end
25
+ gem.files = `git ls-files`.split($/)
26
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
27
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
28
+ gem.require_paths = ["lib"]
140
29
  end
141
-
@@ -120,6 +120,23 @@ class ModelSetTest < Test::Unit::TestCase
120
120
  assert_equal [ironman.id, captain.id, spidey.id], set.ids
121
121
  end
122
122
 
123
+ should "support conditions with or in them" do
124
+ captain = Hero.create(:name => 'Captain America', :universe => 'Marvel')
125
+ spidey = Hero.create(:name => 'Spider Man', :universe => 'Marvel')
126
+ ironman = Hero.create(:name => 'Iron Man', :universe => 'Marvel')
127
+ ryu = Hero.create(:name => 'Ryu', :universe => 'Capcom')
128
+ ken = Hero.create(:name => 'Ken', :universe => 'Capcom')
129
+ guile = Hero.create(:name => 'Guile', :universe => 'Capcom')
130
+ batman = Hero.create(:name => 'Batman', :universe => 'D.C.' )
131
+ superman = Hero.create(:name => 'Superman', :universe => 'D.C.' )
132
+
133
+ set = HeroSet.all
134
+ set.add_conditions!("universe = 'Marvel' OR universe = 'Capcom'")
135
+ set.add_conditions!("name LIKE '%n'")
136
+
137
+ assert_equal [spidey.id, ironman.id, ken.id], set.ids
138
+ end
139
+
123
140
  should "order and reverse set" do
124
141
  captain = Hero.create(:name => 'Captain America', :universe => 'Marvel')
125
142
  spidey = Hero.create(:name => 'Spider Man', :universe => 'Marvel')
@@ -199,6 +216,7 @@ class ModelSetTest < Test::Unit::TestCase
199
216
  underdog = Superpet.create(:name => 'Underdog', :owner_id => hero.id, :species => 'dog')
200
217
 
201
218
  set = hero.pets
219
+
202
220
  assert_equal ['mouse', 'dog', 'dog'], set.collect {|pet| pet.species}
203
221
 
204
222
  assert_equal [sammy.id, underdog.id], set.dogs!.ids
data/test/test_helper.rb CHANGED
@@ -1,13 +1,10 @@
1
1
  require 'rubygems'
2
2
  require 'test/unit'
3
3
  require 'shoulda'
4
- require 'mocha'
4
+ require 'mocha/setup'
5
5
  require 'pp'
6
6
 
7
7
  $LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
8
- ['deep_clonable', 'ordered_set'].each do |dir|
9
- $LOAD_PATH.unshift File.dirname(__FILE__) + "/../../#{dir}/lib"
10
- end
11
8
  require 'model_set'
12
9
 
13
10
  class Test::Unit::TestCase
@@ -16,8 +13,6 @@ end
16
13
  ActiveRecord::Base.establish_connection(
17
14
  :adapter => "postgresql",
18
15
  :host => "localhost",
19
- :username => "postgres",
20
- :password => "",
21
16
  :database => "model_set_test"
22
17
  )
23
18
  ActiveRecord::Migration.verbose = false