sequel_orderable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1 @@
1
+ * 2007.12.02 Initial working revision.
data/COPYING ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2007 Sharon Rosner, Wayne E. Seguin, Aman Gupta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,187 @@
1
+ = Sequel Orderable Plugin
2
+
3
+ Allows for model instances to be part of an ordered list,
4
+ based on a 'position' field in the database.
5
+
6
+ == Basic Usage
7
+
8
+ Load the plugin into the model:
9
+
10
+ is :orderable
11
+
12
+ Given:
13
+
14
+ class Item < Sequel::Model(:items)
15
+ set_schema do
16
+ primary_key :id
17
+ varchar :name
18
+ int :position
19
+ end
20
+ is :orderable, :field => :position
21
+ end
22
+
23
+ item = Item[1]
24
+
25
+ The plugin provides access to the previous and next item in the list
26
+
27
+ item.next
28
+ item.prev
29
+
30
+ And methods to change the position of an item (and update affected items accordingly)
31
+
32
+ item.move_to(new_position)
33
+ item.move_to_top
34
+ item.move_to_bottom
35
+ item.move_up
36
+ item.move_down
37
+
38
+ == Scoping
39
+
40
+ You can scope the position field by another field.
41
+
42
+ For example, to allow each user to have their own a distinct orderable list:
43
+
44
+ class UserItem < Sequel::Model(:items)
45
+ set_schema do
46
+ primary_key :id
47
+ varchar :name
48
+ int :user_id
49
+ int :pos
50
+ end
51
+ is :orderable, :field => :pos, :scope => :user_id
52
+ end
53
+
54
+ All the defined methods will operate within the 'user_id' field's scope.
55
+
56
+ == Examples
57
+
58
+ # Input: irb
59
+ require "sequel"
60
+
61
+ DB = Sequel.sqlite
62
+ class Item < Sequel::Model(:items)
63
+ set_schema do
64
+ primary_key :id
65
+ varchar :name
66
+ int :position
67
+ end
68
+ is :orderable, :field => :position
69
+ end
70
+
71
+ Item.create_table!
72
+ Item.create :name => "alice", :position => 2
73
+ Item.create :name => "bob", :position => 1
74
+ Item.create :name => "charlie", :position => 4
75
+ Item.create :name => "darwin", :position => 3
76
+
77
+ Item.print
78
+
79
+ Item[:name => "alice"].move_down
80
+ Item.print
81
+ Item[:name => "darwin"].move_to_top
82
+ Item.print
83
+ Item[:name => "alice"].next
84
+ Item.print
85
+ Item[:name => "bob"].prev
86
+ Item.print
87
+ Item[:name => "darwin"].move_to(3)
88
+ Item.print
89
+ Item[:name => "bob"].move_to_bottom
90
+ Item.print
91
+
92
+
93
+ # Output
94
+ >> Item.print
95
+ +--+-------+--------+
96
+ |id|name |position|
97
+ +--+-------+--------+
98
+ | 2|bob | 1|
99
+ | 1|alice | 2|
100
+ | 4|darwin | 3|
101
+ | 3|charlie| 4|
102
+ +--+-------+--------+
103
+ => nil
104
+
105
+ >> Item[:name => "alice"].move_down
106
+ => {:position=>3}
107
+
108
+ >> Item.print
109
+ +--+-------+--------+
110
+ |id|name |position|
111
+ +--+-------+--------+
112
+ | 2|bob | 1|
113
+ | 4|darwin | 2|
114
+ | 1|alice | 3|
115
+ | 3|charlie| 4|
116
+ +--+-------+--------+
117
+ => nil
118
+
119
+ >> Item[:name => "darwin"].move_to_top
120
+ => {:position=>1}
121
+
122
+ >> Item.print
123
+ +--+-------+--------+
124
+ |id|name |position|
125
+ +--+-------+--------+
126
+ | 4|darwin | 1|
127
+ | 2|bob | 2|
128
+ | 1|alice | 3|
129
+ | 3|charlie| 4|
130
+ +--+-------+--------+
131
+ => nil
132
+
133
+ >> Item[:name => "alice"].next
134
+ => #<Item:0x119dbc8 @values={:position=>4, :name=>"charlie", :id=>3}, newfalse
135
+
136
+ >> Item.print
137
+ +--+-------+--------+
138
+ |id|name |position|
139
+ +--+-------+--------+
140
+ | 4|darwin | 1|
141
+ | 2|bob | 2|
142
+ | 1|alice | 3|
143
+ | 3|charlie| 4|
144
+ +--+-------+--------+
145
+ => nil
146
+
147
+ >> Item[:name => "bob"].prev
148
+ => #<Item:0x1184bb4 @values={:position=>1, :name=>"darwin", :id=>4}, newfalse
149
+
150
+ >> Item.print
151
+ +--+-------+--------+
152
+ |id|name |position|
153
+ +--+-------+--------+
154
+ | 4|darwin | 1|
155
+ | 2|bob | 2|
156
+ | 1|alice | 3|
157
+ | 3|charlie| 4|
158
+ +--+-------+--------+
159
+ => nil
160
+
161
+ >> Item[:name => "darwin"].move_to(3)
162
+ => {:position=>3}
163
+
164
+ >> Item.print
165
+ +--+-------+--------+
166
+ |id|name |position|
167
+ +--+-------+--------+
168
+ | 2|bob | 1|
169
+ | 1|alice | 2|
170
+ | 4|darwin | 3|
171
+ | 3|charlie| 4|
172
+ +--+-------+--------+
173
+ => nil
174
+
175
+ >> Item[:name => "bob"].move_to_bottom
176
+ => {:position=>4}
177
+
178
+ >> Item.print
179
+ +--+-------+--------+
180
+ |id|name |position|
181
+ +--+-------+--------+
182
+ | 1|alice | 1|
183
+ | 4|darwin | 2|
184
+ | 3|charlie| 3|
185
+ | 2|bob | 4|
186
+ +--+-------+--------+
187
+ => nil
data/Rakefile ADDED
@@ -0,0 +1,158 @@
1
+ ##############################################################################
2
+ # Constants
3
+ ##############################################################################
4
+
5
+ PluginName = "sequel_orderable"
6
+ Version = "0.0.1"
7
+ Title = "Orderable Sequel Plugin"
8
+ Summary = "Sequel Plugin"
9
+ Authors = "Wayne E. Seguin & Aman Gupta"
10
+ Emails = "wayneeseugin@gmail.com,sequel@tmm1.net"
11
+ Homepage = "http://sequel.rubyforge.org"
12
+
13
+ ##############################################################################
14
+ # Gem Management
15
+ ##############################################################################
16
+ require "rake"
17
+ require "rake/clean"
18
+ require "rake/gempackagetask"
19
+ require "rake/rdoctask"
20
+ require "fileutils"
21
+
22
+ include FileUtils
23
+
24
+ CLEAN.include ["**/.*.sw?", "pkg/*", ".config", "doc/*", "coverage/*"]
25
+
26
+ RDocOptions = [
27
+ "--quiet", "--title", Title,
28
+ "--opname", "index.html",
29
+ "--line-numbers",
30
+ "--main", "README",
31
+ "--inline-source"
32
+ ]
33
+
34
+ desc "Packages up the Sequel Plugin: #{PluginName}."
35
+ task :default => [:package]
36
+ task :package => [:clean]
37
+
38
+ task :doc => [:rdoc]
39
+
40
+ Rake::RDocTask.new do |rdoc|
41
+ rdoc.rdoc_dir = "doc/rdoc"
42
+ rdoc.options += RDocOptions
43
+ rdoc.main = "README"
44
+ rdoc.title = Title
45
+ rdoc.rdoc_files.add ["README", "COPYING", "lib/#{PluginName}.rb", "lib/**/*.rb"]
46
+ end
47
+
48
+ spec = Gem::Specification.new do |s|
49
+ s.name = PluginName
50
+ s.version = Version
51
+ s.platform = Gem::Platform::RUBY
52
+ s.has_rdoc = true
53
+ s.extra_rdoc_files = ["README", "CHANGELOG", "COPYING"]
54
+ s.rdoc_options += RDocOptions# +
55
+ #["--exclude", "^(examples|extras)\/", "--exclude", "lib/sequel.rb"]
56
+ s.summary = Summary
57
+ s.description = Summary
58
+ s.author = Authors
59
+ s.email = Emails
60
+ s.homepage = Homepage
61
+ # change this to the plugin name, if the plugin has command line portion
62
+ #s.executables = ["sequel"]
63
+
64
+ s.add_dependency("sequel")
65
+ #s.add_dependency("sequel", ">= 0.4.1")
66
+
67
+ #s.required_ruby_version = ">= 1.8.4" # Sequel should take care of this :)
68
+
69
+ s.files = %w(COPYING README Rakefile) + Dir.glob("{bin,doc,spec,lib}/**/*")
70
+
71
+ s.require_path = "lib"
72
+ s.bindir = "bin"
73
+ end
74
+
75
+ Rake::GemPackageTask.new(spec) do |p|
76
+ p.need_tar = true
77
+ p.gem_spec = spec
78
+ end
79
+
80
+ task :release => [:package] do
81
+ sh %{rubyforge login}
82
+ sh %{rubyforge add_release sequel #{PluginName} #{Version} pkg/#{PluginName}-#{Version}.tgz}
83
+ sh %{rubyforge add_file sequel #{PluginName} #{Version} pkg/#{PluginName}-#{Version}.gem}
84
+ end
85
+
86
+ task :install do
87
+ sh %{rake package}
88
+ sh %{sudo gem install pkg/#{PluginName}-#{Version}.gem}
89
+ end
90
+
91
+ task :install_no_docs do
92
+ sh %{rake package}
93
+ sh %{sudo gem install pkg/#{PluginName}-#{Version} --no-rdoc --no-ri}
94
+ end
95
+
96
+ task :uninstall => [:clean] do
97
+ sh %{sudo gem uninstall #{PluginName}}
98
+ end
99
+
100
+ desc "Update docs and upload to rubyforge.org"
101
+ task :doc_rforge do
102
+ sh %{rake doc}
103
+ sh %{scp -r doc/rdoc/* ciconia@rubyforge.org:/var/www/gforge-projects/sequel/plugins/#{PluginName}}
104
+ end
105
+
106
+ ##############################################################################
107
+ # rSpec
108
+ ##############################################################################
109
+
110
+ require "spec/rake/spectask"
111
+
112
+ desc "Run specs with coverage"
113
+ Spec::Rake::SpecTask.new("spec") do |spec_task|
114
+ spec_task.spec_opts = File.read("spec/spec.opts").split("\n")
115
+ spec_task.spec_files = FileList["spec/*_spec.rb"].sort
116
+ spec_task.rcov = true
117
+ end
118
+
119
+ desc "Run specs without coverage"
120
+ Spec::Rake::SpecTask.new("spec_no_cov") do |spec_task|
121
+ spec_task.spec_opts = File.read("spec/spec.opts").split("\n")
122
+ spec_task.spec_files = FileList["spec/*_spec.rb"].sort
123
+ end
124
+
125
+ desc "Run all specs with coverage"
126
+ Spec::Rake::SpecTask.new("specs") do |spec_task|
127
+ spec_task.spec_opts = File.read("spec/spec.opts").split("\n")
128
+ spec_task.spec_files = FileList["spec/**/*_spec.rb"].sort
129
+ spec_task.rcov = true
130
+ end
131
+
132
+ desc "Run all specs without coverage"
133
+ Spec::Rake::SpecTask.new("specs_no_cov") do |spec_task|
134
+ spec_task.spec_opts = File.read("spec/spec.opts").split("\n")
135
+ spec_task.spec_files = FileList["spec/**/*_spec.rb"].sort
136
+ end
137
+
138
+ desc "Run all specs and output html"
139
+ Spec::Rake::SpecTask.new("specs_html") do |spec_task|
140
+ spec_task.spec_opts = ["--format", "html"]
141
+ spec_task.spec_files = Dir["spec/**/*_spec.rb"].sort
142
+ end
143
+
144
+ ##############################################################################
145
+ # Statistics
146
+ ##############################################################################
147
+
148
+ STATS_DIRECTORIES = [
149
+ %w(Code lib/),
150
+ %w(Spec spec/)
151
+ ].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }
152
+
153
+ desc "Report code statistics (KLOCs, etc) from the application"
154
+ task :stats do
155
+ require "extra/stats"
156
+ verbose = true
157
+ CodeStatistics.new(*STATS_DIRECTORIES).to_s
158
+ end
@@ -0,0 +1,84 @@
1
+ module Sequel
2
+ module Plugins
3
+ module Orderable
4
+
5
+ def self.apply(model, opts = {})
6
+ position_field = opts[:field] || :position
7
+ scope_field = opts[:scope]
8
+
9
+ model.class_def(:at_position) do |p|
10
+ if scope_field
11
+ dataset.first(scope_field => @values[scope_field], position_field => p)
12
+ else
13
+ dataset.first(position_field => p)
14
+ end
15
+ end
16
+
17
+ model.class_def(:move_to) do |pos|
18
+ # XXX: error checking, negative pos?
19
+ cur_pos = position
20
+ return self if pos == cur_pos
21
+
22
+ db.transaction do
23
+ if pos < cur_pos
24
+ ds = self.class.filter {position_field >= pos and position_field < cur_pos}
25
+ ds.filter!(scope_field => @values[scope_field]) if scope_field
26
+ ds.update(position_field => "#{position_field} + 1".lit)
27
+ elsif pos > cur_pos
28
+ ds = self.class.filter {position_field > cur_pos and position_field <= pos}
29
+ ds.filter!(scope_field => @values[scope_field]) if scope_field
30
+ ds.update(position_field => "#{position_field} - 1".lit)
31
+ end
32
+ set(position_field => pos)
33
+ end
34
+ end
35
+
36
+ model.class_def(:move_to_bottom) do
37
+ ds = dataset
38
+ ds = ds.filter(scope_field => @values[scope_field]) if scope_field
39
+ last = ds.select(:max[position_field] => :max).first.values[:max].to_i
40
+ self.move_to(last)
41
+ end
42
+
43
+ model.class_def(:position) {self[position_field]}
44
+
45
+ if scope_field
46
+ model.dataset.order!(scope_field, position_field)
47
+ else
48
+ model.dataset.order!(position_field)
49
+ end
50
+
51
+ model.send(:include, InstanceMethods)
52
+ end
53
+
54
+ module InstanceMethods
55
+ def prev(n = 1)
56
+ target = position - n
57
+ # XXX: error checking, negative target?
58
+ return self if position == target
59
+ at_position(target)
60
+ end
61
+
62
+ def next(n = 1)
63
+ target = position + n
64
+ at_position(target)
65
+ end
66
+
67
+ def move_up(n = 1)
68
+ # XXX: position == 1 already?
69
+ self.move_to(position-n)
70
+ end
71
+
72
+ def move_down(n = 1)
73
+ # XXX: what if we're already at the bottom
74
+ self.move_to(position+n)
75
+ end
76
+
77
+ def move_to_top
78
+ self.move_to(1)
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,116 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ DB = Sequel.sqlite
4
+
5
+ class Item < Sequel::Model(:items)
6
+
7
+ set_schema do
8
+ primary_key :id
9
+ varchar :name
10
+ int :pos
11
+ end
12
+
13
+ is :orderable, :field => :pos
14
+
15
+ end
16
+
17
+ describe Item do
18
+ before(:all) {
19
+ Item.create_table!
20
+
21
+ Item.create :name => "one", :pos => 3
22
+ Item.create :name => "two", :pos => 2
23
+ Item.create :name => "three", :pos => 1
24
+ }
25
+
26
+ it "should return rows in order of position" do
27
+ Item.map(&:pos).should == [1,2,3]
28
+ Item.map(&:name).should == %w[ three two one ]
29
+ end
30
+
31
+ it "should define prev and next" do
32
+ i = Item[:name => "two"]
33
+ i.prev.should == Item[:name => "three"]
34
+ i.next.should == Item[:name => "one"]
35
+ end
36
+
37
+ it "should define move_to" do
38
+ Item[:name => "two"].move_to(1)
39
+ Item.map(&:name).should == %w[ two three one ]
40
+
41
+ Item[:name => "two"].move_to(3)
42
+ Item.map(&:name).should == %w[ three one two ]
43
+ end
44
+
45
+ it "should define move_to_top and move_to_bottom" do
46
+ Item[:name => "two"].move_to_top
47
+ Item.map(&:name).should == %w[ two three one ]
48
+
49
+ Item[:name => "two"].move_to_bottom
50
+ Item.map(&:name).should == %w[ three one two ]
51
+ end
52
+
53
+ it "should define move_up and move_down" do
54
+ Item[:name => "one"].move_up
55
+ Item.map(&:name).should == %w[ one three two ]
56
+
57
+ Item[:name => "three"].move_down
58
+ Item.map(&:name).should == %w[ one two three ]
59
+ end
60
+
61
+ end
62
+
63
+ class ListItem < Sequel::Model(:list_items)
64
+
65
+ set_schema do
66
+ primary_key :id
67
+ int :list_id
68
+ varchar :name
69
+ int :position
70
+ end
71
+
72
+ is :orderable, :scope => :list_id
73
+
74
+ end
75
+
76
+ describe ListItem do
77
+
78
+ before(:all) {
79
+ ListItem.create_table!
80
+
81
+ ListItem.create :name => "a", :list_id => 1, :position => 3
82
+ ListItem.create :name => "b", :list_id => 1, :position => 2
83
+ ListItem.create :name => "c", :list_id => 1, :position => 1
84
+
85
+ ListItem.create :name => "d", :list_id => 2, :position => 1
86
+ ListItem.create :name => "e", :list_id => 2, :position => 2
87
+ ListItem.create :name => "f", :list_id => 2, :position => 3
88
+ }
89
+
90
+ it "should print in order with scope provided" do
91
+ ListItem.map(&:name).should == %w[ c b a d e f ]
92
+ end
93
+
94
+ it "should fetch prev and next records with scope" do
95
+ b = ListItem[:name => "b"]
96
+ b.next.name.should == "a"
97
+ b.prev.name.should == "c"
98
+ b.next.next.should be_nil
99
+ b.prev.prev.should be_nil
100
+
101
+ e = ListItem[:name => "e"]
102
+ e.next.name.should == "f"
103
+ e.prev.name.should == "d"
104
+ e.next.next.should be_nil
105
+ e.prev.prev.should be_nil
106
+ end
107
+
108
+ it "should move only within the scope provided" do
109
+ ListItem[:name => "b"].move_to_top
110
+ ListItem.map(&:name).should == %w[ b c a d e f ]
111
+
112
+ ListItem[:name => "c"].move_to_bottom
113
+ ListItem.map(&:name).should == %w[ b a c d e f ]
114
+ end
115
+
116
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,6 @@
1
+ --colour
2
+ --loadby
3
+ mtime
4
+ --backtrace
5
+ --format
6
+ specdoc
@@ -0,0 +1,6 @@
1
+ require "sequel"
2
+ require File.join(File.dirname(__FILE__), "../lib/sequel_orderable")
3
+
4
+ class Symbol
5
+ def to_proc() lambda{ |object, *args| object.send(self, *args) } end
6
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequel_orderable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Wayne E. Seguin & Aman Gupta
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2007-12-04 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: sequel
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ description: Sequel Plugin
25
+ email: wayneeseugin@gmail.com,sequel@tmm1.net
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - README
32
+ - CHANGELOG
33
+ - COPYING
34
+ files:
35
+ - COPYING
36
+ - README
37
+ - Rakefile
38
+ - spec/sequel_orderable_spec.rb
39
+ - spec/spec.opts
40
+ - spec/spec_helper.rb
41
+ - lib/sequel_orderable.rb
42
+ - CHANGELOG
43
+ has_rdoc: true
44
+ homepage: http://sequel.rubyforge.org
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --quiet
48
+ - --title
49
+ - Orderable Sequel Plugin
50
+ - --opname
51
+ - index.html
52
+ - --line-numbers
53
+ - --main
54
+ - README
55
+ - --inline-source
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 0.9.5
74
+ signing_key:
75
+ specification_version: 2
76
+ summary: Sequel Plugin
77
+ test_files: []
78
+