Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 7d1d1ab88cae0d39bd362bf5efff6bd9d62a0c41 0 parents
Paweł Kondzior authored
BIN  .DS_Store
Binary file not shown
1  CHANGELOG
@@ -0,0 +1 @@
+
21 COPYING
@@ -0,0 +1,21 @@
+Copyright (c) 2009 Paweł Kondzior
+Copyright (c) 2007-2008 collectiveidea
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3  README
@@ -0,0 +1,3 @@
+Port of awesome nested set to Sequel::Model
+
+more readme tobedone.
161 Rakefile
@@ -0,0 +1,161 @@
+##############################################################################
+# Constants
+##############################################################################
+
+PluginName = "sequel_timestamped"
+Version = "0.0.1"
+Title = "Timestamped Sequel Plugin"
+Summary = "Sequel Plugin"
+Authors = "Brian Cooke"
+Emails = "cookebri@gmail.com"
+Homepage = "http://sequel.rubyforge.org"
+
+##############################################################################
+# Gem Management
+##############################################################################
+require "rake"
+require "rake/clean"
+require "rake/gempackagetask"
+require "rake/rdoctask"
+require "fileutils"
+
+include FileUtils
+
+CLEAN.include ["**/.*.sw?", "pkg/*", ".config", "doc/*", "coverage/*"]
+
+RDocOptions = [
+ "--quiet", "--title", Title,
+ "--opname", "index.html",
+ "--line-numbers",
+ "--main", "README",
+ "--inline-source"
+]
+
+desc "Packages up the Sequel Plugin: #{PluginName}."
+task :default => [:package]
+task :package => [:clean]
+
+task :doc => [:rdoc]
+
+Rake::RDocTask.new do |rdoc|
+ rdoc.rdoc_dir = "doc/rdoc"
+ rdoc.options += RDocOptions
+ rdoc.main = "README"
+ rdoc.title = Title
+ rdoc.rdoc_files.add ["README", "COPYING", "lib/#{PluginName}.rb", "lib/**/*.rb"]
+end
+
+spec = Gem::Specification.new do |s|
+ s.name = PluginName
+ s.version = Version
+ s.platform = Gem::Platform::RUBY
+ s.has_rdoc = true
+ s.extra_rdoc_files = ["README", "CHANGELOG", "COPYING"]
+ s.rdoc_options += RDocOptions# +
+ #["--exclude", "^(examples|extras)\/", "--exclude", "lib/sequel.rb"]
+ s.summary = Summary
+ s.description = Summary
+ s.author = Authors
+ s.email = Emails
+ s.homepage = Homepage
+ # change this to the plugin name, if the plugin has command line portion
+ #s.executables = ["sequel"]
+
+ s.add_dependency("sequel_model")
+
+ s.files = %w(COPYING README Rakefile) + Dir.glob("{bin,doc,spec,lib}/**/*")
+
+ s.require_path = "lib"
+ s.bindir = "bin"
+end
+
+Rake::GemPackageTask.new(spec) do |p|
+ p.need_tar = true
+ p.gem_spec = spec
+end
+
+task :release => [:package] do
+ sh %{rubyforge login}
+ sh %{rubyforge add_release sequel #{PluginName} #{Version} pkg/#{PluginName}-#{Version}.tgz}
+ sh %{rubyforge add_file sequel #{PluginName} #{Version} pkg/#{PluginName}-#{Version}.gem}
+end
+
+task :install do
+ sh %{rake package}
+ sh %{sudo gem install pkg/#{PluginName}-#{Version}.gem}
+end
+
+task :install_no_docs do
+ sh %{rake package}
+ sh %{sudo gem install pkg/#{PluginName}-#{Version}.gem --no-rdoc --no-ri}
+end
+
+task :uninstall => [:clean] do
+ sh %{sudo gem uninstall #{PluginName}}
+end
+
+desc "Update docs and upload to rubyforge.org"
+task :doc_rforge do
+ sh %{rake doc}
+ sh %{scp -r doc/rdoc/* ciconia@rubyforge.org:/var/www/gforge-projects/sequel/plugins/#{PluginName}}
+end
+
+##############################################################################
+# rSpec
+##############################################################################
+
+require "spec/rake/spectask"
+
+desc "Run specs with coverage"
+Spec::Rake::SpecTask.new("spec") do |spec_task|
+ spec_task.spec_opts = File.read("spec/spec.opts").split("\n")
+ spec_task.spec_files = FileList["spec/*_spec.rb"].sort
+ spec_task.rcov = true
+ spec_task.rcov_opts = lambda do
+ IO.readlines("spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
+ end
+end
+
+desc "Run specs without coverage"
+Spec::Rake::SpecTask.new("spec_no_cov") do |spec_task|
+ spec_task.spec_opts = File.read("spec/spec.opts").split("\n")
+ spec_task.spec_files = FileList["spec/*_spec.rb"].sort
+end
+
+desc "Run all specs with coverage"
+Spec::Rake::SpecTask.new("specs") do |spec_task|
+ spec_task.spec_opts = File.read("spec/spec.opts").split("\n")
+ spec_task.spec_files = FileList["spec/**/*_spec.rb"].sort
+ spec_task.rcov = true
+ spec_task.rcov_opts = lambda do
+ IO.readlines("spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
+ end
+end
+
+desc "Run all specs without coverage"
+Spec::Rake::SpecTask.new("specs_no_cov") do |spec_task|
+ spec_task.spec_opts = File.read("spec/spec.opts").split("\n")
+ spec_task.spec_files = FileList["spec/**/*_spec.rb"].sort
+end
+
+desc "Run all specs and output html"
+Spec::Rake::SpecTask.new("specs_html") do |spec_task|
+ spec_task.spec_opts = ["--format", "html"]
+ spec_task.spec_files = Dir["spec/**/*_spec.rb"].sort
+end
+
+##############################################################################
+# Statistics
+##############################################################################
+
+STATS_DIRECTORIES = [
+ %w(Code lib/),
+ %w(Spec spec/)
+].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }
+
+desc "Report code statistics (KLOCs, etc) from the application"
+task :stats do
+ require "extra/stats"
+ verbose = true
+ CodeStatistics.new(*STATS_DIRECTORIES).to_s
+end
3  TODO
@@ -0,0 +1,3 @@
+- Add configuration options
+- Add move, rebuild! and integration check methods, and specs for methods + full code covarage
+- Do more specs for scoped nested_set
225 coverage/index.html
@@ -0,0 +1,225 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html lang='en' xml:lang='en' xmlns='http://www.w3.org/1999/xhtml'><head><title>C0 code coverage information</title>
+ <style type='text/css'>body { background-color: rgb(240, 240, 245); }</style>
+ <style type='text/css'>span.cross-ref-title {
+ font-size: 140%;
+}
+span.cross-ref a {
+ text-decoration: none;
+}
+span.cross-ref {
+ background-color:#f3f7fa;
+ border: 1px dashed #333;
+ margin: 1em;
+ padding: 0.5em;
+ overflow: hidden;
+}
+a.crossref-toggle {
+ text-decoration: none;
+}
+span.marked0 {
+ background-color: rgb(185, 210, 200);
+ display: block;
+}
+span.marked1 {
+ background-color: rgb(190, 215, 205);
+ display: block;
+}
+span.inferred0 {
+ background-color: rgb(255, 255, 240);
+ display: block;
+}
+span.inferred1 {
+ background-color: rgb(255, 255, 240);
+ display: block;
+}
+span.uncovered0 {
+ background-color: rgb(225, 110, 110);
+ display: block;
+}
+span.uncovered1 {
+ background-color: rgb(235, 120, 120);
+ display: block;
+}
+span.overview {
+ border-bottom: 8px solid black;
+}
+div.overview {
+ border-bottom: 8px solid black;
+}
+body {
+ font-family: verdana, arial, helvetica;
+}
+div.footer {
+ font-size: 68%;
+ margin-top: 1.5em;
+}
+h1, h2, h3, h4, h5, h6 {
+ margin-bottom: 0.5em;
+}
+h5 {
+ margin-top: 0.5em;
+}
+.hidden {
+ display: none;
+}
+div.separator {
+ height: 10px;
+}
+/* Commented out for better readability, esp. on IE */
+/*
+table tr td, table tr th {
+ font-size: 68%;
+}
+td.value table tr td {
+ font-size: 11px;
+}
+*/
+table.percent_graph {
+ height: 12px;
+ border: #808080 1px solid;
+ empty-cells: show;
+}
+table.percent_graph td.covered {
+ height: 10px;
+ background: #00f000;
+}
+table.percent_graph td.uncovered {
+ height: 10px;
+ background: #e00000;
+}
+table.percent_graph td.NA {
+ height: 10px;
+ background: #eaeaea;
+}
+table.report {
+ border-collapse: collapse;
+ width: 100%;
+}
+table.report td.heading {
+ background: #dcecff;
+ border: #d0d0d0 1px solid;
+ font-weight: bold;
+ text-align: center;
+}
+table.report td.heading:hover {
+ background: #c0ffc0;
+}
+table.report td.text {
+ border: #d0d0d0 1px solid;
+}
+table.report td.value,
+table.report td.lines_total,
+table.report td.lines_code {
+ text-align: right;
+ border: #d0d0d0 1px solid;
+}
+table.report tr.light {
+ background-color: rgb(240, 240, 245);
+}
+table.report tr.dark {
+ background-color: rgb(230, 230, 235);
+}
+</style>
+ <script type='text/javascript'>
+// <![CDATA[
+ function toggleCode( id ) {
+ if ( document.getElementById )
+ elem = document.getElementById( id );
+ else if ( document.all )
+ elem = eval( "document.all." + id );
+ else
+ return false;
+
+ elemStyle = elem.style;
+
+ if ( elemStyle.display != "block" ) {
+ elemStyle.display = "block"
+ } else {
+ elemStyle.display = "none"
+ }
+
+ return true;
+ }
+
+ // Make cross-references hidden by default
+ document.writeln( "<style type=\"text/css\">span.cross-ref { display: none }</style>" )
+ // ]]>
+</script>
+ </head>
+ <body><h3>C0 code coverage information</h3>
+ <p>Generated on Thu Jan 08 04:51:09 +0100 2009 with <a href='http://github.com/spicycode/rcov'>rcov 0.8.1.5</a>
+ </p>
+ <hr/>
+ <table class='report'><thead><tr><td class='heading'>Name</td>
+ <td class='heading'>Total lines</td>
+ <td class='heading'>Lines of code</td>
+ <td class='heading'>Total coverage</td>
+ <td class='heading'>Code coverage</td>
+ </tr>
+ </thead>
+ <tbody><tr class='light'><td>TOTAL</td>
+ <td class='lines_total'><tt>223</tt>
+ </td>
+ <td class='lines_code'><tt>149</tt>
+ </td>
+ <td><table cellspacing='0' cellpadding='0' align='right'><tr><td><tt class='coverage_total'>95.1%</tt>
+ &nbsp;</td>
+ <td><table cellspacing='0' class='percent_graph' cellpadding='0' width='100'><tr><td class='covered' width='95'/>
+ <td class='uncovered' width='5'/>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </td>
+ <td><table cellspacing='0' cellpadding='0' align='right'><tr><td><tt class='coverage_code'>93.3%</tt>
+ &nbsp;</td>
+ <td><table cellspacing='0' class='percent_graph' cellpadding='0' width='100'><tr><td class='covered' width='93'/>
+ <td class='uncovered' width='7'/>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr class='dark'><td><a href='lib-sequel_nested_set_rb.html'>lib/sequel_nested_set.rb</a>
+ </td>
+ <td class='lines_total'><tt>223</tt>
+ </td>
+ <td class='lines_code'><tt>149</tt>
+ </td>
+ <td><table cellspacing='0' cellpadding='0' align='right'><tr><td><tt class='coverage_total'>95.1%</tt>
+ &nbsp;</td>
+ <td><table cellspacing='0' class='percent_graph' cellpadding='0' width='100'><tr><td class='covered' width='95'/>
+ <td class='uncovered' width='5'/>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </td>
+ <td><table cellspacing='0' cellpadding='0' align='right'><tr><td><tt class='coverage_code'>93.3%</tt>
+ &nbsp;</td>
+ <td><table cellspacing='0' class='percent_graph' cellpadding='0' width='100'><tr><td class='covered' width='93'/>
+ <td class='uncovered' width='7'/>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+<hr/>
+ <p>Generated using the <a href='http://eigenclass.org/hiki.rb?rcov'>rcov code coverage analysis tool for Ruby</a>
+ version 0.8.1.5.</p>
+<p><a href='http://validator.w3.org/check/referer'><img src='http://www.w3.org/Icons/valid-xhtml11' height='31' alt='Valid XHTML 1.1!' width='88'/>
+ </a>
+ <a href='http://jigsaw.w3.org/css-validator/check/referer'><img src='http://jigsaw.w3.org/css-validator/images/vcss' alt='Valid CSS!' style='border:0;width:88px;height:31px'/>
+ </a>
+ </p>
+ </body>
+ </html>
833 coverage/lib-sequel_nested_set_rb.html
@@ -0,0 +1,833 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html lang='en' xml:lang='en' xmlns='http://www.w3.org/1999/xhtml'><head><title>lib/sequel_nested_set.rb - C0 code coverage information</title>
+ <style type='text/css'>body { background-color: rgb(240, 240, 245); }</style>
+ <style type='text/css'>span.cross-ref-title {
+ font-size: 140%;
+}
+span.cross-ref a {
+ text-decoration: none;
+}
+span.cross-ref {
+ background-color:#f3f7fa;
+ border: 1px dashed #333;
+ margin: 1em;
+ padding: 0.5em;
+ overflow: hidden;
+}
+a.crossref-toggle {
+ text-decoration: none;
+}
+span.marked0 {
+ background-color: rgb(185, 210, 200);
+ display: block;
+}
+span.marked1 {
+ background-color: rgb(190, 215, 205);
+ display: block;
+}
+span.inferred0 {
+ background-color: rgb(255, 255, 240);
+ display: block;
+}
+span.inferred1 {
+ background-color: rgb(255, 255, 240);
+ display: block;
+}
+span.uncovered0 {
+ background-color: rgb(225, 110, 110);
+ display: block;
+}
+span.uncovered1 {
+ background-color: rgb(235, 120, 120);
+ display: block;
+}
+span.overview {
+ border-bottom: 8px solid black;
+}
+div.overview {
+ border-bottom: 8px solid black;
+}
+body {
+ font-family: verdana, arial, helvetica;
+}
+div.footer {
+ font-size: 68%;
+ margin-top: 1.5em;
+}
+h1, h2, h3, h4, h5, h6 {
+ margin-bottom: 0.5em;
+}
+h5 {
+ margin-top: 0.5em;
+}
+.hidden {
+ display: none;
+}
+div.separator {
+ height: 10px;
+}
+/* Commented out for better readability, esp. on IE */
+/*
+table tr td, table tr th {
+ font-size: 68%;
+}
+td.value table tr td {
+ font-size: 11px;
+}
+*/
+table.percent_graph {
+ height: 12px;
+ border: #808080 1px solid;
+ empty-cells: show;
+}
+table.percent_graph td.covered {
+ height: 10px;
+ background: #00f000;
+}
+table.percent_graph td.uncovered {
+ height: 10px;
+ background: #e00000;
+}
+table.percent_graph td.NA {
+ height: 10px;
+ background: #eaeaea;
+}
+table.report {
+ border-collapse: collapse;
+ width: 100%;
+}
+table.report td.heading {
+ background: #dcecff;
+ border: #d0d0d0 1px solid;
+ font-weight: bold;
+ text-align: center;
+}
+table.report td.heading:hover {
+ background: #c0ffc0;
+}
+table.report td.text {
+ border: #d0d0d0 1px solid;
+}
+table.report td.value,
+table.report td.lines_total,
+table.report td.lines_code {
+ text-align: right;
+ border: #d0d0d0 1px solid;
+}
+table.report tr.light {
+ background-color: rgb(240, 240, 245);
+}
+table.report tr.dark {
+ background-color: rgb(230, 230, 235);
+}
+</style>
+ <script type='text/javascript'>
+// <![CDATA[
+ function toggleCode( id ) {
+ if ( document.getElementById )
+ elem = document.getElementById( id );
+ else if ( document.all )
+ elem = eval( "document.all." + id );
+ else
+ return false;
+
+ elemStyle = elem.style;
+
+ if ( elemStyle.display != "block" ) {
+ elemStyle.display = "block"
+ } else {
+ elemStyle.display = "none"
+ }
+
+ return true;
+ }
+
+ // Make cross-references hidden by default
+ document.writeln( "<style type=\"text/css\">span.cross-ref { display: none }</style>" )
+ // ]]>
+</script>
+ <style type='text/css'>span.run0 {
+ background-color: rgb(178, 204, 255);
+ display: block;
+}
+span.run1 {
+ background-color: rgb(178, 206, 255);
+ display: block;
+}
+span.run2 {
+ background-color: rgb(178, 209, 255);
+ display: block;
+}
+span.run3 {
+ background-color: rgb(178, 211, 255);
+ display: block;
+}
+span.run4 {
+ background-color: rgb(178, 214, 255);
+ display: block;
+}
+span.run5 {
+ background-color: rgb(178, 218, 255);
+ display: block;
+}
+span.run6 {
+ background-color: rgb(178, 220, 255);
+ display: block;
+}
+span.run7 {
+ background-color: rgb(178, 223, 255);
+ display: block;
+}
+span.run8 {
+ background-color: rgb(178, 225, 255);
+ display: block;
+}
+span.run9 {
+ background-color: rgb(178, 228, 255);
+ display: block;
+}
+span.run10 {
+ background-color: rgb(178, 232, 255);
+ display: block;
+}
+span.run11 {
+ background-color: rgb(178, 234, 255);
+ display: block;
+}
+span.run12 {
+ background-color: rgb(178, 237, 255);
+ display: block;
+}
+span.run13 {
+ background-color: rgb(178, 239, 255);
+ display: block;
+}
+span.run14 {
+ background-color: rgb(178, 242, 255);
+ display: block;
+}
+span.run15 {
+ background-color: rgb(178, 246, 255);
+ display: block;
+}
+span.run16 {
+ background-color: rgb(178, 248, 255);
+ display: block;
+}
+span.run17 {
+ background-color: rgb(178, 251, 255);
+ display: block;
+}
+span.run18 {
+ background-color: rgb(178, 253, 255);
+ display: block;
+}
+span.run19 {
+ background-color: rgb(178, 255, 253);
+ display: block;
+}
+span.run20 {
+ background-color: rgb(178, 255, 249);
+ display: block;
+}
+span.run21 {
+ background-color: rgb(178, 255, 247);
+ display: block;
+}
+span.run22 {
+ background-color: rgb(178, 255, 244);
+ display: block;
+}
+span.run23 {
+ background-color: rgb(178, 255, 242);
+ display: block;
+}
+span.run24 {
+ background-color: rgb(178, 255, 239);
+ display: block;
+}
+span.run25 {
+ background-color: rgb(178, 255, 235);
+ display: block;
+}
+span.run26 {
+ background-color: rgb(178, 255, 233);
+ display: block;
+}
+span.run27 {
+ background-color: rgb(178, 255, 230);
+ display: block;
+}
+span.run28 {
+ background-color: rgb(178, 255, 228);
+ display: block;
+}
+span.run29 {
+ background-color: rgb(178, 255, 225);
+ display: block;
+}
+span.run30 {
+ background-color: rgb(178, 255, 221);
+ display: block;
+}
+span.run31 {
+ background-color: rgb(178, 255, 219);
+ display: block;
+}
+span.run32 {
+ background-color: rgb(178, 255, 216);
+ display: block;
+}
+span.run33 {
+ background-color: rgb(178, 255, 214);
+ display: block;
+}
+span.run34 {
+ background-color: rgb(178, 255, 211);
+ display: block;
+}
+span.run35 {
+ background-color: rgb(178, 255, 207);
+ display: block;
+}
+span.run36 {
+ background-color: rgb(178, 255, 205);
+ display: block;
+}
+span.run37 {
+ background-color: rgb(178, 255, 202);
+ display: block;
+}
+span.run38 {
+ background-color: rgb(178, 255, 200);
+ display: block;
+}
+span.run39 {
+ background-color: rgb(178, 255, 197);
+ display: block;
+}
+span.run40 {
+ background-color: rgb(178, 255, 193);
+ display: block;
+}
+span.run41 {
+ background-color: rgb(178, 255, 191);
+ display: block;
+}
+span.run42 {
+ background-color: rgb(178, 255, 188);
+ display: block;
+}
+span.run43 {
+ background-color: rgb(178, 255, 186);
+ display: block;
+}
+span.run44 {
+ background-color: rgb(178, 255, 183);
+ display: block;
+}
+span.run45 {
+ background-color: rgb(178, 255, 179);
+ display: block;
+}
+span.run46 {
+ background-color: rgb(179, 255, 178);
+ display: block;
+}
+span.run47 {
+ background-color: rgb(182, 255, 178);
+ display: block;
+}
+span.run48 {
+ background-color: rgb(184, 255, 178);
+ display: block;
+}
+span.run49 {
+ background-color: rgb(187, 255, 178);
+ display: block;
+}
+span.run50 {
+ background-color: rgb(191, 255, 178);
+ display: block;
+}
+span.run51 {
+ background-color: rgb(193, 255, 178);
+ display: block;
+}
+span.run52 {
+ background-color: rgb(196, 255, 178);
+ display: block;
+}
+span.run53 {
+ background-color: rgb(198, 255, 178);
+ display: block;
+}
+span.run54 {
+ background-color: rgb(201, 255, 178);
+ display: block;
+}
+span.run55 {
+ background-color: rgb(205, 255, 178);
+ display: block;
+}
+span.run56 {
+ background-color: rgb(207, 255, 178);
+ display: block;
+}
+span.run57 {
+ background-color: rgb(210, 255, 178);
+ display: block;
+}
+span.run58 {
+ background-color: rgb(212, 255, 178);
+ display: block;
+}
+span.run59 {
+ background-color: rgb(215, 255, 178);
+ display: block;
+}
+span.run60 {
+ background-color: rgb(219, 255, 178);
+ display: block;
+}
+span.run61 {
+ background-color: rgb(221, 255, 178);
+ display: block;
+}
+span.run62 {
+ background-color: rgb(224, 255, 178);
+ display: block;
+}
+span.run63 {
+ background-color: rgb(226, 255, 178);
+ display: block;
+}
+span.run64 {
+ background-color: rgb(229, 255, 178);
+ display: block;
+}
+span.run65 {
+ background-color: rgb(233, 255, 178);
+ display: block;
+}
+span.run66 {
+ background-color: rgb(235, 255, 178);
+ display: block;
+}
+span.run67 {
+ background-color: rgb(238, 255, 178);
+ display: block;
+}
+span.run68 {
+ background-color: rgb(240, 255, 178);
+ display: block;
+}
+span.run69 {
+ background-color: rgb(243, 255, 178);
+ display: block;
+}
+span.run70 {
+ background-color: rgb(247, 255, 178);
+ display: block;
+}
+span.run71 {
+ background-color: rgb(249, 255, 178);
+ display: block;
+}
+span.run72 {
+ background-color: rgb(252, 255, 178);
+ display: block;
+}
+span.run73 {
+ background-color: rgb(255, 255, 178);
+ display: block;
+}
+span.run74 {
+ background-color: rgb(255, 252, 178);
+ display: block;
+}
+span.run75 {
+ background-color: rgb(255, 248, 178);
+ display: block;
+}
+span.run76 {
+ background-color: rgb(255, 246, 178);
+ display: block;
+}
+span.run77 {
+ background-color: rgb(255, 243, 178);
+ display: block;
+}
+span.run78 {
+ background-color: rgb(255, 240, 178);
+ display: block;
+}
+span.run79 {
+ background-color: rgb(255, 238, 178);
+ display: block;
+}
+span.run80 {
+ background-color: rgb(255, 234, 178);
+ display: block;
+}
+span.run81 {
+ background-color: rgb(255, 232, 178);
+ display: block;
+}
+span.run82 {
+ background-color: rgb(255, 229, 178);
+ display: block;
+}
+span.run83 {
+ background-color: rgb(255, 226, 178);
+ display: block;
+}
+span.run84 {
+ background-color: rgb(255, 224, 178);
+ display: block;
+}
+span.run85 {
+ background-color: rgb(255, 220, 178);
+ display: block;
+}
+span.run86 {
+ background-color: rgb(255, 218, 178);
+ display: block;
+}
+span.run87 {
+ background-color: rgb(255, 215, 178);
+ display: block;
+}
+span.run88 {
+ background-color: rgb(255, 212, 178);
+ display: block;
+}
+span.run89 {
+ background-color: rgb(255, 210, 178);
+ display: block;
+}
+span.run90 {
+ background-color: rgb(255, 206, 178);
+ display: block;
+}
+span.run91 {
+ background-color: rgb(255, 204, 178);
+ display: block;
+}
+span.run92 {
+ background-color: rgb(255, 201, 178);
+ display: block;
+}
+span.run93 {
+ background-color: rgb(255, 198, 178);
+ display: block;
+}
+span.run94 {
+ background-color: rgb(255, 196, 178);
+ display: block;
+}
+span.run95 {
+ background-color: rgb(255, 192, 178);
+ display: block;
+}
+span.run96 {
+ background-color: rgb(255, 189, 178);
+ display: block;
+}
+span.run97 {
+ background-color: rgb(255, 187, 178);
+ display: block;
+}
+span.run98 {
+ background-color: rgb(255, 184, 178);
+ display: block;
+}
+span.run99 {
+ background-color: rgb(255, 182, 178);
+ display: block;
+}
+span.run100 {
+ background-color: rgb(255, 178, 178);
+ display: block;
+}
+</style>
+ </head>
+ <body><h3>C0 code coverage information</h3>
+ <p>Generated on Thu Jan 08 04:51:09 +0100 2009 with <a href='http://github.com/spicycode/rcov'>rcov 0.8.1.5</a>
+ </p>
+ <hr/>
+ <pre><span class='marked0'>Code reported as executed by Ruby looks like this...
+</span><span class='marked1'>and this: this line is also marked as covered.
+</span><span class='inferred0'>Lines considered as run by rcov, but not reported by Ruby, look like this,
+</span><span class='inferred1'>and this: these lines were inferred by rcov (using simple heuristics).
+</span><span class='uncovered0'>Finally, here&apos;s a line marked as not executed.
+</span></pre>
+<table class='report'><thead><tr><td class='heading'>Name</td>
+ <td class='heading'>Total lines</td>
+ <td class='heading'>Lines of code</td>
+ <td class='heading'>Total coverage</td>
+ <td class='heading'>Code coverage</td>
+ </tr>
+ </thead>
+ <tbody><tr class='light'><td><a href='lib-sequel_nested_set_rb.html'>lib/sequel_nested_set.rb</a>
+ </td>
+ <td class='lines_total'><tt>223</tt>
+ </td>
+ <td class='lines_code'><tt>149</tt>
+ </td>
+ <td><table cellspacing='0' cellpadding='0' align='right'><tr><td><tt class='coverage_total'>95.1%</tt>
+ &nbsp;</td>
+ <td><table cellspacing='0' class='percent_graph' cellpadding='0' width='100'><tr><td class='covered' width='95'/>
+ <td class='uncovered' width='5'/>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </td>
+ <td><table cellspacing='0' cellpadding='0' align='right'><tr><td><tt class='coverage_code'>93.3%</tt>
+ &nbsp;</td>
+ <td><table cellspacing='0' class='percent_graph' cellpadding='0' width='100'><tr><td class='covered' width='93'/>
+ <td class='uncovered' width='7'/>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+<pre><span class="marked1"><a name="line1"></a> 1 module Sequel
+</span><span class="marked0"><a name="line2"></a> 2 module Plugins
+</span><span class="marked1"><a name="line3"></a> 3 module NestedSet
+</span><span class="inferred0"><a name="line4"></a> 4 # Apply the plugin to the model.
+</span><span class="marked1"><a name="line5"></a> 5 def self.apply(model, options = {})
+</span><span class="marked0"><a name="line6"></a> 6 options = {
+</span><span class="inferred1"><a name="line7"></a> 7 :parent_column =&gt; :parent_id,
+</span><span class="inferred0"><a name="line8"></a> 8 :left_column =&gt; :lft,
+</span><span class="inferred1"><a name="line9"></a> 9 :right_column =&gt; :rgt,
+</span><span class="inferred0"><a name="line10"></a> 10 :dependent =&gt; :delete_all, # or :destroy
+</span><span class="inferred1"><a name="line11"></a> 11 }.merge(options)
+</span><span class="inferred0"><a name="line12"></a> 12
+</span><span class="marked1"><a name="line13"></a> 13 if options[:scope].is_a?(Symbol) &amp;&amp; options[:scope].to_s !~ /_id$/
+</span><span class="inferred0"><a name="line14"></a> 14 options[:scope] = &quot;#{options[:scope]}_id&quot;.to_sym
+</span><span class="inferred1"><a name="line15"></a> 15 end
+</span><span class="inferred0"><a name="line16"></a> 16
+</span><span class="marked1"><a name="line17"></a> 17 model.class.class_eval do
+</span><span class="marked0"><a name="line18"></a> 18 attr_accessor :nested_set_options
+</span><span class="inferred1"><a name="line19"></a> 19 end
+</span><span class="marked0"><a name="line20"></a> 20 model.nested_set_options = options
+</span><span class="inferred1"><a name="line21"></a> 21 end
+</span><span class="inferred0"><a name="line22"></a> 22
+</span><span class="marked1"><a name="line23"></a> 23 module DatasetMethods
+</span><span class="marked0"><a name="line24"></a> 24 def nested_scope
+</span><span class="marked1"><a name="line25"></a> 25 order(self.model_classes[nil].qualified_left_column)
+</span><span class="marked0"><a name="line26"></a> 26 end
+</span><span class="inferred1"><a name="line27"></a> 27
+</span><span class="marked0"><a name="line28"></a> 28 def roots
+</span><span class="marked1"><a name="line29"></a> 29 nested_scope.filter(self.model_classes[nil].qualified_parent_column =&gt; nil)
+</span><span class="marked0"><a name="line30"></a> 30 end
+</span><span class="inferred1"><a name="line31"></a> 31
+</span><span class="marked0"><a name="line32"></a> 32 def leaves
+</span><span class="marked1"><a name="line33"></a> 33 nested_scope.filter(self.model_classes[nil].qualified_right_column - self.model_classes[nil].qualified_left_column =&gt; 1)
+</span><span class="marked0"><a name="line34"></a> 34 end
+</span><span class="inferred1"><a name="line35"></a> 35 end
+</span><span class="inferred0"><a name="line36"></a> 36
+</span><span class="marked1"><a name="line37"></a> 37 module ClassMethods
+</span><span class="marked0"><a name="line38"></a> 38 def qualified_parent_column
+</span><span class="marked1"><a name="line39"></a> 39 &quot;#{self.implicit_table_name}__#{self.nested_set_options[:parent_column]}&quot;.to_sym
+</span><span class="marked0"><a name="line40"></a> 40 end
+</span><span class="inferred1"><a name="line41"></a> 41
+</span><span class="marked0"><a name="line42"></a> 42 def qualified_left_column
+</span><span class="marked1"><a name="line43"></a> 43 &quot;#{self.implicit_table_name}__#{self.nested_set_options[:left_column]}&quot;.to_sym
+</span><span class="marked0"><a name="line44"></a> 44 end
+</span><span class="inferred1"><a name="line45"></a> 45
+</span><span class="marked0"><a name="line46"></a> 46 def qualified_right_column
+</span><span class="marked1"><a name="line47"></a> 47 &quot;#{self.implicit_table_name}__#{self.nested_set_options[:right_column]}&quot;.to_sym
+</span><span class="marked0"><a name="line48"></a> 48 end
+</span><span class="inferred1"><a name="line49"></a> 49 end
+</span><span class="inferred0"><a name="line50"></a> 50
+</span><span class="marked1"><a name="line51"></a> 51 module InstanceMethods
+</span><span class="inferred0"><a name="line52"></a> 52
+</span><span class="inferred1"><a name="line53"></a> 53 # Returns hash of Model nested set options
+</span><span class="marked0"><a name="line54"></a> 54 def nested_set_options
+</span><span class="marked1"><a name="line55"></a> 55 self.class.nested_set_options
+</span><span class="marked0"><a name="line56"></a> 56 end
+</span><span class="inferred1"><a name="line57"></a> 57
+</span><span class="inferred0"><a name="line58"></a> 58 # Setter of the left column
+</span><span class="marked1"><a name="line59"></a> 59 def left=(value)
+</span><span class="marked0"><a name="line60"></a> 60 self[self.nested_set_options[:left_column]] = value
+</span><span class="marked1"><a name="line61"></a> 61 end
+</span><span class="inferred0"><a name="line62"></a> 62
+</span><span class="inferred1"><a name="line63"></a> 63 # Setter of the right column
+</span><span class="marked0"><a name="line64"></a> 64 def right=(value)
+</span><span class="marked1"><a name="line65"></a> 65 self[self.nested_set_options[:right_column]] = value
+</span><span class="marked0"><a name="line66"></a> 66 end
+</span><span class="inferred1"><a name="line67"></a> 67
+</span><span class="inferred0"><a name="line68"></a> 68 # Getter of the left column
+</span><span class="marked1"><a name="line69"></a> 69 def left
+</span><span class="marked0"><a name="line70"></a> 70 self[self.nested_set_options[:left_column]]
+</span><span class="marked1"><a name="line71"></a> 71 end
+</span><span class="inferred0"><a name="line72"></a> 72
+</span><span class="inferred1"><a name="line73"></a> 73 # Getter of the right column
+</span><span class="marked0"><a name="line74"></a> 74 def right
+</span><span class="marked1"><a name="line75"></a> 75 self[self.nested_set_options[:right_column]]
+</span><span class="marked0"><a name="line76"></a> 76 end
+</span><span class="inferred1"><a name="line77"></a> 77
+</span><span class="inferred0"><a name="line78"></a> 78 # Setter of the parent column
+</span><span class="marked1"><a name="line79"></a> 79 def parent_id=(value)
+</span><span class="marked0"><a name="line80"></a> 80 self[self.nested_set_options[:parent_column]] = value
+</span><span class="marked1"><a name="line81"></a> 81 end
+</span><span class="inferred0"><a name="line82"></a> 82
+</span><span class="inferred1"><a name="line83"></a> 83 # Getter of parent column
+</span><span class="marked0"><a name="line84"></a> 84 def parent_id
+</span><span class="marked1"><a name="line85"></a> 85 self[self.nested_set_options[:parent_column]]
+</span><span class="marked0"><a name="line86"></a> 86 end
+</span><span class="inferred1"><a name="line87"></a> 87
+</span><span class="inferred0"><a name="line88"></a> 88 # Set left=, right= and parent_id= to be procted methods
+</span><span class="inferred1"><a name="line89"></a> 89 # this methods should be used only internally by nested set plugin
+</span><span class="marked0"><a name="line90"></a> 90 protected :left=, :right=, :parent_id=
+</span><span class="inferred1"><a name="line91"></a> 91
+</span><span class="inferred0"><a name="line92"></a> 92 # Returns the level of this object in the tree
+</span><span class="inferred1"><a name="line93"></a> 93 # root level is 0
+</span><span class="marked0"><a name="line94"></a> 94 def level
+</span><span class="marked1"><a name="line95"></a> 95 root? ? 0 : ancestors.count
+</span><span class="marked0"><a name="line96"></a> 96 end
+</span><span class="inferred1"><a name="line97"></a> 97
+</span><span class="inferred0"><a name="line98"></a> 98 # Returns true if this is a root node
+</span><span class="marked1"><a name="line99"></a> 99 def root?
+</span><span class="marked0"><a name="line100"></a>100 parent_id.nil?
+</span><span class="marked1"><a name="line101"></a>101 end
+</span><span class="inferred0"><a name="line102"></a>102
+</span><span class="inferred1"><a name="line103"></a>103 # Returns true if this is a leaf node
+</span><span class="marked0"><a name="line104"></a>104 def leaf?
+</span><span class="marked1"><a name="line105"></a>105 right - left == 1
+</span><span class="marked0"><a name="line106"></a>106 end
+</span><span class="inferred1"><a name="line107"></a>107
+</span><span class="inferred0"><a name="line108"></a>108 # Returns true is this is a child node
+</span><span class="marked1"><a name="line109"></a>109 def child?
+</span><span class="marked0"><a name="line110"></a>110 !root?
+</span><span class="marked1"><a name="line111"></a>111 end
+</span><span class="inferred0"><a name="line112"></a>112
+</span><span class="inferred1"><a name="line113"></a>113 # order by left column
+</span><span class="marked0"><a name="line114"></a>114 def &lt;=&gt;(x)
+</span><span class="marked1"><a name="line115"></a>115 left &lt;=&gt; x.left
+</span><span class="marked0"><a name="line116"></a>116 end
+</span><span class="inferred1"><a name="line117"></a>117
+</span><span class="inferred0"><a name="line118"></a>118 # Returns root
+</span><span class="marked1"><a name="line119"></a>119 def root
+</span><span class="marked0"><a name="line120"></a>120 self_and_ancestors.first
+</span><span class="marked1"><a name="line121"></a>121 end
+</span><span class="inferred0"><a name="line122"></a>122
+</span><span class="inferred1"><a name="line123"></a>123 # Returns the immediate parent
+</span><span class="marked0"><a name="line124"></a>124 def parent
+</span><span class="marked1"><a name="line125"></a>125 dataset.nested_scope.filter(self.primary_key =&gt; self.parent_id).first if self.parent_id
+</span><span class="marked0"><a name="line126"></a>126 end
+</span><span class="inferred1"><a name="line127"></a>127
+</span><span class="inferred0"><a name="line128"></a>128 # Returns the dataset for all parent nodes and self
+</span><span class="marked1"><a name="line129"></a>129 def self_and_ancestors
+</span><span class="marked0"><a name="line130"></a>130 dataset.filter((self.class.qualified_left_column &lt;= left) &amp; (self.class.qualified_right_column &gt;= right))
+</span><span class="marked1"><a name="line131"></a>131 end
+</span><span class="inferred0"><a name="line132"></a>132
+</span><span class="inferred1"><a name="line133"></a>133 # Returns the dataset for all children of the parent, including self
+</span><span class="marked0"><a name="line134"></a>134 def self_and_siblings
+</span><span class="marked1"><a name="line135"></a>135 dataset.nested_scope.filter(self.class.qualified_parent_column =&gt; self.parent_id)
+</span><span class="marked0"><a name="line136"></a>136 end
+</span><span class="inferred1"><a name="line137"></a>137
+</span><span class="inferred0"><a name="line138"></a>138 # Returns dataset for itself and all of its nested children
+</span><span class="marked1"><a name="line139"></a>139 def self_and_descendants
+</span><span class="marked0"><a name="line140"></a>140 dataset.nested_scope.filter((self.class.qualified_left_column &gt;= left) &amp; (self.class.qualified_right_column &lt;= right))
+</span><span class="marked1"><a name="line141"></a>141 end
+</span><span class="inferred0"><a name="line142"></a>142
+</span><span class="inferred1"><a name="line143"></a>143 # Filter for dataset that will exclude self object
+</span><span class="marked0"><a name="line144"></a>144 def without_self(dataset)
+</span><span class="marked1"><a name="line145"></a>145 dataset.nested_scope.filter(~{self.primary_key =&gt; self.id})
+</span><span class="marked0"><a name="line146"></a>146 end
+</span><span class="inferred1"><a name="line147"></a>147
+</span><span class="inferred0"><a name="line148"></a>148 # Returns dataset for its immediate children
+</span><span class="marked1"><a name="line149"></a>149 def children
+</span><span class="marked0"><a name="line150"></a>150 dataset.nested_scope.filter(self.class.qualified_parent_column =&gt; self.id)
+</span><span class="marked1"><a name="line151"></a>151 end
+</span><span class="inferred0"><a name="line152"></a>152
+</span><span class="inferred1"><a name="line153"></a>153 # Returns dataset for all parents
+</span><span class="marked0"><a name="line154"></a>154 def ancestors
+</span><span class="marked1"><a name="line155"></a>155 without_self(self_and_ancestors)
+</span><span class="marked0"><a name="line156"></a>156 end
+</span><span class="inferred1"><a name="line157"></a>157
+</span><span class="inferred0"><a name="line158"></a>158 # Returns dataset for all children of the parent, except self
+</span><span class="marked1"><a name="line159"></a>159 def siblings
+</span><span class="marked0"><a name="line160"></a>160 without_self(self_and_siblings)
+</span><span class="marked1"><a name="line161"></a>161 end
+</span><span class="inferred0"><a name="line162"></a>162
+</span><span class="inferred1"><a name="line163"></a>163 # Returns dataset for all of its children and nested children
+</span><span class="marked0"><a name="line164"></a>164 def descendants
+</span><span class="marked1"><a name="line165"></a>165 without_self(self_and_descendants)
+</span><span class="marked0"><a name="line166"></a>166 end
+</span><span class="inferred1"><a name="line167"></a>167
+</span><span class="inferred0"><a name="line168"></a>168 # Returns dataset for all of its nested children which do not have children
+</span><span class="marked1"><a name="line169"></a>169 def leaves
+</span><span class="marked0"><a name="line170"></a>170 descendants.filter(self.class.qualified_right_column - self.class.qualified_left_column =&gt; 1)
+</span><span class="marked1"><a name="line171"></a>171 end
+</span><span class="inferred0"><a name="line172"></a>172
+</span><span class="marked1"><a name="line173"></a>173 def is_descendant_of?(other)
+</span><span class="marked0"><a name="line174"></a>174 other.left &lt; self.left &amp;&amp; self.left &lt; other.right &amp;&amp; same_scope?(other)
+</span><span class="marked1"><a name="line175"></a>175 end
+</span><span class="inferred0"><a name="line176"></a>176
+</span><span class="marked1"><a name="line177"></a>177 def is_or_is_descendant_of?(other)
+</span><span class="marked0"><a name="line178"></a>178 other.left &lt;= self.left &amp;&amp; self.left &lt; other.right &amp;&amp; same_scope?(other)
+</span><span class="marked1"><a name="line179"></a>179 end
+</span><span class="inferred0"><a name="line180"></a>180
+</span><span class="marked1"><a name="line181"></a>181 def is_ancestor_of?(other)
+</span><span class="marked0"><a name="line182"></a>182 self.left &lt; other.left &amp;&amp; other.left &lt; self.right &amp;&amp; same_scope?(other)
+</span><span class="marked1"><a name="line183"></a>183 end
+</span><span class="inferred0"><a name="line184"></a>184
+</span><span class="marked1"><a name="line185"></a>185 def is_or_is_ancestor_of?(other)
+</span><span class="marked0"><a name="line186"></a>186 self.left &lt;= other.left &amp;&amp; other.left &lt; self.right &amp;&amp; same_scope?(other)
+</span><span class="marked1"><a name="line187"></a>187 end
+</span><span class="inferred0"><a name="line188"></a>188
+</span><span class="inferred1"><a name="line189"></a>189 # Check if other model is in the same scope
+</span><span class="marked0"><a name="line190"></a>190 def same_scope?(other)
+</span><span class="marked1"><a name="line191"></a>191 Array(nil).all? do |attr|
+</span><span class="uncovered0"><a name="line192"></a>192 self.send(attr) == other.send(attr)
+</span><span class="uncovered1"><a name="line193"></a>193 end
+</span><span class="marked0"><a name="line194"></a>194 end
+</span><span class="inferred1"><a name="line195"></a>195
+</span><span class="inferred0"><a name="line196"></a>196 # Find the first sibling to the left
+</span><span class="marked1"><a name="line197"></a>197 def left_sibling
+</span><span class="marked0"><a name="line198"></a>198 siblings.filter(self.class.qualified_left_column &lt; left).order(self.class.qualified_left_column.desc).first
+</span><span class="marked1"><a name="line199"></a>199 end
+</span><span class="inferred0"><a name="line200"></a>200
+</span><span class="inferred1"><a name="line201"></a>201 # Find the first sibling to the right
+</span><span class="marked0"><a name="line202"></a>202 def right_sibling
+</span><span class="marked1"><a name="line203"></a>203 siblings.filter(self.class.qualified_left_column &gt; left).first
+</span><span class="marked0"><a name="line204"></a>204 end
+</span><span class="inferred1"><a name="line205"></a>205
+</span><span class="marked0"><a name="line206"></a>206 def to_text
+</span><span class="marked1"><a name="line207"></a>207 self_and_descendants.map do |node|
+</span><span class="marked0"><a name="line208"></a>208 &quot;#{'*'*(node.level+1)} #{node.class.inspect} (#{node.parent_id.inspect}, #{node.left}, #{node.right})&quot;
+</span><span class="inferred1"><a name="line209"></a>209 end.join(&quot;\n&quot;)
+</span><span class="marked0"><a name="line210"></a>210 end
+</span><span class="inferred1"><a name="line211"></a>211
+</span><span class="marked0"><a name="line212"></a>212 protected
+</span><span class="inferred1"><a name="line213"></a>213 # on creation, set automatically lft and rgt to the end of the tree
+</span><span class="marked0"><a name="line214"></a>214 def set_default_left_and_right
+</span><span class="uncovered1"><a name="line215"></a>215 maxright = dataset.nested_scopemax(self.class.qualified_right_column) || 0
+</span><span class="uncovered0"><a name="line216"></a>216 # adds the new node to the right of all existing nodes
+</span><span class="uncovered1"><a name="line217"></a>217 self.left = maxright + 1
+</span><span class="uncovered0"><a name="line218"></a>218 self.right = maxright + 2
+</span><span class="uncovered1"><a name="line219"></a>219 end
+</span><span class="uncovered0"><a name="line220"></a>220 end
+</span><span class="uncovered1"><a name="line221"></a>221 end
+</span><span class="uncovered0"><a name="line222"></a>222 end
+</span><span class="uncovered1"><a name="line223"></a>223 end
+</span></pre><hr/>
+ <p>Generated using the <a href='http://eigenclass.org/hiki.rb?rcov'>rcov code coverage analysis tool for Ruby</a>
+ version 0.8.1.5.</p>
+<p><a href='http://validator.w3.org/check/referer'><img src='http://www.w3.org/Icons/valid-xhtml10' height='31' alt='Valid XHTML 1.0!' width='88'/>
+ </a>
+ <a href='http://jigsaw.w3.org/css-validator/check/referer'><img src='http://jigsaw.w3.org/css-validator/images/vcss' alt='Valid CSS!' style='border:0;width:88px;height:31px'/>
+ </a>
+ </p>
+ </body>
+ </html>
223 lib/sequel_nested_set.rb
@@ -0,0 +1,223 @@
+module Sequel
+ module Plugins
+ module NestedSet
+ # Apply the plugin to the model.
+ def self.apply(model, options = {})
+ options = {
+ :parent_column => :parent_id,
+ :left_column => :lft,
+ :right_column => :rgt,
+ :dependent => :delete_all, # or :destroy
+ }.merge(options)
+
+ if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
+ options[:scope] = "#{options[:scope]}_id".to_sym
+ end
+
+ model.class.class_eval do
+ attr_accessor :nested_set_options
+ end
+ model.nested_set_options = options
+ end
+
+ module DatasetMethods
+ def nested_scope
+ order(self.model_classes[nil].qualified_left_column)
+ end
+
+ def roots
+ nested_scope.filter(self.model_classes[nil].qualified_parent_column => nil)
+ end
+
+ def leaves
+ nested_scope.filter(self.model_classes[nil].qualified_right_column - self.model_classes[nil].qualified_left_column => 1)
+ end
+ end
+
+ module ClassMethods
+ def qualified_parent_column
+ "#{self.implicit_table_name}__#{self.nested_set_options[:parent_column]}".to_sym
+ end
+
+ def qualified_left_column
+ "#{self.implicit_table_name}__#{self.nested_set_options[:left_column]}".to_sym
+ end
+
+ def qualified_right_column
+ "#{self.implicit_table_name}__#{self.nested_set_options[:right_column]}".to_sym
+ end
+ end
+
+ module InstanceMethods
+
+ # Returns hash of Model nested set options
+ def nested_set_options
+ self.class.nested_set_options
+ end
+
+ # Setter of the left column
+ def left=(value)
+ self[self.nested_set_options[:left_column]] = value
+ end
+
+ # Setter of the right column
+ def right=(value)
+ self[self.nested_set_options[:right_column]] = value
+ end
+
+ # Getter of the left column
+ def left
+ self[self.nested_set_options[:left_column]]
+ end
+
+ # Getter of the right column
+ def right
+ self[self.nested_set_options[:right_column]]
+ end
+
+ # Setter of the parent column
+ def parent_id=(value)
+ self[self.nested_set_options[:parent_column]] = value
+ end
+
+ # Getter of parent column
+ def parent_id
+ self[self.nested_set_options[:parent_column]]
+ end
+
+ # Set left=, right= and parent_id= to be procted methods
+ # this methods should be used only internally by nested set plugin
+ protected :left=, :right=, :parent_id=
+
+ # Returns the level of this object in the tree
+ # root level is 0
+ def level
+ root? ? 0 : ancestors.count
+ end
+
+ # Returns true if this is a root node
+ def root?
+ parent_id.nil?
+ end
+
+ # Returns true if this is a leaf node
+ def leaf?
+ right - left == 1
+ end
+
+ # Returns true is this is a child node
+ def child?
+ !root?
+ end
+
+ # order by left column
+ def <=>(x)
+ left <=> x.left
+ end
+
+ # Returns root
+ def root
+ self_and_ancestors.first
+ end
+
+ # Returns the immediate parent
+ def parent
+ dataset.nested_scope.filter(self.primary_key => self.parent_id).first if self.parent_id
+ end
+
+ # Returns the dataset for all parent nodes and self
+ def self_and_ancestors
+ dataset.filter((self.class.qualified_left_column <= left) & (self.class.qualified_right_column >= right))
+ end
+
+ # Returns the dataset for all children of the parent, including self
+ def self_and_siblings
+ dataset.nested_scope.filter(self.class.qualified_parent_column => self.parent_id)
+ end
+
+ # Returns dataset for itself and all of its nested children
+ def self_and_descendants
+ dataset.nested_scope.filter((self.class.qualified_left_column >= left) & (self.class.qualified_right_column <= right))
+ end
+
+ # Filter for dataset that will exclude self object
+ def without_self(dataset)
+ dataset.nested_scope.filter(~{self.primary_key => self.id})
+ end
+
+ # Returns dataset for its immediate children
+ def children
+ dataset.nested_scope.filter(self.class.qualified_parent_column => self.id)
+ end
+
+ # Returns dataset for all parents
+ def ancestors
+ without_self(self_and_ancestors)
+ end
+
+ # Returns dataset for all children of the parent, except self
+ def siblings
+ without_self(self_and_siblings)
+ end
+
+ # Returns dataset for all of its children and nested children
+ def descendants
+ without_self(self_and_descendants)
+ end
+
+ # Returns dataset for all of its nested children which do not have children
+ def leaves
+ descendants.filter(self.class.qualified_right_column - self.class.qualified_left_column => 1)
+ end
+
+ def is_descendant_of?(other)
+ other.left < self.left && self.left < other.right && same_scope?(other)
+ end
+
+ def is_or_is_descendant_of?(other)
+ other.left <= self.left && self.left < other.right && same_scope?(other)
+ end
+
+ def is_ancestor_of?(other)
+ self.left < other.left && other.left < self.right && same_scope?(other)
+ end
+
+ def is_or_is_ancestor_of?(other)
+ self.left <= other.left && other.left < self.right && same_scope?(other)
+ end
+
+ # Check if other model is in the same scope
+ def same_scope?(other)
+ Array(nil).all? do |attr|
+ self.send(attr) == other.send(attr)
+ end
+ end
+
+ # Find the first sibling to the left
+ def left_sibling
+ siblings.filter(self.class.qualified_left_column < left).order(self.class.qualified_left_column.desc).first
+ end
+
+ # Find the first sibling to the right
+ def right_sibling
+ siblings.filter(self.class.qualified_left_column > left).first
+ end
+
+ def to_text
+ self_and_descendants.map do |node|
+ "#{'*'*(node.level+1)} #{node.class.inspect} (#{node.parent_id.inspect}, #{node.left}, #{node.right})"
+ end.join("\n")
+ end
+
+ protected
+ # on creation, set automatically lft and rgt to the end of the tree
+ def set_default_left_and_right
+ maxright = dataset.nested_scopemax(self.class.qualified_right_column) || 0
+ # adds the new node to the right of all existing nodes
+ self.left = maxright + 1
+ self.right = maxright + 2
+ end
+ end
+ end
+ end
+end
237 spec/nested_set_spec.rb
@@ -0,0 +1,237 @@
+require File.join(File.dirname(__FILE__), "spec_helper")
+
+describe "Sequel Nested Set Class" do
+ before(:all) do
+ @root = Client.filter(:name => 'Top Level').first
+ @node1 = Client.filter(:name => 'Child 1').first
+ @node2 = Client.filter(:name => 'Child 2').first
+ @node2_1 = Client.filter(:name => 'Child 2.1').first
+ @node3 = Client.filter(:name => 'Child 3').first
+ @root2 = Client.filter(:name => 'Top Level 2').first
+ end
+
+ it "should have nested_set_options" do
+ Client.should respond_to :nested_set_options
+ end
+
+
+ it "should have default options :left_column, :right_column, :parent_column, :dependent and :scope" do
+ Client.nested_set_options[:left_column].should == :lft
+ Client.nested_set_options[:right_column].should == :rgt
+ Client.nested_set_options[:parent_column].should == :parent_id
+ Client.nested_set_options[:dependent].should == :delete_all
+ Client.nested_set_options[:scope].should be_nil
+ end
+
+ it "should have qualified column methods" do
+ Client.qualified_parent_column.should == :clients__parent_id
+ Client.qualified_left_column.should == :clients__lft
+ Client.qualified_right_column.should == :clients__rgt
+ end
+
+ it "should have roots that contains all root nodes" do
+ roots = Client.roots.all
+ roots.should == Client.filter(:parent_id => nil).all
+ roots.should == [@root, @root2]
+ end
+
+ it "should have root that will be root? => true" do
+ Client.roots.first.root?.should be_true
+ end
+
+ it "should have all leaves" do
+ leaves = Client.leaves.all
+ leaves.should == Client.nested_scope.filter(:rgt - :lft => 1).all
+ leaves.should == [@node1, @node2_1, @node3, @root2]
+ end
+end
+
+describe "Sequel Nested Set Instance" do
+
+ before(:all) do
+ @root = Client.filter(:name => 'Top Level').first
+ @node1 = Client.filter(:name => 'Child 1').first
+ @node2 = Client.filter(:name => 'Child 2').first
+ @node2_1 = Client.filter(:name => 'Child 2.1').first
+ @node3 = Client.filter(:name => 'Child 3').first
+ @root2 = Client.filter(:name => 'Top Level 2').first
+ end
+
+ it "should have nested_set_options" do
+ @root.class.should respond_to :nested_set_options
+ end
+
+ it "should have parent, left, right getter based on nested set config" do
+ node = Client.new(:parent_id => nil, :lft => 1, :rgt => 2)
+ node.left.should == node[node.class.nested_set_options[:left_column]]
+ node.right.should == node[node.class.nested_set_options[:right_column]]
+ node.parent_id.should == node[node.class.nested_set_options[:parent_column]]
+ end
+
+ it "should have parent, left, right setter based on nested set config" do
+ node = Client.new
+ node.send(:left=, 1)
+ node.send(:right=, 2)
+ node.send(:parent_id=, 69)
+ node.left.should == node[node.class.nested_set_options[:left_column]]
+ node.right.should == node[node.class.nested_set_options[:right_column]]
+ node.parent_id.should == node[node.class.nested_set_options[:parent_column]]
+ end
+
+ it "should parent, left and right setters be protected methods" do
+ Client.new.protected_methods.include?("left=").should be_true
+ Client.new.protected_methods.include?("right=").should be_true
+ Client.new.protected_methods.include?("parent_id=").should be_true
+ end
+
+ it "Client.new with {:left => 1, :right => 2, :parent_id => nil} should raise NoMethodError exception" do
+
+ end
+
+ it "should have nested_set_options equal to Model.nested_set_options" do
+ @root.nested_set_options.should == Client.nested_set_options
+ end
+
+ it "should have nodes that have common root" do
+ @node1.root.should == @root
+ end
+
+ it "should have nodes that have their parent" do
+ @node2_1.parent.should == @node2
+ end
+
+ it "should have leaf that will be true leaf?" do
+ @root.leaf?.should_not be_true
+ @node2_1.leaf?.should be_true
+ end
+
+ it "should have child that will be true child?" do
+ @root.child?.should_not be_true
+ @node2_1.child?.should be_true
+ end
+
+ it "should have <=> method" do
+ @root.should respond_to :<=>
+ end
+
+ it "Should order by left column" do
+ (@node1 <=> @node2).should == -1
+ end
+
+ it "should have level of node" do
+ @root.level.should == 0
+ @node1.level.should == 1
+ @node2.level.should == 1
+ @node2_1.level.should == 2
+ end
+
+ it "should have parent relation" do
+ @node2_1.parent.should == @node2
+ end
+
+ it "should have self_and_sibling that have self node and all its siblings" do
+ @root.self_and_siblings.all.should == [@root, @root2]
+ @node1.self_and_siblings.all.should == [@node1, @node2, @node3]
+ end
+
+ it "should have siblings of node withot itself" do
+ @root.siblings.all.should == [@root2]
+ @node1.siblings.all.should == [@node2, @node3]
+ end
+
+ it "should have self_and_ancestors that have self node and all its ancestors" do
+ @root.self_and_ancestors.all.should == [@root]
+ @node1.self_and_ancestors.all.should == [@root, @node1]
+ end
+
+ it "should have ancestors of node withot itself" do
+ @root.ancestors.all.should == []
+ @node1.ancestors.all.should == [@root]
+ end
+
+ it "should have self_and_descendants that have self node and all its descendents" do
+ @root.self_and_descendants.all.should == [@root, @node1, @node2, @node2_1, @node3]
+ @node2.self_and_descendants.all.should == [@node2, @node2_1]
+ @node2_1.self_and_descendants.all.should == [@node2_1]
+ end
+
+ it "should have descendents that are children and nested children wihout itself" do
+ @root.descendants.all.should == [@node1, @node2, @node2_1, @node3]
+ @node2.descendants.all.should == [@node2_1]
+ @node2_1.descendants.all.should == []
+ end
+
+ it "should have children that returns set of only node immediate children" do
+ @root.children.all.should == [@node1, @node2, @node3]
+ @node2.children.all.should == [@node2_1]
+ @node2_1.children.all.should == []
+ end
+
+ it "should have leaves that are set of all of node nested children which do not have children" do
+ @root.leaves.all.should == [@node1, @node2_1, @node3]
+ @node2.leaves.all.should == [@node2_1]
+ @node2_1.leaves.all.should == []
+ end
+
+ it "should be able to get left sibling" do
+ @node2.left_sibling.should == @node1
+ @node3.left_sibling.should == @node2
+ @node1.left_sibling.should be_nil
+ end
+
+ it "should be able to get proper right sibling" do
+ @node1.right_sibling.should == @node2
+ @node2.right_sibling.should == @node3
+ @node3.right_sibling.should be_nil
+ end
+
+ it "should @root and @node be in same scope" do
+ @root.same_scope?(@node).should be_true
+ end
+
+ it "should @root and @root_in_other_scope be in different scope" do
+
+ end
+
+ it "should have node_x.is_or_is_descendant_of?(node_y) that will return proper boolean value" do
+ @node1.is_or_is_descendant_of?(@root).should be_true
+ @node2_1.is_or_is_descendant_of?(@root).should be_true
+ @node2_1.is_or_is_descendant_of?(@node2).should be_true
+ @node2.is_or_is_descendant_of?(@node2_1).should be_false
+ @node2.is_or_is_descendant_of?(@node1).should be_false
+ @node1.is_or_is_descendant_of?(@node1).should be_true
+ end
+
+ it "should have node_x.is_ancestor_of?(node_y) that will return proper boolean value" do
+ @node1.is_descendant_of?(@root).should be_true
+ @node2_1.is_descendant_of?(@root).should be_true
+ @node2_1.is_descendant_of?(@node2).should be_true
+ @node2.is_descendant_of?(@node2_1).should be_false
+ @node2.is_descendant_of?(@node1).should be_false
+ @node1.is_descendant_of?(@node1).should be_false
+ end
+
+ it "should have node_x.is_ancestor_of?(node_y) that will return proper boolean value" do
+ @root.is_ancestor_of?(@node1).should be_true
+ @root.is_ancestor_of?(@node2_1).should be_true
+ @node2.is_ancestor_of?(@node2_1).should be_true
+ @node2_1.is_ancestor_of?(@node2).should be_false
+ @node1.is_ancestor_of?(@node2).should be_false
+ @node1.is_ancestor_of?(@node1).should be_false
+ end
+
+ it "should have node_x.is_or_is_ancestor_of?(node_y) that will return proper boolean value" do
+ @root.is_or_is_ancestor_of?(@node1).should be_true
+ @root.is_or_is_ancestor_of?(@node2_1).should be_true
+ @node2.is_or_is_ancestor_of?(@node2_1).should be_true
+ @node2_1.is_or_is_ancestor_of?(@node2).should be_false
+ @node1.is_or_is_ancestor_of?(@node2).should be_false
+ @node1.is_or_is_ancestor_of?(@node1).should be_true
+ end
+
+ it "should have to_text method" do
+ @root.to_text.should == "* Client (nil, 1, 10)\n** Client (1, 2, 3)\n** Client (1, 4, 7)\n*** Client (3, 5, 6)\n** Client (1, 8, 9)"
+ end
+
+end
+
1  spec/rcov.opts
@@ -0,0 +1 @@
+-x gems,spec
6 spec/spec.opts
@@ -0,0 +1,6 @@
+--colour
+--loadby
+mtime
+--backtrace
+--format
+specdoc
27 spec/spec_helper.rb
@@ -0,0 +1,27 @@
+require 'rubygems'
+require 'spec'
+require 'sequel'
+
+require File.dirname(__FILE__) + '/../lib/sequel_nested_set'
+
+DB = Sequel.sqlite # memory database
+
+DB.create_table :clients do # Create a new table
+ primary_key :id
+ column :name, :text
+ column :parent_id, :integer
+ column :lft, :integer
+ column :rgt, :integer
+end
+
+class Client < Sequel::Model
+ is :nested_set
+end
+
+DB[:clients] << {"name"=>"Top Level 2", "lft"=>11, "id"=>6, "rgt"=>12}
+DB[:clients] << {"name"=>"Child 2.1", "lft"=>5, "id"=>4, "parent_id"=>3, "rgt"=>6}
+DB[:clients] << {"name"=>"Child 1", "lft"=>2, "id"=>2, "parent_id"=>1, "rgt"=>3}
+DB[:clients] << {"name"=>"Top Level", "lft"=>1, "id"=>1, "rgt"=>10}
+DB[:clients] << {"name"=>"Child 2", "lft"=>4, "id"=>3, "parent_id"=>1, "rgt"=>7}
+DB[:clients] << {"name"=>"Child 3", "lft"=>8, "id"=>5, "parent_id"=>1, "rgt"=>9}
+
Please sign in to comment.
Something went wrong with that request. Please try again.