Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ tmp/
.DS_Store
.yardoc/
.rvmrc
Gemfile.lock
*.lock
35 changes: 31 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,42 @@
language: ruby
rvm:
- 2.1.2
- 1.9.3
- 2.0.0
- 2.1.2
- ruby-head
- rbx-2

gemfile:
- ci/Gemfile.rails-4.1.x
- ci/Gemfile.rails-4.0.x
- ci/Gemfile.rails-3.2.x
- gemfiles/activerecord_3.2.gemfile
- gemfiles/activerecord_4.0.gemfile
- gemfiles/activerecord_4.1.gemfile
- gemfiles/activerecord_edge.gemfile

env:
- DB=sqlite
- DB=mysql
- DB=postgresql

script: WITH_ADVISORY_LOCK_PREFIX=$TRAVIS_JOB_ID bundle exec rake --trace all_spec_flavors

matrix:
fast_finish: true
allow_failures:
- gemfile: gemfiles/activerecord_edge.gemfile
- rvm: rbx-2
- rvm: ruby-head
exclude:
- rvm: 1.9.3
gemfile: gemfiles/activerecord_4.0.gemfile
- rvm: 1.9.3
gemfile: gemfiles/activerecord_4.1.gemfile
- rvm: 1.9.3
gemfile: gemfiles/activerecord_edge.gemfile
- rvm: rbx-2
gemfile: gemfiles/activerecord_3.2.gemfile
- rvm: ruby-head
gemfile: gemfiles/activerecord_3.2.gemfile
- rvm: ruby-head
gemfile: gemfiles/activerecord_4.0.gemfile
- rvm: ruby-head
gemfile: gemfiles/activerecord_4.1.gemfile
21 changes: 21 additions & 0 deletions Appraisals
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
appraise "activerecord-3.2" do
gem 'activerecord', '~> 3.2'
gem 'strong_parameters'
gem 'foreigner', :git => 'https://github.com/mceachen/foreigner.git'
end

appraise "activerecord-4.0" do
gem "activerecord", "~> 4.0"
gem 'foreigner', :git => 'https://github.com/mceachen/foreigner.git'
end

appraise "activerecord-4.1" do
gem "activerecord", "~> 4.1"
gem 'foreigner', :git => 'https://github.com/mceachen/foreigner.git'
end

