closuretree / closure_tree Goto Github PK
View Code? Open in Web Editor NEWEasily and efficiently make your ActiveRecord models support hierarchies
Home Page: https://closuretree.github.io/closure_tree/
License: MIT License
Easily and efficiently make your ActiveRecord models support hierarchies
Home Page: https://closuretree.github.io/closure_tree/
License: MIT License
Deterministic ordering seems to use the default parent_id instead of the newly defined parent_column_name
calling siblings_before gives:
ActiveRecord::StatementInvalid: PG::Error: ERROR: column algo_elements.parent_id does not exist
What is the best way to get all the nodes with a given depth. Currently, I do Node.all.select {|n| n.depth == 3}
. This generates one query for each iteration which isn't very optimized.
Hey there,
i'm currently working on a cms where i use closure tree for managing the web pages.
Now i want to do a navigation so i wanted to use Page.hash_tree.
@Navigation = Page.hash_tree(:limit_depth => 2) creates this error "undefined method []=' for nil:NilClass" in closure_tree (3.6.4) lib/closure_tree/acts_as_tree.rb:113:in
block in hash_tree'
can someone help me? would be great ;)
kind regards,
patrick
Hi, I was working with Closure-tree gem and I noticed this
1.9.3p194 :019 > m.self_and_ancestors
=> [# CuisineType id: 27, name: "m", parent_id: 22 ]
so we have "m" with a parent... not in the list
1.9.3p194 :020 > m.parent
=> # CuisineType id: 22, name: "e", parent_id: nil
For me, the "logic" behaviour should be smth like
console > m.self_and_ancestors
=> [# ...m..., #...e...]
At least I've resolved it creating my own function (recursive simple one). I've posted it just for "help the developers if it were really a bug"
Apart from that the gem is sooo nice ^^
Manu
Hello, I'm having a problem using Deterministic ordering with Polymorphic hierarchies with STI.
In my project I'm moving elements of different types. When I move an element in between it's siblings the resulting SQL statement updates the sort_order column of only elements of the same type.
For example:
If I prepend Element A to C then the resulting SQL statement is:
UPDATE elements
SET sort_order
= sort_order
- 1 WHERE elements
.type
IN ('Photo') AND (parent_id
= ## AND sort_order
<= 2))
So, only elements type "Photo" have their "sort_order" value updated resulting in:
Element C remains with a "sort_order" value of 3 while element A and B now have a "sort_order" value of 2.
How can I override this behavior and have that moving an element updates the order of all siblings before or after it?
Hello. Is there any way to render nested resources as JSON? I use closure_tree to implement comments with replies and the method "hash_tree" is very helpful when rendering all this heirarchy in plain HTML. But how can I render the same stuff in JSON, for example like (or anything like this - i just basically need some mechanism to mark which comments are roots, which are children and to which roots they related, in order to render it with AngularJS):
[
{
body: "Text of the comment"
name: "Username"
comment:
{
body: "Nested comment"
name: "User who posted nested comment",
comment:
{
body: "2 levels nested comment"
name: "Username"
comment:
{
....
}
}
}
}
]
Any help will be much appreciated.
Right now it just executes the :hash_tree method for each root. So it's potentially a O(N) query. It can be done in one swipe, preloading by generation including the roots:
scope = Category.select("categories.*, category_hierarchies.*").
joins("left join category_hierarchies on ancestor_id = categories.id").
order("parent_id IS NOT NULL, generations ASC, name")
Here's part of my exception_notification email:
A NoMethodError occurred in product_groups#update:
undefined method `ActiveRecordError' for ActiveRecord:Module
closure_tree (3.0.0) lib/closure_tree/acts_as_tree.rb:172:in `acts_as_tree_before_save'
Rails (and ActiveRecord) is 3.2.1, closure_tree 3.0.0
I believe this can be a very particular feature, but do you have any thoughts on making tag.descendants
return its results with a preorder sequence? What would you recommend?
grandparent = Tag.find_or_create_by_path(["grandparent", "parentA", "childA"]).parent.parent
Tag.find_or_create_by_path(["grandparent", "parentB", "childB"])
grandparent.descendants
=> ["parentA", "parentB", "childA", "childB"] # current traversal sequence
=> ["parentA", "childA", "parentB", "childB"] # preorder traversal sequence
When calling create on the children's type is not passed to the newly created child.
Piggybacking on the example provided
class Tag < ActiveRecord::Base
acts_as_tree
end
class WhenTag < Tag ; end
class WhereTag < Tag ; end
class WhatTag < Tag ; end
now = WhenTag.create name: 'Now'
now.children
=> []
right_now = now.children.create name: 'Right now'
right_now.type
=> nil
I was expecting the type to be kept. I don't know if I'm asking too much from closure_tree and this is just not supposed to be kept. Meanwhile I'll set it up manually or create the child using WhenTag.create and assign it to its parent later.
Hey, I'm just opening this to thank you publicly and to tell you how much I appreciate the work you've been doing on this project. Keep it rolling, you rock!
p.s please close this once you read it :)
Hello @mceachen,
Just uncovered an interesting issue today and wanted to discuss how it should be handled. I was seeing errors from active record where my hierarchical model table didn't exist during a rake run.
Turns out that there was an observer on that same model (the one that has acts_as_tree order: "name"
defined). That observer loads the model and the order
option then attempts to look up column information on the table.
This isn't a problem unless you're in an environment like Travis CI (or any environment that starts without a database defined).
No database to start
rake db:setup
acts_as_tree order: "field_name"
needs to include ClosureTree::DeterministicNumericOrdering if order_is_numeric
order_is_numeric
calls ct_class.columns_hash[order_option]
columns_hash
access the ActiveRecord metadata for the tableObservers are scheduled for removal in Rails 4 but I've seen you mention the desire to maintain support for older Rails versions for this project.
I am happy to help fix this issue, just not sure how or if you'd like it fixed. My initial reaction was to change order_is_numeric
as follows (although I hate &&
in unless
):
def order_is_numeric
return false unless order_option && self.table_exists?
c = ct_class.columns_hash[order_option]
c && c.type == :integer
end
If you'd like a pull request I can put one together and get this moving.
Thanks for a great project!
I was wondering if you could provide some steps for how to set up a testing environment for this gem? I had a quick go but came unstuck pretty fast. It looks like you use rbenv for your larger test suite but is there a way to get a basic test setup going on say one database, ruby and rails version?
Hey there. Lovely library you have here.
I have a class like:
class Field
acts_as_tree order: 'sort_order', dependent: :destroy, name_column: :body
end
I am trying to build a tree and then save all at once, e.g.
a = Field.new(body: "a")
b = Field.new(body: "b")
c = Field.new(body: "c")
a.children << b
b.children << c
a.save
I then get this error:
1.9.3p194 :009 > a.save
(0.1ms) BEGIN
SQL (8.2ms) INSERT INTO "fields" ("body", "created_at", "extra", "form_id", "parent_id", "required", "sort_order", "type", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING "id" [["body", "a"], ["created_at", Thu, 26 Jul 2012 14:11:05 EDT -04:00], ["extra", nil], ["form_id", 31], ["parent_id", nil], ["required", false], ["sort_order", nil], ["type", "Field::MultipleChoice"], ["updated_at", Thu, 26 Jul 2012 14:11:05 EDT -04:00]]
Field Load (0.8ms) SELECT "fields".* FROM "fields" WHERE "fields"."id" = 255 ORDER BY sort_order ASC LIMIT 1
FieldHierarchy Load (0.5ms) SELECT "field_hierarchies".* FROM "field_hierarchies" WHERE "field_hierarchies"."descendant_id" = 255 ORDER BY "field_hierarchies".generations asc
SQL (0.6ms) INSERT INTO "fields" ("body", "created_at", "extra", "form_id", "parent_id", "required", "sort_order", "type", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING "id" [["body", "b"], ["created_at", Thu, 26 Jul 2012 14:11:05 EDT -04:00], ["extra", nil], ["form_id", 31], ["parent_id", 255], ["required", false], ["sort_order", nil], ["type", "Field::MultipleChoice"], ["updated_at", Thu, 26 Jul 2012 14:11:05 EDT -04:00]]
Field Load (0.4ms) SELECT "fields".* FROM "fields" WHERE "fields"."id" = 256 ORDER BY sort_order ASC LIMIT 1
FieldHierarchy Load (0.3ms) SELECT "field_hierarchies".* FROM "field_hierarchies" WHERE "field_hierarchies"."descendant_id" = 256 ORDER BY "field_hierarchies".generations asc
SQL (1.2ms) INSERT INTO "fields" ("body", "created_at", "extra", "form_id", "parent_id", "required", "sort_order", "type", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING "id" [["body", "c"], ["created_at", Thu, 26 Jul 2012 14:11:05 EDT -04:00], ["extra", nil], ["form_id", 31], ["parent_id", 256], ["required", false], ["sort_order", nil], ["type", "Field::MultipleChoice"], ["updated_at", Thu, 26 Jul 2012 14:11:05 EDT -04:00]]
SQL (0.4ms) INSERT INTO "field_hierarchies" ("ancestor_id", "descendant_id", "generations") VALUES ($1, $2, $3) [["ancestor_id", 257], ["descendant_id", 257], ["generations", 0]]
(0.4ms) INSERT INTO "field_hierarchies"
(ancestor_id, descendant_id, generations)
SELECT x.ancestor_id, 257, x.generations + 1
FROM "field_hierarchies" x
WHERE x.descendant_id = 256
Field Load (0.3ms) SELECT "fields".* FROM "fields" WHERE "fields"."parent_id" = 257 ORDER BY sort_order ASC, sort_order
SQL (0.1ms) INSERT INTO "field_hierarchies" ("ancestor_id", "descendant_id", "generations") VALUES ($1, $2, $3) [["ancestor_id", 256], ["descendant_id", 256], ["generations", 0]]
(0.2ms) INSERT INTO "field_hierarchies"
(ancestor_id, descendant_id, generations)
SELECT x.ancestor_id, 256, x.generations + 1
FROM "field_hierarchies" x
WHERE x.descendant_id = 255
SQL (0.5ms) INSERT INTO "field_hierarchies" ("ancestor_id", "descendant_id", "generations") VALUES ($1, $2, $3) [["ancestor_id", 257], ["descendant_id", 257], ["generations", 0]]
(0.2ms) ROLLBACK
ActiveRecord::RecordNotUnique: PG::Error: ERROR: duplicate key value violates unique constraint "index_field_hierarchies_on_ancestor_id_and_descendant_id"
DETAIL: Key (ancestor_id, descendant_id)=(257, 257) already exists.
: INSERT INTO "field_hierarchies" ("ancestor_id", "descendant_id", "generations") VALUES ($1, $2, $3)
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/connection_adapters/postgresql_adapter.rb:1171:in `get_last_result'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/connection_adapters/postgresql_adapter.rb:1171:in `exec_cache'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/connection_adapters/postgresql_adapter.rb:665:in `block in exec_query'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/connection_adapters/abstract_adapter.rb:280:in `block in log'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activesupport-3.2.7.rc1/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/connection_adapters/abstract_adapter.rb:275:in `log'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/connection_adapters/postgresql_adapter.rb:663:in `exec_query'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/connection_adapters/abstract/database_statements.rb:63:in `exec_insert'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/connection_adapters/abstract/database_statements.rb:90:in `insert'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/connection_adapters/abstract/query_cache.rb:14:in `insert'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/relation.rb:66:in `insert'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/persistence.rb:367:in `create'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/timestamp.rb:57:in `create'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/callbacks.rb:268:in `block in create'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activesupport-3.2.7.rc1/lib/active_support/callbacks.rb:403:in `_run__1815317462497579710__create__1621482155338212246__callbacks'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activesupport-3.2.7.rc1/lib/active_support/callbacks.rb:405:in `__run_callback'
... 62 levels...
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/validations.rb:50:in `save'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/attribute_methods/dirty.rb:22:in `save'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/transactions.rb:241:in `block (2 levels) in save'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/transactions.rb:295:in `block in with_transaction_returning_status'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/connection_adapters/abstract/database_statements.rb:192:in `transaction'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/transactions.rb:208:in `transaction'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/transactions.rb:293:in `with_transaction_returning_status'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/transactions.rb:241:in `block in save'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/transactions.rb:252:in `rollback_active_record_state!'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/activerecord-3.2.7.rc1/lib/active_record/transactions.rb:240:in `save'
from (irb):9
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/railties-3.2.7.rc1/lib/rails/commands/console.rb:47:in `start'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/railties-3.2.7.rc1/lib/rails/commands/console.rb:8:in `start'
from /Users/chrismcc/.rvm/gems/ruby-1.9.3-p194-fast@amanda/gems/railties-3.2.7.rc1/lib/rails/commands.rb:41:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'1.9.3p194 :010 >
Any ideas?
Note that it DOES work if I do:
a = Field.new(body: "a")
b = Field.new(body: "b")
c = Field.new(body: "c")
a.children << b
b.children << c
c.save
b.save
a.save
Thanks!
I have a really specific model validation that must ensure parent is not defined to enable some validations.
The problem is that if I create both the parent and the children without saving to the database (via build), it fails. Here is a code snippet:
class Category < ActiveRecord::Base
acts_as_tree
validates :code, :presence => {:if => first_level?}
def first_level?
parent.nil?
end
end
here is how to make it fail:
c = Category.new(:code => '123')
c1 = c.children.build
c1.valid?
=> false
I've searched for the problem and found this patch submmited to rails 2.3.6 (it's old but still there): https://rails.lighthouseapp.com/projects/8994/tickets/2815-nested-models-build-should-directly-assign-the-parent
The solution is to use "inverse_of" while defining the associations. In theory it will fix this error.
Quick question: is it possible to have different acts_as_tree options for subclasses of a STI model? Specifically, can we do this:
def Person
acts_as_tree
end
def Person::Teacher < Person
acts_as_tree :order => 'age'
end
def Person::Student < Person
acts_as_tree :order => 'name'
end
In this example, each teacher, ordered by age, has children ordered by name.
NoMethodError (undefined method `new' for #FileOrFolder:0x)
code :
newStructure = FileOrFolder.new
newStructure.fullpath = path
pathbits = path.split('/')
newStructure.name = pathbits.last
grandparent = newStructure.new(:name => "Grandparent")
I've got 2 models: User and Contract. User has many contracts but he should be able to see all of his and his descendants contracts. I tried something like this:
class User < ActiveRecord::Base
acts_as_tree
has_many :contracts, through: :self_and_descendants
end
class Contract < ActiveRecord::Base
belongs_to :user
end
but i'm getting:
1.9.3p194 :058 > User.find(3).contracts
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 3]]
SystemStackError: stack level too deep
from gems/activerecord-3.2.6/lib/active_record/reflection.rb:530
is it possible to do such thing?
I've noticed that one has to manually order a call to .roots as the defined order isn't applied to this scope. Is there a reason for this or is it a bug? :)
When position is negative the child showed before the parent in roots_and_descendants_preordered
, self_and_descendants_preordered
it's behaving correctly (displaying Parent first, followed by its children)
Child A
Parent
Child B
Child C
What do you think about adding :adopt to the :dependent options... children of the deleted node are assigned to their grandparent node? If the deleted node is a "root" node it could then perform :nullify on the children instead?
Does this support one node having multiple parents?
Strange, with v3.0.0
it works fine, with 3.0.1
and 3.0.2
not.. The Rails I'm using is 3.2.1
, and here's what I get when trying to use 3.0.1
or 3.0.2
:
$ rails server
/usr/lib/ruby/gems/1.8/gems/activesupport-3.2.1/lib/active_support/dependencies.rb:251:in `require': no such file to load -- closure_tree/acts_as_tree (LoadError)
from /usr/lib/ruby/gems/1.8/gems/activesupport-3.2.1/lib/active_support/dependencies.rb:251:in `require'
(...)
I can't figure out, why is it happen..
I hope this time it's not my fault ;)
Hi, I already posted this question at http://stackoverflow.com/questions/13457479/implementing-version-history-with-a-closure-table-schema but I thought I should get mceachen's attention on it:
I have a custom CMS implementation that stores content nodes using the closure_tree gem.
The time has come for me to implement a version history where any change in the content tree (editing, inserting, moving, deleting nodes, etc.) would create a new version of the root node (a publication). And users would be able to look at older versions and revert back to them. The revert action would create a newer version which is a copy of the reverted one.
Is there a well known way to achieve this? or does anyone have an idea or example implementation for this sort of thing?
Thanks!
I have a category hierarchy and was trying to avoid N+1 queries by doing:
Category.includes(:self_and_descendants).find(params[:id])
However, I am getting an error:
ActiveRecord::StatementInvalid: PG::Error: ERROR:
column "name" does not exist
Here is the SQL being generated
Category Load (0.4ms) SELECT "categories".*
FROM "categories"
WHERE "categories"."id" = $1 LIMIT 1 [["id", 5]]
CategoryHierarchy Load (0.6ms) SELECT "category_hierarchies".*
FROM "category_hierarchies"
WHERE "category_hierarchies"."ancestor_id" IN (5)
ORDER BY "category_hierarchies".generations asc, name
My category model has an order by:
acts_as_tree :order => 'name'
When I get rid of the ordering it works fine.
How can I use .includes(:self_and_descendants)
and also order by name
? Thanks
Hi!
I'd like to use closure_tree
on a project, but my objects use UUIDs as the primary key. Some SQL statements interpolate id
directly, which fails for UUIDs because they need to be quoted. Maybe use ActiveRecord::Base.quote(id)
instead?
Hi, it is quite counterintuitive, that 'self_and_ancestors' doesn't see changes to node until reload. Is it targeted behaviour and I should use reload in methods, which use these types of scopes?
x = Vega::Category.create(name: 'Parent2')
=> #<Vega::Category id: 3, name: "Parent2", position: 100, uri: "parent2", parent_id: nil, created_at: "2013-07-10 14:16:39", updated_at: "2013-07-10 14:16:39">
x.self_and_ancestors.to_a
=> []
x.reload
=> #<Vega::Category id: 3, name: "Parent2", position: 100, uri: "parent2", parent_id: nil, created_at: "2013-07-10 14:16:39", updated_at: "2013-07-10 14:16:39">
x.self_and_ancestors.to_a
=> [#<Vega::Category id: 3, name: "Parent2", position: 100, uri: "parent2", parent_id: nil, created_at: "2013-07-10 14:16:39", updated_at: "2013-07-10 14:16:39">]
I've had the need to have some sort of "path enumeration", and figured out a SQL query (that I use as a view) to do that:
SELECT nodes.id AS node_id, nodes.name,
( SELECT group_concat(node.code separator '.') AS path
FROM nodes as node
JOIN node_hierarchies as tree
ON (node.id = tree.ancestor_id)
WHERE tree.descendant_id = nodes.id
) AS path
from nodes;
The output of this query looks like:
1, "leaf node", 1.2.1.1
1, "leaf node 2", 1.2
This is useful to make for example, a search based on "path" (think auto complete).
I don't know exactly how it could be incorporated by the gem, but think it is useful enough to be considered for.
When setting the order column a string must be used, using a symbol won't work.
Works
acts_as_tree order: 'position'
It doesn't
acts_as_tree order: :position
I don't have a good explanation for why would this be better, other than it feels more natural and it wouldn't take much, changing:
https://github.com/mceachen/closure_tree/blob/master/lib/closure_tree/columns.rb#L71
# c = ct_class.columns_hash[order_option]
c = ct_class.columns_hash[order_option.to_s]
Should work with both strings and symbols.
UPDATE
I'm fairly new to testing, but a failing test could look something like this:
# spec/support/models.rb
class OtherLabel < Label
acts_as_tree :order => :sort_order
end
# spec/label_spec.rb
context "OtherLabel" do
it "should include DeterministicNumericOrdering" do
OtherLabel.include?(ClosureTree::DeterministicNumericOrdering).should be_true
end
end
Here's how it currently works:
parent = Tag.create(:name => 'Parent')
child = parent.children.create(:name => 'Child')
# parent child: ['Child']
other_parent = Tag.create(:name => 'Other parent')
other_parent.children << child
# parent child: []
# other_parent child: ['Child']
You can add or move a node to a non-controlled position of another node. The order of the children' list of a node is determined by the id of the children.
On the other hand, it would be great if one could do this with the child list of other_parent
:
second_child = other_parent.children.create(:name => 'Second child')
# ['Child', 'Second child']
third_child = Tag.create(:name => 'Third child')
third_child.move_to_right(child)
# ['Child', 'Third child', 'Second child']
third_child.move_to_left(child)
# ['Third child', 'Child', 'Second child']
We need to explicitly determine the order in which the children are set and retrieved: an order column index for every hierarchy.
This means:
4.2.1 works fine, but 4.2.2 gives me:
ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes: ancestor, descendant, generations
I get this message when I use this gem
attr_accessible
is extracted out of Rails into a gem. Please use new recommended protection model for params(strong_parameters) or addprotected_attributes
to your Gemfile to use old one.
Maybe creating a branch or something like that for rails 4 closure_tree ?
I don't know if this is really an issue. I am using RSpec and every time I run a suite, a .lock-closuretree file is generated in my source's root directory. Is this expected or is there something wrong with my configuration?
When trying to load hash_tree as much effectively as can I came into an issue.
In model I have set order option act_as_tree order: 'name'
And then when trying to call Right.includes(:self_and_descendants)
mysql throws error:
Unknown column 'name' in 'order clause
That's because ActiveRecord are trying to preload rIght_hierarchies without joining the model (Right) table.
I triet to dig into the code and find the bug. But only found that in closure_tree/model.rb
there is _ct.has_many_with_order_option
when defining has_many association on model.
Couldn't find out the way how to implement it to work with preloading and also preserve ordering feature on preloaded models.
Any ideas ?
I'm making the assumption that if I move a child to a new parent, all of the ancestor / descendent relationships would be updated within the hierarchy table - similar to when you simply add a child. Here is how I've addressed the issue:
module Ext
module ClosureTree
module ActsAsTree
def move_self_and_descendants_to_child_of(parent)
move_to_child_of parent
children.each do |child|
update_children_for_move self, child
end
end
private
def update_children_for_move(parent, child)
child.move_to_child_of parent
child.children.each do |next_child|
update_children_for_move child, next_child
end
end
end
end
end
ActiveRecord::Base.send(:include, Ext::ClosureTree::ActsAsTree)
Hey,
I'm building something weird.
I have one model, which is Post
, and subclasses of it: Comment
, Album
, Photo
.
class Post < ActiveRecord::Base
acts_as_tree
end
Lets say I created an album, etc with these steps:
album = Album.create # <Album...
album.children # []
comment = Comment.create # <Comment...
# add the comment to be child of the album
album.children << comment
# create a photo
photo = Photo.create # <Photo...>
# add the comment to be child of the album
album.children << photo
# Let's create a photo doesnt belong to an album
Photo.create
Now here is my list action:
@posts = Post.roots.includes(:children).order('created_at DESC')
It will return Posts with eager loading all children (comments and photos). With the case above [Album, Photo] as parent and [Comment, Photo] as children of the Album.
The question: Is there any way to return for example: the children of the album [all the comment, and only 3 photos]?
Maybe in another words, how could parent have 2 trees of children?
While running a migration file with Tag.rebuild! I encounter the following error:
PGError: ERROR: column "id" does not exist LINE 1: ...r_id", "descendant_id") VALUES(0, NULL, NULL) RETURNING "id"
This is because the join table lacks an id column and this is a problem for the PostgreSQL adapter, see this:
http://www.ruby-forum.com/topic/195472
Any idea how to get around this? Adding an id column is not possible because rails prohibits it for HABTM associations. Composite primary keys also do not work.
TIA,
Subhash
I'm using a table prefix, which is specified in application.rb like so:
config.active_record.table_name_prefix = "d2"
I have a model called "TagDefinition" that I have acts_as_tree on.
The problem is that acts_as_tree appears to look at the table name for TagDefinition, which due to the prefix is actually "d2_tag_definition", and then creates a model "D2TagDefinitionHierarchy" - ActiveRecord adds the table prefix to this, so it thinks the table name for this model is d2_d2_tag_definition_hierarchies. The actual table I added with the migration is d2_tag_definition_hierarchies.
So, the SQL queries in the gem where you manually insert the table name work fine, but if you do something like an ActiveRecord delete on TagDefinition (or pretty much anything where ActiveRecord tries to handle the generated Hierarchy object, rather than with manual SQL), it errors out when ActiveRecord uses the prefix and tries to select from d2_d2_tag_definition_hierarchies, which doesn't exist.
Rails had a similar bug in the table dumper, described here: rails/rails@1bdc098#activerecord/lib/active_record/schema_dumper.rb
I think if you change hierarchy_class_name to strip the prefix from the table name like rails did, you should be fine.
I've got problem when I'm trying to rebuild hierarchies table. Running User.rebuild! causes this error:
https://gist.github.com/1244628 (tried this on sqlite3 and postgresql)
My model:
class User < ActiveRecord::Base
# invited_by_id comes from devise_invitable
acts_as_tree parent_column_name: :invited_by_id
end
Migration file:
class CreateUsersHierarchies < ActiveRecord::Migration
def change
create_table :users_hierarchies, id: false do |t|
t.integer :ancestor_id, null: false # ID of the parent/grandparent/great-grandparent/... tag
t.integer :descendant_id, null: false # ID of the target tag
t.integer :generations, null: false # Number of generations between the ancestor and the descendant. Parent/child = 1, for example.
end
# For "all progeny of..." selects:
add_index :users_hierarchies, [:ancestor_id, :descendant_id], unique: true
# For "all ancestors of..." selects
add_index :users_hierarchies, [:descendant_id]
end
end
Is this a bug or am I doing something wrong?
After setting config.whitelist_attributes = false
in application.rb, the model that is acting as a tree raises ActiveModel::MassAssignmentSecurity::Error
. This is, because closure_tree is adding attr_accessible :parent
(https://github.com/mceachen/closure_tree/blob/32da850126336fec6036ad8154a9b1fa738cb30e/lib/closure_tree/model.rb#L17)
def use_attr_accessible?
ActiveRecord::VERSION::MAJOR == 3 &&
defined?(ActiveModel::MassAssignmentSecurity) &&
model_class.ancestors.include?(ActiveModel::MassAssignmentSecurity)
end
I beleive that this happens because of the last condition of use_attr_accessible?
, which returns true even when using strong parameters. A better condition will be model_class.accessible_attributes.empty?
, which returns true only when attr_accessible has been used in the model.
I have a weird problem here. I added some nodes and suddenly I can't use Taxonomy.hash_tree
anymore.
I thought, "ok, given I just started out, I'll just delete everything and start over", but then what if this issue happens when I go to production?
In fact, after starting over and recreating the nodes, I haven't got the error so far.
Do you have any idea what this could be?
Thanks!
Here, everything's ok.
irb(main):003:0> Company.first.taxonomies.all
Company Load (0.6ms) SELECT "companies".* FROM "companies" LIMIT 1
Taxonomy Load (0.3ms) SELECT "taxonomies".* FROM "taxonomies" WHERE "taxonomies"."store_id" = 1
=> [#<Taxonomy id: 15, name: "a", parent_id: nil, store_id: 1, created_at: "2013-01-08 01:14:34", updated_at: "2013-01-08 01:14:34">,
#<Taxonomy id: 16, name: "aa", parent_id: 15, store_id: 1, created_at: "2013-01-08 01:14:37", updated_at: "2013-01-08 01:14:37">,
#<Taxonomy id: 17, name: "aaa", parent_id: 16, store_id: 1, created_at: "2013-01-08 01:14:40", updated_at: "2013-01-08 01:14:40">,
#<Taxonomy id: 18, name: "b", parent_id: nil, store_id: 1, created_at: "2013-01-08 01:14:45", updated_at: "2013-01-08 01:14:45">,
#<Taxonomy id: 19, name: "bb", parent_id: 18, store_id: 1, created_at: "2013-01-08 01:14:48", updated_at: "2013-01-08 01:14:48">,
#<Taxonomy id: 20, name: "aaa2", parent_id: 16, store_id: 1, created_at: "2013-01-08 01:14:59", updated_at: "2013-01-08 01:14:59">,
#<Taxonomy id: 21, name: "aaaa", parent_id: 20, store_id: 1, created_at: "2013-01-08 01:59:28", updated_at: "2013-01-08 01:59:28">,
#<Taxonomy id: 22, name: "aaaa2", parent_id: 20, store_id: 1, created_at: "2013-01-08 01:59:34", updated_at: "2013-01-08 01:59:34">,
#<Taxonomy id: 23, name: "aaa3", parent_id: 20, store_id: 1, created_at: "2013-01-08 01:59:38", updated_at: "2013-01-08 01:59:38">,
#<Taxonomy id: 24, name: "a", parent_id: 9, store_id: 1, created_at: "2013-01-08 02:34:03", updated_at: "2013-01-08 02:34:03">,
#<Taxonomy id: 25, name: "a", parent_id: 9, store_id: 1, created_at: "2013-01-08 02:34:08", updated_at: "2013-01-08 02:34:08">]
Here, the error.
irb(main):006:0> Company.first.taxonomies.hash_tree
Company Load (0.7ms) SELECT "companies".* FROM "companies" LIMIT 1
Taxonomy Load (0.7ms) SELECT "taxonomies".* FROM "taxonomies" INNER JOIN (
SELECT descendant_id, MAX(generations) as depth
FROM "taxonomy_hierarchies"
GROUP BY descendant_id
) AS generation_depth
ON "taxonomies".id = generation_depth.descendant_id WHERE "taxonomies"."store_id" = 1 ORDER BY generation_depth.depth
NoMethodError: undefined method `[]=' for nil:NilClass
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/bundler/gems/closure_tree-7bf33728b625/lib/closure_tree/acts_as_tree.rb:398:in `block in build_hash_tree'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/relation/delegation.rb:6:in `each'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/relation/delegation.rb:6:in `each'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/bundler/gems/closure_tree-7bf33728b625/lib/closure_tree/acts_as_tree.rb:393:in `build_hash_tree'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/bundler/gems/closure_tree-7bf33728b625/lib/closure_tree/acts_as_tree.rb:94:in `hash_tree'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/relation/delegation.rb:14:in `block in hash_tree'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/relation.rb:241:in `block in scoping'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/scoping.rb:98:in `with_scope'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/relation.rb:241:in `scoping'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/relation/delegation.rb:14:in `hash_tree'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/associations/collection_proxy.rb:100:in `method_missing'
from (irb):6
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/railties-3.2.9/lib/rails/commands/console.rb:47:in `start'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/railties-3.2.9/lib/rails/commands/console.rb:8:in `start'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus/rails.rb:117:in `console'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:105:in `block in command'
... 8 levels...
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:61:in `go'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:67:in `block (3 levels) in go'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:67:in `fork'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:67:in `block (2 levels) in go'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:63:in `each'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:63:in `block in go'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:61:in `loop'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:61:in `go'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:67:in `block (3 levels) in go'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:67:in `fork'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:67:in `block (2 levels) in go'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:63:in `each'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:63:in `block in go'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:61:in `loop'
from /Users/kurko/.rbenv/versions/1.9.3-p194-perf/lib/ruby/gems/1.9.1/gems/zeus-0.12.0/lib/zeus.rb:61:in `go'
from -e:1:in `<main>'
Hello,
I'm using closure_tree 4.0.1 with rails 3.0 and ruby 1.9.3
In the model I use to act_as_tree, I need to trigger an after_create callback when a leaf is created. This callback needs the ancestors of the current object to do some operation.
Unfortunately, I cant access the ancestors in the after_create because it seems that the hierarchie of the newly created object is written in the table after the callbacks.
Would you have any idea how to fix this issue ?
I pass unexisted parent id (1
)
> cc = Comment.new(parent_id: 1)
=> #<Comment id: nil, parent_id: 1, created_at: nil, updated_at: nil>
> cc.parent
Comment Load (1.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."id" = $1 ORDER BY "comments"."id" ASC LIMIT 1 [["id", 1]]
=> nil
> cc.save
(0.8ms) BEGIN
SQL (5.6ms) INSERT INTO "comments" ("created_at", "parent_id", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["created_at", Fri, 09 Aug 2013 13:56:01 UTC +00:00], ["parent_id", 1], ["updated_at", Fri, 09 Aug 2013 13:56:01 UTC +00:00]]
(1.0ms) SELECT pg_try_advisory_lock(2085799232), 1376056561.717587
SQL (0.9ms) INSERT INTO "comment_hierarchies" ("ancestor_id", "descendant_id", "generations") VALUES ($1, $2, $3) [["ancestor_id", 20621], ["descendant_id", 20621], ["generations", 0]]
(1.0ms) INSERT INTO "comment_hierarchies"
(ancestor_id, descendant_id, generations)
SELECT x.ancestor_id, 20621, x.generations + 1
FROM "comment_hierarchies" x
WHERE x.descendant_id = 1
Comment Load (10.3ms) SELECT "comments".* FROM "comments" WHERE "comments"."parent_id" = $1 ORDER BY created_at [["parent_id", 20621]]
(0.7ms) SELECT pg_advisory_unlock(2085799232), 1376056561.764168
(1.8ms) COMMIT
=> true
> cc.parent
=> nil
> cc.parent_id
=> 1
But parent_id and record in hierarchies table was added.
Hello, I've been trying to solve this problem where I'd like to search for a node across a few different trees. In essence, something similar to calling descendants, but works with multiple trees at once in 1 query. Has anyone had any success in doing this with closure_tree?
Here's a quick example:
A => [B]
C => [D, Z]
E => [F, G, H]
>> Node.subtrees_for([A, C]).find(D)
=> D
Thank you.
Some companies will only use gems with a certain license.
The canonical and easy way to check is via the gemspec
via e.g.
spec.license = 'MIT'
# or
spec.licenses = ['MIT', 'GPL-2']
There is even a License Finder to help companies ensure all gems they use
meet their licensing needs. This tool depends on license information being available in the gemspec.
Including a license in your gemspec is a good practice, in any case.
How did I find you?
I'm using a script to collect stats on gems, originally looking for download data, but decided to collect licenses too,
and make issues for missing ones as a public service :)
https://gist.github.com/bf4/5952053#file-license_issue-rb-L13 So far it's going pretty well
The hierarchy class' ==
is checking equality on the ancestor and descendant associations (acts_as_tree.rb:39) which causes those objects to be unnecessarily loaded when checking equality. Can the equality check instead compare the ancestor_id and descendant_id?
This is especially noticeable during eager loading. For example, running the following query in my app:
Category.where(id: ids).includes(:self_and_ancestors)
Runs the following SQL statements:
Category Load (0.9ms) SELECT "categories".* FROM "categories" WHERE "categories"."organization_id" = 1 AND "categories"."id" IN (20, 22)
CategoryHierarchy Load (0.5ms) SELECT "category_hierarchies".* FROM "category_hierarchies" WHERE "category_hierarchies"."descendant_id" IN (20, 22) ORDER BY "category_hierarchies".generations asc
Category Load (0.6ms) SELECT "categories".* FROM "categories" WHERE "categories"."organization_id" = 1 AND "categories"."id" = 19 LIMIT 1
Category Load (0.4ms) SELECT "categories".* FROM "categories" WHERE "categories"."organization_id" = 1 AND "categories"."id" = 20 LIMIT 1
Category Load (0.3ms) SELECT "categories".* FROM "categories" WHERE "categories"."organization_id" = 1 AND "categories"."id" = 18 LIMIT 1
Category Load (0.3ms) SELECT "categories".* FROM "categories" WHERE "categories"."organization_id" = 1 AND "categories"."id" = 22 LIMIT 1
Category Load (0.5ms) SELECT "categories".* FROM "categories" WHERE "categories"."organization_id" = 1 AND "categories"."id" = 18 LIMIT 1
Category Load (0.3ms) SELECT "categories".* FROM "categories" WHERE "categories"."organization_id" = 1 AND "categories"."id" = 19 LIMIT 1
If I change the equal implementation to compare ids, it only runs the following:
Category Load (0.8ms) SELECT "categories".* FROM "categories" WHERE "categories"."organization_id" = 1 AND "categories"."id" IN (20, 22)
CategoryHierarchy Load (0.4ms) SELECT "category_hierarchies".* FROM "category_hierarchies" WHERE "category_hierarchies"."descendant_id" IN (20, 22) ORDER BY "category_hierarchies".generations asc
Category Load (0.5ms) SELECT "categories".* FROM "categories" WHERE "categories"."organization_id" = 1 AND "categories"."id" IN (20, 19, 18, 22)
I'll work on putting together a pull request (although it's a pretty easy fix).
As per Rails docs #save
and #destroy
are wrapped into a single transaction by default. This way you don't need to handle transactions when affecting multiple related models.
closure_tree
does not extend this on its operations, specifically when moving nodes around. Here's what I see when adding a new node as children of another:
# assume the parent has been already persisted
child = Tag.new(:name => 'Child')
child.save
parent.children << child
BEGIN
-- INSERT THE CHILD (parent_id is NULL)
-- INSERT TO THE HIERARCHY everything related to the child (good!)
COMMIT -- :(
BEGIN
-- UPDATE parent_id
-- DELETE FROM THE HIERARCHY everything related to the child
-- INSERT TO THE HIERARCHY everything related to the child
COMMIT
Arrange a tree or part of it into an ordered hash is a piece a closure_tree user will never regret to have.
The Ancestry project approaches to it in a very useful way.
Me again, with another weird edge case issue:
In my app, our hierarchical tags are scoped to a particular client and we indicate this with a 'client_name' field in our TagDefinition object.
I'm calling find_or_create_by_path thusly
TagDefinition.find_or_create_by_path(hierarchies, {'client_name' => client_name, 'context' => TagDefinition::HIERARCHY_CONTEXT})
where 'hierarchies' is an array of tag_definition names. The problem is this - every single one of our clients has a root tag called "All". When I run the above snippet for a hierarchies value of ['All','North','South'] for a client_name of 'aperture_science', closure tree happily finds the root tag 'All' - for another client, 'acme', and then creates all the other tags with the incorrect tag as the root.
The problem is this:
In both the class and instance methods of find_or_create_by_path, you accept a hash of attributes that get passed to active record. This is all groovy, except that the hash of attributes is only honored in the insert/update, NOT in the where clause.
I updated my fork https://github.com/juddblair/closure_tree with a quick and dirty fix (merged in the attributes hash before the where clause) and ran it against our app, seemed to fix the problem.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.