liszt 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/.gitignore +4 -6
  2. data/Gemfile +2 -3
  3. data/Rakefile +1 -32
  4. data/lib/liszt/redis_list.rb +9 -5
  5. data/lib/liszt/version.rb +1 -1
  6. data/lib/liszt.rb +74 -47
  7. data/liszt.gemspec +8 -4
  8. data/test/fixtures/groups.yml +14 -0
  9. data/test/{dummy_2.3/test/fixtures → fixtures}/people.yml +0 -0
  10. data/test/liszt_test.rb +174 -37
  11. data/test/redis_list_test.rb +36 -37
  12. data/test/test_helper.rb +91 -20
  13. metadata +86 -150
  14. data/Gemfile.lock +0 -104
  15. data/Gemfile.rails2 +0 -8
  16. data/Gemfile.rails2.lock +0 -45
  17. data/test/dummy_2.3/Rakefile +0 -10
  18. data/test/dummy_2.3/app/controllers/application_controller.rb +0 -10
  19. data/test/dummy_2.3/app/models/group.rb +0 -2
  20. data/test/dummy_2.3/app/models/person.rb +0 -3
  21. data/test/dummy_2.3/config/boot.rb +0 -114
  22. data/test/dummy_2.3/config/database.yml +0 -22
  23. data/test/dummy_2.3/config/environment.rb +0 -43
  24. data/test/dummy_2.3/config/environments/development.rb +0 -17
  25. data/test/dummy_2.3/config/environments/test.rb +0 -28
  26. data/test/dummy_2.3/config/initializers/backtrace_silencers.rb +0 -7
  27. data/test/dummy_2.3/config/initializers/bundler.rb +0 -4
  28. data/test/dummy_2.3/config/initializers/cookie_verification_secret.rb +0 -7
  29. data/test/dummy_2.3/config/initializers/inflections.rb +0 -10
  30. data/test/dummy_2.3/config/initializers/mime_types.rb +0 -5
  31. data/test/dummy_2.3/config/initializers/new_rails_defaults.rb +0 -21
  32. data/test/dummy_2.3/config/initializers/session_store.rb +0 -15
  33. data/test/dummy_2.3/config/locales/en.yml +0 -5
  34. data/test/dummy_2.3/config/routes.rb +0 -43
  35. data/test/dummy_2.3/db/migrate/20110922194432_create_people.rb +0 -15
  36. data/test/dummy_2.3/db/migrate/20110922194512_create_groups.rb +0 -12
  37. data/test/dummy_2.3/db/schema.rb +0 -27
  38. data/test/dummy_2.3/db/seeds.rb +0 -7
  39. data/test/dummy_2.3/script/about +0 -4
  40. data/test/dummy_2.3/script/console +0 -3
  41. data/test/dummy_2.3/script/dbconsole +0 -3
  42. data/test/dummy_2.3/script/destroy +0 -3
  43. data/test/dummy_2.3/script/generate +0 -3
  44. data/test/dummy_2.3/script/performance/benchmarker +0 -3
  45. data/test/dummy_2.3/script/performance/profiler +0 -3
  46. data/test/dummy_2.3/script/plugin +0 -3
  47. data/test/dummy_2.3/script/runner +0 -3
  48. data/test/dummy_2.3/script/server +0 -3
  49. data/test/dummy_2.3/test/fixtures/groups.yml +0 -12
  50. data/test/dummy_3.1/Rakefile +0 -7
  51. data/test/dummy_3.1/app/controllers/application_controller.rb +0 -3
  52. data/test/dummy_3.1/app/models/.gitkeep +0 -0
  53. data/test/dummy_3.1/app/models/group.rb +0 -2
  54. data/test/dummy_3.1/app/models/person.rb +0 -3
  55. data/test/dummy_3.1/config/application.rb +0 -45
  56. data/test/dummy_3.1/config/boot.rb +0 -10
  57. data/test/dummy_3.1/config/database.yml +0 -25
  58. data/test/dummy_3.1/config/environment.rb +0 -5
  59. data/test/dummy_3.1/config/environments/development.rb +0 -27
  60. data/test/dummy_3.1/config/environments/test.rb +0 -39
  61. data/test/dummy_3.1/config/initializers/backtrace_silencers.rb +0 -7
  62. data/test/dummy_3.1/config/initializers/inflections.rb +0 -10
  63. data/test/dummy_3.1/config/initializers/mime_types.rb +0 -5
  64. data/test/dummy_3.1/config/initializers/secret_token.rb +0 -7
  65. data/test/dummy_3.1/config/initializers/session_store.rb +0 -8
  66. data/test/dummy_3.1/config/initializers/wrap_parameters.rb +0 -12
  67. data/test/dummy_3.1/config/locales/en.yml +0 -5
  68. data/test/dummy_3.1/config/routes.rb +0 -58
  69. data/test/dummy_3.1/config.ru +0 -4
  70. data/test/dummy_3.1/db/migrate/20110922194058_create_people.rb +0 -11
  71. data/test/dummy_3.1/db/migrate/20110922194126_create_groups.rb +0 -8
  72. data/test/dummy_3.1/db/schema.rb +0 -29
  73. data/test/dummy_3.1/lib/assets/.gitkeep +0 -0
  74. data/test/dummy_3.1/log/.gitkeep +0 -0
  75. data/test/dummy_3.1/script/rails +0 -6
  76. data/test/dummy_3.1/test/fixtures/groups.yml +0 -12
  77. data/test/dummy_3.1/test/fixtures/people.yml +0 -59
