liszt 0.0.7 → 0.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.
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