appraise "activerecord-edge" do
gem "activerecord", github: "rails/rails"
gem 'arel', github: 'rails/arel'
gem 'foreigner', :git => 'https://github.com/mceachen/foreigner.git'
end
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
source 'https://rubygems.org'
gem 'foreigner', :git => 'https://github.com/mceachen/foreigner.git'
gemspec
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ closure_tree has some great features:
* __Best-in-class mutation performance__:
* 2 SQL INSERTs on node creation
* 3 SQL INSERT/UPDATEs on node reparenting
* __Support for Rails 3.1, 3.2, 4.0, and 4.1__
* __Support for Rails 3.2, 4.0, and 4.1__
* Support for reparenting children (and all their descendants)
* Support for [concurrency](#concurrency) (using [with_advisory_lock](https://github.com/mceachen/with_advisory_lock))
* Support for polymorphism [STI](#sti) within the hierarchy
Expand Down Expand Up @@ -243,7 +243,7 @@ Just for kicks, this is the test tree I used for proving that preordered tree tr
When you include ```acts_as_tree``` in your model, you can provide a hash to override the following defaults:

* ```:parent_column_name``` to override the column name of the parent foreign key in the model's table. This defaults to "parent_id".
* ```:hierarchy_table_name``` to override the hierarchy class name. This defaults to the singular name of the model + "Hierarchy", like ```TagHierarchy```.
* ```:hierarchy_class_name``` to override the hierarchy class name. This defaults to the singular name of the model + "Hierarchy", like ```TagHierarchy```.
* ```:hierarchy_table_name``` to override the hierarchy table name. This defaults to the singular name of the model + "_hierarchies", like ```tag_hierarchies```.
* ```:dependent``` determines what happens when a node is destroyed. Defaults to ```nullify```.
* ```:nullify``` will simply set the parent column to null. Each child node will be considered a "root" node. This is the default.
Expand Down Expand Up @@ -488,9 +488,10 @@ end

Closure tree is [tested under every valid combination](http://travis-ci.org/#!/mceachen/closure_tree) of

* Ruby 1.9.3 and Ruby 2.1.2
* The latest Rails 3.2, 4.0, and 4.1 branches, and
* MySQL and PostgreSQL. SQLite works in a single-threaded environment.
* Ruby 1.9.3 , 2.0.0 and 2.1.2
* Rubinius 2.2.6
* The latest Rails 3.2, 4.0, 4.1 and master branches
* Concurrency tests for MySQL and PostgreSQL. SQLite works in a single-threaded environment.

Assuming you're using [rbenv](https://github.com/sstephenson/rbenv), you can use ```tests.sh``` to
run the test matrix locally.
Expand Down
6 changes: 0 additions & 6 deletions ci/Gemfile.rails-3.2.x

This file was deleted.

6 changes: 0 additions & 6 deletions ci/Gemfile.rails-4.0.x

This file was deleted.

6 changes: 0 additions & 6 deletions ci/Gemfile.rails-4.1.x

This file was deleted.

23 changes: 11 additions & 12 deletions closure_tree.gemspec
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
$:.push File.expand_path("../lib", __FILE__)
require "closure_tree/version"
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
require 'closure_tree/version'

Gem::Specification.new do |gem|
gem.name = "closure_tree"
gem.name = 'closure_tree'
gem.version = ::ClosureTree::VERSION
gem.authors = ["Matthew McEachen"]
gem.email = ["matthew-github@mceachen.org"]
gem.homepage = "http://mceachen.github.io/closure_tree/"
gem.authors = ['Matthew McEachen']
gem.email = ['matthew-github@mceachen.org']
gem.homepage = 'http://mceachen.github.io/closure_tree/'

gem.summary = %q{Easily and efficiently make your ActiveRecord model support hierarchies}
gem.summary = %q(Easily and efficiently make your ActiveRecord model support hierarchies)
gem.description = gem.summary
gem.license = 'MIT'

gem.files = Dir["lib/**/*"] + ["MIT-LICENSE", "Rakefile", "README.md"]
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
gem.test_files = gem.files.grep(%r{^spec/})

gem.add_runtime_dependency 'activerecord', '>= 3.0.0'
gem.add_runtime_dependency 'activerecord', '>= 3.2.0'
gem.add_runtime_dependency 'with_advisory_lock', '>= 0.0.9' # <- to prevent duplicate roots

gem.add_development_dependency 'rake'
gem.add_development_dependency 'yard'
gem.add_development_dependency 'rspec'
gem.add_development_dependency 'rspec-instafail'
# gem.add_development_dependency 'rspec', '~> 2.13.0' # <- Rubymine can't handle 2.14.x
gem.add_development_dependency 'rspec-rails' # FIXME: for rspec-rails and rspec fixture support
gem.add_development_dependency 'mysql2'
gem.add_development_dependency 'pg'
gem.add_development_dependency 'sqlite3'
gem.add_development_dependency 'uuidtools'
gem.add_development_dependency 'database_cleaner'
gem.add_development_dependency 'foreigner'
gem.add_development_dependency 'appraisal', '~> 1.0'

# gem.add_development_dependency 'ruby-prof' # <- don't need this normally.
# TODO: gem 'activerecord-jdbcsqlite3-adapter', :platform => :jruby
Expand Down
9 changes: 9 additions & 0 deletions gemfiles/activerecord_3.2.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# This file was generated by Appraisal

source "https://rubygems.org"

gem "foreigner", :git => "https://github.com/mceachen/foreigner.git"
gem "activerecord", "~> 3.2"
gem "strong_parameters"

gemspec :path => "../"
8 changes: 8 additions & 0 deletions gemfiles/activerecord_4.0.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This file was generated by Appraisal

source "https://rubygems.org"

gem "foreigner", :git => "https://github.com/mceachen/foreigner.git"
gem "activerecord", "~> 4.0"

gemspec :path => "../"
8 changes: 8 additions & 0 deletions gemfiles/activerecord_4.1.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This file was generated by Appraisal

source "https://rubygems.org"

gem "foreigner", :git => "https://github.com/mceachen/foreigner.git"
gem "activerecord", "~> 4.1"

gemspec :path => "../"
9 changes: 9 additions & 0 deletions gemfiles/activerecord_edge.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# This file was generated by Appraisal

source "https://rubygems.org"

gem "foreigner", :git => "https://github.com/mceachen/foreigner.git"
gem "activerecord", :github => "rails/rails"
gem "arel", :github => "rails/arel"

gemspec :path => "../"
4 changes: 1 addition & 3 deletions lib/closure_tree.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
require 'active_support'
require 'active_record'
require 'closure_tree/acts_as_tree'

ActiveSupport.on_load :active_record do
require 'closure_tree/acts_as_tree'

ActiveRecord::Base.send :extend, ClosureTree::ActsAsTree
end
5 changes: 5 additions & 0 deletions lib/closure_tree/acts_as_tree.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
module ClosureTree
module ActsAsTree
def acts_as_tree(options = {})
options.assert_valid_keys(:name, :dependent, :order,
:parent_column_name, :name_column,
:hierarchy_class_name, :hierarchy_table_name,
:with_advisory_lock, :base_class)

class_attribute :_ct
self._ct = ClosureTree::Support.new(self, options)

Expand Down
4 changes: 2 additions & 2 deletions lib/closure_tree/digraphs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ def to_digraph_label
module ClassMethods
# Renders the given scope as a DOT digraph, suitable for rendering by Graphviz
def to_dot_digraph(tree_scope)
id_to_instance = tree_scope.inject({}) { |h, ea| h[ea.id] = ea; h }
id_to_instance = tree_scope.reduce({}) { |h, ea| h[ea.id] = ea; h }
output = StringIO.new
output << "digraph G {\n"
tree_scope.each do |ea|
if id_to_instance.has_key? ea._ct_parent_id
if id_to_instance.key? ea._ct_parent_id
output << " #{ea._ct_parent_id} -> #{ea._ct_id}\n"
end
output << " #{ea._ct_id} [label=\"#{ea.to_digraph_label}\"]\n"
Expand Down
43 changes: 22 additions & 21 deletions lib/closure_tree/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,40 @@ module Model

included do
belongs_to :parent,
:class_name => _ct.model_class.to_s,
:foreign_key => _ct.parent_column_name,
:inverse_of => :children
class_name: _ct.model_class.to_s,
foreign_key: _ct.parent_column_name,
inverse_of: :children

# TODO, remove when activerecord 3.2 support is dropped
attr_accessible :parent if _ct.use_attr_accessible?

order_by_generations = "#{_ct.quoted_hierarchy_table_name}.generations asc"

has_many :children, *_ct.has_many_with_order_option(
:class_name => _ct.model_class.to_s,
:foreign_key => _ct.parent_column_name,
:dependent => _ct.options[:dependent],
:inverse_of => :parent)
class_name: _ct.model_class.to_s,
foreign_key: _ct.parent_column_name,
dependent: _ct.options[:dependent],
inverse_of: :parent)

has_many :ancestor_hierarchies, *_ct.has_many_without_order_option(
:class_name => _ct.hierarchy_class_name,
:foreign_key => "descendant_id",
:order => order_by_generations)
class_name: _ct.hierarchy_class_name,
foreign_key: 'descendant_id',
order: order_by_generations)

has_many :self_and_ancestors, *_ct.has_many_without_order_option(
:through => :ancestor_hierarchies,
:source => :ancestor,
:order => order_by_generations)
through: :ancestor_hierarchies,
source: :ancestor,
order: order_by_generations)

