model_set 1.0.0 → 1.1.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.
@@ -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