data/.gitignore CHANGED
@@ -2,13 +2,11 @@
2
2
  log/*.log
3
3
  pkg/
4
4
  doc/
5
- test/dummy_2.3/db/*.sqlite3
6
- test/dummy_2.3/log/*.log
7
- test/dummy_2.3/tmp/
8
- test/dummy_3.1/db/*.sqlite3
9
- test/dummy_3.1/log/*.log
10
- test/dummy_3.1/tmp/
5
+ test/app/log/*.log
6
+ test/app/tmp/
11
7
  .yardoc/
12
8
  .rvmrc
13
9
  /tags
14
10
  /dump.rdb
11
+ coverage/
12
+ Gemfile.lock
data/Gemfile CHANGED
@@ -1,8 +1,7 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem 'rails', '3.1.0'
4
- gem 'sqlite3'
5
- gem 'shoulda-context'
3
+ gem 'activerecord', '~> 3.1.0'
4
+ gem 'simplecov'
6
5
 
7
6
  # Specify your gem's dependencies in has_rank.gemspec
8
7
  gemspec
data/Rakefile CHANGED
@@ -2,12 +2,6 @@
2
2
 
3
3
  require 'bundler/gem_tasks'
4
4
 
5
- desc "Install gems for Rails 2.3.14 and Rails 3.1.0"
6
- task :install_bundle do
7
- system "bundle install --gemfile Gemfile"
8
- system "bundle install --gemfile Gemfile.rails2"
9
- end
10
-
11
5
  begin
12
6
  require 'yard'
13
7
  YARD::Rake::YardocTask.new
@@ -23,29 +17,4 @@ Rake::TestTask.new(:test) do |t|
23
17
  t.verbose = false
24
18
  end
25
19
 
26
- def execute(cmd)
27
- Dir.chdir "test/dummy_2.3"
28
- system "env RAILS_ENV=test BUNDLE_GEMFILE=../../Gemfile.rails2 bundle exec #{cmd}"
29
- Dir.chdir "../dummy_3.1"
30
- system "env RAILS_ENV=test BUNDLE_GEMFILE=../../Gemfile bundle exec #{cmd}"
31
- Dir.chdir "../.."
32
- end
33
-
34
- desc "Create and migrate the test databases for Rails 2.3.14 and Rails 3.1.0"
35
- task :set_up_test_dbs do
36
- execute "rake db:create"
37
- execute "rake db:migrate"
38
- end
39
-
40
- desc "Open console in Rails 2.3.14 app"
41
- task :console_rails_2 do
42
- Dir.chdir("test/dummy_2.3")
43
- system("env RAILS_ENV=test BUNDLE_GEMFILE=../../Gemfile.rails2 bundle exec script/console")
44
- end
45
-
46
- desc "Run tests on Rails 2.3.14"
47
- task :test_rails_2 do
48
- system("env BUNDLE_GEMFILE=Gemfile.rails2 bundle exec rake test")
49
- end
50
-
51
- task :default => [:test, :test_rails_2]
20
+ task :default => :test
@@ -79,9 +79,11 @@ module Liszt
79
79
  # Push the given id onto the bottom of the list.
80
80
  # @param [Fixnum] id
81
81
  def push!(id)
82
- redis.rpop(@key)
83
- redis.rpush(@key, id)
84
- redis.rpush(@key, '*')
82
+ redis.multi do
83
+ redis.rpop(@key)
84
+ redis.rpush(@key, id)
85
+ redis.rpush(@key, '*')
86
+ end
85
87
  end
86
88
 
87
89
  # Remove the given id from the list.
@@ -92,8 +94,10 @@ module Liszt
92
94
 
93
95
  # Clear all items from the list.
94
96
  def clear
95
- redis.del(@key)
96
- redis.rpush(@key, '*')
97
+ redis.multi do
98
+ redis.del(@key)
99
+ redis.rpush(@key, '*')
100
+ end
97
101
  end
98
102
 
99
103
  # Return the number of ids in the list, or nil if it's uninitialized.
data/lib/liszt/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Liszt
2
- VERSION = "0.0.7"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/liszt.rb CHANGED
@@ -2,26 +2,38 @@ require "liszt/version"
2
2
  require "liszt/instantizeable"
3
3
  require "liszt/redis_list"
4
4
 
5
+ require "active_record"
6
+
5
7
  module Liszt
6
- mattr_accessor :redis
8
+ class << self
9
+ attr_accessor :redis
10
+
11
+ def merge_id_lists(canonical, modified, append = false)
12
+ if append
13
+ (modified + (canonical - modified)) & canonical
14
+ else
15
+ ((canonical - modified) + modified) & canonical
16
+ end
17
+ end
18
+ end
7
19
 
8
20
  # Set up a scoped ordering for this model.
9
21
  #
10
- # Liszt currently only supports one type of ranking per model. It also doesn't
11
- # currently support re-sorting lists when a scope changes. The assumption is
12
- # that attributes used as scopes won't change after creation.
22
+ # Liszt currently only supports one type of ranking per model. It also
23
+ # doesn't currently support re-sorting lists when a scope changes. The
24
+ # assumption is that attributes used as scopes won't change after creation.
13
25
  #
14
- # The other major limitation at the moment is that scopes can't be <tt>nil</tt>.
15
- # If a record has nil for a scope value, its associated list will never have
16
- # any items in it.
26
+ # The other major limitation at the moment is that scopes can't be
27
+ # <tt>nil</tt>. If a record has nil for a scope value, its associated list
28
+ # will never have any items in it.
17
29
  #
18
30
  # @param [Hash] options
19
31
  # @option options [Symbol, Array] :scope The attribute or attributes to use
20
32
  # as list constraints.
21
33
  # @option options [Hash] :conditions Any extra constraints to impose.
22
- # @option options [Proc] :sort_by A lambda to pass into initialize_list! the first
23
- # time an item is added to an uninitialized list. It has the same semantics as
24
- # <tt>Enumerable#sort_by</tt>.
34
+ # @option options [Proc] :sort_by A lambda to pass into initialize_list! the
35
+ # first time an item is added to an uninitialized list. It has the same
36
+ # semantics as <tt>Enumerable#sort_by</tt>.
25
37
  def acts_as_liszt(options = {})
26
38
  extend Instantizeable
27
39
  extend ClassMethods
@@ -49,21 +61,20 @@ module Liszt
49
61
  end
50
62
 
51
63
  def initialize_list!(obj={}, &block)
52
- objects = find(:all, :conditions => liszt_query(obj))
64
+ objects = liszt_relation(obj).to_a
53
65
 
54
66
  # If the caller provided a block, or if they passed in a default
55
67
  # with the :sort_by option, sort the objects by that block's
56
68
  # output before populating the list with their ids. If not, put
57
69
  # the objects in descending order by id.
58
- ids = if block_given?
59
- objects.sort_by(&block).map(&:id)
60
- else
61
- if @liszt_sort_by
62
- objects.sort_by(&@liszt_sort_by).map(&:id)
63
- else
64
- objects.map(&:id).sort.reverse
65
- end
66
- end
70
+ ids = case
71
+ when block_given?
72
+ objects.sort_by(&block).map(&:id)
73
+ when @liszt_sort_by
74
+ objects.sort_by(&@liszt_sort_by).map(&:id)
75
+ else
76
+ objects.map(&:id).sort.reverse
77
+ end
67
78
 
68
79
  ordered_list(obj).clear_and_populate!(ids)
69
80
  ids
@@ -87,27 +98,45 @@ module Liszt
87
98
 
88
99
  def ordered_list_items(obj={}, opts={})
89
100
  force_refresh = opts.delete(:force_refresh) || false
90
- was_initialized = ordered_list_initialized?(obj)
91
- ids = ordered_list_ids(obj)
92
-
93
- # If ordered_list_ids just did the initialization, we can trust that
94
- # the list of ids is accurate and ignore the force_refresh flag.
95
- if force_refresh and was_initialized
96
- objs = find(:all, {:conditions => liszt_query(obj)}.merge(opts))
97
- real_ids = objs.map(&:id)
98
- unlisted_ids = real_ids - ids
99
- if unlisted_ids.count > 0
100
- ids = ordered_list(obj).clear_and_populate!(unlisted_ids + ids)
101
- end
101
+ previously_initialized = ordered_list_initialized?(obj)
102
+
103
+ # If the list isn't initialized already, we can trust ordered_list_ids to
104
+ # do the initialization correctly and ignore the force_refresh flag.
105
+ if force_refresh && previously_initialized
106
+ refresh_ordered_list(obj)
102
107
  else
103
- objs = find(:all, {:conditions => ['id in (?)', ids]}.merge(opts))
108
+ list_ids = ordered_list_ids(obj)
109
+ records = where('id in (?)', list_ids).to_a
110
+ records.sort_by { |obj| list_ids.index(obj.id) }
111
+ end
112
+ end
113
+
114
+ # Synchronizes the ordered list with the database, returning the relevant
115
+ # records in order.
116
+ def refresh_ordered_list(obj={})
117
+ list_ids = ordered_list_ids(obj)
118
+ records = liszt_relation(obj).to_a
119
+ real_ids = records.map(&:id)
120
+ merged_ids = Liszt.merge_id_lists(
121
+ real_ids, list_ids, @liszt_append_new_items)
122
+
123
+ if merged_ids != list_ids
124
+ ordered_list(obj).clear_and_populate!(merged_ids)
104
125
  end
105
126
 
106
- objs.sort_by { |obj| ids.index(obj.id) }
127
+ records.sort_by { |obj| merged_ids.index(obj.id) }
107
128
  end
108
129
 
109
- def clear_list(obj={})
110
- ordered_list(obj).clear
130
+ # Update the given object's list with the given ids. Returns the final list
131
+ # of ids, which may be different from the given list if the given list was
132
+ # inconsistent with the database.
133
+ def update_ordered_list(obj, new_ids)
134
+ records = ordered_list_items(obj, force_refresh: true)
135
+ real_ids = records.map(&:id)
136
+ merged_ids = Liszt.merge_id_lists(
137
+ real_ids, new_ids, @liszt_append_new_items)
138
+
139
+ ordered_list(obj).clear_and_populate!(merged_ids)
111
140
  end
112
141
 
113
142
  def meets_list_conditions?(obj={})
@@ -115,6 +144,7 @@ module Liszt
115
144
  end
116
145
 
117
146
  private
147
+
118
148
  # Return the key for the Redis list that includes the given object.
119
149
  def liszt_key(obj={})
120
150
  key = "liszt:#{table_name}"
@@ -124,9 +154,9 @@ module Liszt
124
154
  key
125
155
  end
126
156
 
127
- # Return the query that retrieves objects eligible to be
128
- # in the list that includes the given object.
129
- def liszt_query(obj={})
157
+ # Return a relation/scope containing objects eligible to be in the list
158
+ # that includes the given object.
159
+ def liszt_relation(obj={})
130
160
  if @liszt_query.nil?
131
161
  query = ['1 = 1']
132
162
 
@@ -147,7 +177,7 @@ module Liszt
147
177
  @liszt_query = query
148
178
  end
149
179
 
150
- @liszt_query + @liszt_scope.map { |scope| obj[scope] }
180
+ where(@liszt_query + @liszt_scope.map { |scope| obj[scope] })
151
181
  end
152
182
  end
153
183
 
@@ -155,7 +185,7 @@ module Liszt
155
185
  def self.included(base)
156
186
  base.class_eval do
157
187
  after_create :add_to_list
158
- after_update :update_list
188
+ after_update :add_to_or_remove_from_list
159
189
  after_destroy :remove_from_list
160
190
  end
161
191
  end
@@ -170,16 +200,12 @@ module Liszt
170
200
  end
171
201
  end
172
202
  else
173
- if self.class.liszt_sort_by
174
- initialize_list! &self.class.liszt_sort_by
175
- else
176
- initialize_list!
177
- end
203
+ initialize_list!
178
204
  end
179
205
  true
180
206
  end
181
207
 
182
- def update_list
208
+ def add_to_or_remove_from_list
183
209
  if meets_list_conditions?
184
210
  add_to_list
185
211
  else
@@ -211,4 +237,5 @@ module Liszt
211
237
  end
212
238
  end
213
239
 
240
+ # @@ TODO: use an actual railtie
214
241
  ActiveRecord::Base.extend Liszt
data/liszt.gemspec CHANGED
@@ -16,9 +16,13 @@ Gem::Specification.new do |s|
16
16
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
17
  s.require_paths = %w{lib}
18
18
 
19
- s.add_runtime_dependency 'rails', '>= 2.3.2'
20
- s.add_runtime_dependency 'redis'
19
+ s.add_runtime_dependency "activerecord", ">= 3.0.0"
20
+ s.add_runtime_dependency "redis"
21
21
 
22
- s.add_development_dependency 'yard'
23
- s.add_development_dependency 'rdiscount'
22
+ s.add_development_dependency "minitest"
23
+ s.add_development_dependency "pry"
24
+ s.add_development_dependency "rake"
25
+ s.add_development_dependency "rdiscount"
26
+ s.add_development_dependency "sqlite3"
27
+ s.add_development_dependency "yard"
24
28
  end
@@ -0,0 +1,14 @@
1
+ one:
2
+ id: 1
3
+ is_foo: true
4
+ is_bar:
5
+
6
+ two:
7
+ id: 2
8
+ is_foo: true
9
+ is_bar:
10
+
11
+ three:
12
+ id: 3
13
+ is_foo: true
14
+ is_bar:
data/test/liszt_test.rb CHANGED
@@ -1,59 +1,56 @@
1
1
  require 'test_helper'
2
2
 
3
- class LisztTest < ActiveSupport::TestCase
4
- fixtures :groups
5
- fixtures :people
6
-
7
- setup do
3
+ describe Liszt do
4
+ before do
8
5
  Liszt.redis.flushall
9
6
  end
10
7
 
11
- context "before initialization" do
12
- should "not be initialized" do
8
+ describe "before initialization" do
9
+ it "isn't initialized" do
13
10
  assert !people(:nelson).ordered_list_initialized?
14
11
  end
15
12
 
16
- should "initialize successfully" do
17
- assert_nothing_raised { people(:nelson).initialize_list! }
13
+ it "initializes successfully" do
14
+ people(:nelson).initialize_list!
18
15
  assert people(:nelson).ordered_list_initialized?
19
16
  end
20
17
 
21
- should "auto-initialize successfully" do
22
- assert_nothing_raised { people(:nelson).ordered_list_ids }
18
+ it "auto-initializes successfully" do
19
+ people(:nelson).ordered_list_ids
23
20
  assert people(:nelson).ordered_list_initialized?
24
21
  end
25
22
  end
26
23
 
27
- context "after initialization" do
28
- setup do
24
+ describe "after initialization" do
25
+ before do
29
26
  people(:nelson).initialize_list!(&:id)
30
27
  @id = people(:nelson).id
31
28
  @current_list = Person.ordered_list_ids(people(:nelson))
32
29
  end
33
30
 
34
- context "basic functionality" do
35
- should "move to top" do
31
+ describe "basic functionality" do
32
+ it "moves to top" do
36
33
  people(:nelson).move_to_top!
37
34
  @current_list.delete(@id)
38
35
  @current_list.unshift(@id)
39
36
  assert_equal people(:nelson).ordered_list_ids, @current_list
40
37
  end
41
38
 
42
- should "move up" do
39
+ it "moves up" do
43
40
  old_index = people(:nelson).ordered_list_ids.index(@id)
44
41
  people(:nelson).move_up!
45
42
  @current_list.swap(old_index, old_index - 1)
46
43
  assert_equal people(:nelson).ordered_list_ids, @current_list
47
44
  end
48
45
 
49
- should "move down" do
46
+ it "moves down" do
50
47
  old_index = people(:nelson).ordered_list_ids.index(@id)
51
48
  people(:nelson).move_down!
52
49
  @current_list.swap(old_index, old_index + 1)
53
50
  assert_equal people(:nelson).ordered_list_ids, @current_list
54
51
  end
55
52
 
56
- should "move to bottom" do
53
+ it "moves to bottom" do
57
54
  people(:nelson).move_to_bottom!
58
55
  @current_list.delete(@id)
59
56
  @current_list.push(@id)
@@ -61,73 +58,213 @@ class LisztTest < ActiveSupport::TestCase
61
58
  end
62
59
  end
63
60
 
64
- context "ActiveRecord hooks" do
65
- setup do
61
+ describe "ActiveRecord hooks" do
62
+ before do
66
63
  Person.initialize_list!(:group_id => 1, :is_male => true)
67
64
  @person = Person.new(:name => "John Smith", :group_id => 1, :is_male => true)
68
65
  end
69
66
 
70
- should "not be in list before saving" do
67
+ it "isn't in list before saving" do
71
68
  assert !@person.ordered_list_items.include?(@person)
72
69
  end
73
70
 
74
- should "be in list after saving" do
71
+ it "is in list after saving" do
75
72
  @person.save
76
73
  assert @person.ordered_list_items.include?(@person)
77
74
  end
78
75
 
79
- should "be removed from list after deletion" do
76
+ it "is removed from list after deletion" do
80
77
  @person.save
81
78
  @person.destroy
82
79
  assert !@person.ordered_list_items.include?(@person)
83
80
  end
84
81
  end
85
82
 
86
- context "options" do
87
- setup do
83
+ describe "options" do
84
+ before do
88
85
  Person.initialize_list!(:group_id => 1, :is_male => true)
89
86
  @person = Person.new(:name => "John Smith", :group_id => 1, :is_male => true)
90
87
  end
91
88
 
92
- should "not confirm the list when force_refresh is nil" do
89
+ it "doesn't confirm the list when force_refresh is nil" do
93
90
  @person.save
94
91
  @person.remove_from_list
95
92
  assert !@person.ordered_list_items.include?(@person)
96
93
  assert !@person.ordered_list_ids.include?(@person.id)
97
94
  end
98
95
 
99
- should "confirm the list when force_refresh is true" do
96
+ it "confirms the list when force_refresh is true" do
100
97
  @person.save
101
98
  @person.remove_from_list
102
99
  assert @person.ordered_list_items(:force_refresh => true).include?(@person)
103
100
  assert @person.ordered_list_ids.include?(@person.id)
104
101
  end
105
102
 
106
- should "pass any other options through to the ActiveRecord query" do
103
+ it "removes incorrect items from the list when force_refresh is true" do
107
104
  @person.save
108
- assert @person.ordered_list_items(:limit => 2).count == 2
105
+ @person.ordered_list.push(12314231)
106
+ assert @person.ordered_list_ids.include?(12314231)
107
+ @person.ordered_list_items
108
+ assert @person.ordered_list_ids.include?(12314231)
109
+ @person.ordered_list_items(:force_refresh => true)
110
+ refute @person.ordered_list_ids.include?(12314231)
109
111
  end
110
112
  end
111
113
  end
112
114
 
113
- context "auto-initialization" do
114
- setup do
115
- Group.acts_as_liszt :sort_by => lambda { |o| o.id }
116
- end
117
-
118
- should "sort a newly initialized list with the given proc" do
115
+ describe "auto-initialization" do
116
+ it "sorts a newly initialized list with the given proc" do
119
117
  assert !Group.ordered_list_initialized?
120
118
  Group.initialize_list!
121
119
  assert Group.ordered_list_initialized?
122
120
  assert_equal Group.ordered_list_ids, [1, 2, 3]
123
121
  end
124
122
 
125
- should "sort a newly auto-initialized list with the given proc" do
126
- g = Group.new
123
+ it "sorts a newly auto-initialized (by record creation) list" do
124
+ g = Group.new(is_foo: true, is_bar: nil)
127
125
  assert !Group.ordered_list_initialized?
128
126
  g.save
129
127
  assert Group.ordered_list_initialized?
130
128
  assert_equal Group.ordered_list_ids, [1, 2, 3, g.id]
131
129
  end
130
+
131
+ it "sorts a newly auto-initialized (by querying) list" do
132
+ assert !Group.ordered_list_initialized?
133
+ Group.ordered_list_items
134
+ assert Group.ordered_list_initialized?
135
+ assert_equal Group.ordered_list_ids, [1, 2, 3]
136
+ end
137
+
138
+ it "sorts by id descending if there is no proc" do
139
+ nelson = people(:nelson)
140
+ refute nelson.ordered_list_initialized?
141
+ Person.create!(group_id: nelson.group_id, is_male: nelson.is_male)
142
+ assert nelson.ordered_list_initialized?
143
+ nelson.ordered_list_ids.must_equal nelson.ordered_list_ids.sort.reverse
144
+ end
145
+ end
146
+
147
+ describe "with :conditions and :append_new_items" do
148
+ before do
149
+ Group.initialize_list!
150
+ assert Group.ordered_list_initialized?
151
+ assert_equal Group.ordered_list_ids, [1, 2, 3]
152
+ end
153
+
154
+ it "appends records that meet the conditions" do
155
+ Group.create!(is_foo: true, is_bar: true)
156
+ assert_equal Group.ordered_list_ids, [1, 2, 3]
157
+
158
+ Group.create!(is_foo: false, is_bar: nil)
159
+ assert_equal Group.ordered_list_ids, [1, 2, 3]
160
+
161
+ g = Group.create!(is_foo: true, is_bar: nil)
162
+ assert_equal Group.ordered_list_ids, [1, 2, 3, g.id]
163
+ end
164
+
165
+ it "inserts/removes records if an update changes the conditions" do
166
+ g = Group.create!(is_foo: true, is_bar: nil)
167
+ assert_equal Group.ordered_list_ids, [1, 2, 3, g.id]
168
+ g.update_attributes is_bar: true
169
+ assert_equal Group.ordered_list_ids, [1, 2, 3]
170
+ g.update_attributes is_bar: nil
171
+ assert_equal Group.ordered_list_ids, [1, 2, 3, g.id]
172
+ end
173
+ end
174
+
175
+ describe ".update_ordered_list" do
176
+ before do
177
+ @nelson = people(:nelson)
178
+ @nelson.initialize_list!
179
+ @id1, @id2, @id3, @id4 = @nelson.ordered_list_ids
180
+ end
181
+
182
+ it "reorders the elements based on the given list" do
183
+ retval = @nelson.update_ordered_list [@id4, @id3, @id2, @id1]
184
+ retval.must_equal [@id4, @id3, @id2, @id1]
185
+ @nelson.ordered_list_ids.must_equal [@id4, @id3, @id2, @id1]
186
+ end
187
+
188
+ describe "when the existing list is missing elements from the db" do
189
+ before do
190
+ @nelson.ordered_list.remove @id3
191
+ @nelson.ordered_list_ids.must_equal [@id1, @id2, @id4]
192
+ end
193
+
194
+ describe "and the user also didn't provide them" do
195
+ it "prepends those elements to the list" do
196
+ retval = @nelson.update_ordered_list [@id4, @id2, @id1]
197
+ retval.must_equal [@id3, @id4, @id2, @id1]
198
+ @nelson.ordered_list_ids.must_equal [@id3, @id4, @id2, @id1]
199
+ end
200
+ end
201
+
202
+ describe "and the user provided them as part of their ordering" do
203
+ it "adds those elements where the user provided them" do
204
+ @nelson.update_ordered_list [@id4, @id2, @id3, @id1]
205
+ @nelson.ordered_list_ids.must_equal [@id4, @id2, @id3, @id1]
206
+ end
207
+ end
208
+
209
+ describe "and the user provided one but not another" do
210
+ before do
211
+ @nelson.ordered_list.remove @id2
212
+ end
213
+
214
+ it "prepends only the one that wasn't provided" do
215
+ @nelson.update_ordered_list [@id4, @id2, @id1]
216
+ @nelson.ordered_list_ids.must_equal [@id3, @id4, @id2, @id1]
217
+ end
218
+ end
219
+ end
220
+
221
+ describe "when the existing list contains extra elements" do
222
+ before do
223
+ @nelson.ordered_list << 123
224
+ end
225
+
226
+ it "removes those elements from the list" do
227
+ @nelson.update_ordered_list [@id4, @id3, @id2, @id1]
228
+ @nelson.ordered_list_ids.must_equal [@id4, @id3, @id2, @id1]
229
+ end
230
+
231
+ it "ignores those elements if given by the user" do
232
+ @nelson.update_ordered_list [@id4, @id3, 123, @id2, @id1]
233
+ @nelson.ordered_list_ids.must_equal [@id4, @id3, @id2, @id1]
234
+ end
235
+ end
236
+
237
+ describe "when the list given by the user omits needed elements" do
238
+ it "prepends them to the list" do
239
+ @nelson.update_ordered_list [@id4, @id3, @id1]
240
+ @nelson.ordered_list_ids.must_equal [@id2, @id4, @id3, @id1]
241
+ end
242
+ end
243
+
244
+ describe "when the list given by the user contains nonexistent elements" do
245
+ it "ignores them and calls back" do
246
+ @nelson.update_ordered_list [@id4, @id3, 123, @id2, @id1]
247
+ @nelson.ordered_list_ids.must_equal [@id4, @id3, @id2, @id1]
248
+ end
249
+ end
250
+ end
251
+
252
+ describe ".update_ordered_list with :append_new_items" do
253
+ it "appends missing elements to the list" do
254
+ group = groups(:one)
255
+ group.initialize_list!
256
+ id1, id2, id3 = group.ordered_list_ids
257
+
258
+ group.ordered_list.remove id2
259
+ group.ordered_list_ids.must_equal [id1, id3]
260
+
261
+ group.update_ordered_list [id3, id1]
262
+
263
+ group.ordered_list_ids.must_equal [id3, id1, id2]
264
+
265
+ group.update_ordered_list [id1, id2]
266
+
267
+ group.ordered_list_ids.must_equal [id1, id2, id3]
268
+ end
132
269
  end
133
270
  end