has_many :descendant_hierarchies, *_ct.has_many_without_order_option(
:class_name => _ct.hierarchy_class_name,
:foreign_key => "ancestor_id",
:order => order_by_generations)
class_name: _ct.hierarchy_class_name,
foreign_key: 'ancestor_id',
order: order_by_generations)

has_many :self_and_descendants, *_ct.has_many_with_order_option(
:through => :descendant_hierarchies,
:source => :descendant,
:order => order_by_generations)
through: :descendant_hierarchies,
source: :descendant,
order: order_by_generations)
end

# Delegate to the Support instance on the class:
Expand Down Expand Up @@ -76,7 +77,7 @@ def depth
ancestors.size
end

alias :level :depth
alias_method :level, :depth

def ancestors
without_self(self_and_ancestors)
Expand All @@ -94,7 +95,7 @@ def self_and_ancestors_ids
# to the +name_column+.
# (so child.ancestry_path == +%w{grandparent parent child}+
def ancestry_path(to_s_column = _ct.name_column)
self_and_ancestors.reverse.collect { |n| n.send to_s_column.to_sym }
self_and_ancestors.reverse.map { |n| n.send to_s_column.to_sym }
end

def child_ids
Expand Down
2 changes: 1 addition & 1 deletion spec/db/database.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ common: &common

sqlite:
adapter: <%= "jdbc" if defined? JRUBY_VERSION %>sqlite3
database: spec/sqlite3.db
database: ':memory:'

postgresql:
<<: *common
Expand Down
15 changes: 15 additions & 0 deletions spec/hierarchy_maintenance_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require 'spec_helper'

describe ClosureTree::HierarchyMaintenance do
describe '.rebuild!' do
it 'rebuild tree' do
20.times do |counter|
Metal.create(:value => "Nitro-#{counter}", parent: Metal.all.sample)
end
hierarchy_count = MetalHierarchy.count
MetalHierarchy.delete_all
Metal.rebuild!
expect(MetalHierarchy.count).to eq(hierarchy_count)
end
end
end
12 changes: 3 additions & 9 deletions spec/label_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ def create_preorder_tree(suffix = "", &block)
b = c.parent
a = c.root
a.destroy
Label.exists?(a).should be_false
Label.exists?(b).should be_false
Label.exists?(c).should be_false
Label.exists?(id: [a.id,b.id,c.id]).should be_false
end
end

Expand Down Expand Up @@ -354,9 +352,7 @@ def children_name_and_order
c = Label.new(name: 'c')
b.add_child c
a.destroy
Label.exists?(a).should be_false
Label.exists?(b).should be_false
Label.exists?(c).should be_false
Label.exists?(id: [a.id,b.id,c.id]).should be_false
end

it "properly destroys descendents created with <<" do
Expand All @@ -366,9 +362,7 @@ def children_name_and_order
c = Label.new(name: 'c')
b.children << c
a.destroy
Label.exists?(a).should be_false
Label.exists?(b).should be_false
Label.exists?(c).should be_false
Label.exists?(id: [a.id,b.id,c.id]).should be_false
end
end

Expand Down
9 changes: 9 additions & 0 deletions spec/model_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require 'spec_helper'

describe ClosureTree::Model do
describe '#_ct' do
it 'should delegate to the Support instance on the class' do
expect(Tag.new._ct).to eq(Tag._ct)
end
end
end
Loading