Class: SimpleNestedSet::NestedSet
- Inherits:
-
ActiveRecord::Relation
- Object
- ActiveRecord::Relation
- SimpleNestedSet::NestedSet
- Includes:
- SqlAbstraction
- Defined in:
- lib/simple_nested_set/nested_set.rb
Instance Attribute Summary collapse
-
#node ⇒ Object
readonly
Returns the value of attribute node.
Class Method Summary collapse
- .build_class(model, scopes) ⇒ Object
- .extract_attributes!(attributes) ⇒ Object
- .scope(scope) ⇒ Object
- .scope_condition(scope) ⇒ Object
Instance Method Summary collapse
- #attribute_names ⇒ Object
-
#db_adapter ⇒ Object
def denormalize_query_mysql(field) synonym = field.to_s.reverse.to_sym aliaz = arel_table.as(“table_#synonym”).
-
#denormalize! ⇒ Object
FIXME This needs to be abstracted away into the SqlAbstraction module FIXME we don’t always want to call this on after_save, do we? it’s only relevant when either the structure or the slug has changed.
-
#denormalize_level_query ⇒ Object
sqlite3, postgresql.
-
#denormalize_path_query ⇒ Object
sqlite3, postgresql.
-
#init_as_node ⇒ Object
before validation set lft and rgt to the end of the tree.
-
#initialize(*args) ⇒ NestedSet
constructor
A new instance of NestedSet.
- #move_by_attributes(attributes) ⇒ Object
- #move_to(target, position) ⇒ Object
- #move_to_path(path) ⇒ Object
- #populate_associations(nodes) ⇒ Object
-
#prune_branch ⇒ Object
Prunes a branch off of the tree, shifting all of the elements on the right back to the left so the counts still work.
- #rebuild_from_parents!(sort_order = :id) ⇒ Object
- #rebuild_from_paths! ⇒ Object
-
#reload ⇒ Object
reload nested set attributes.
-
#same_scope?(other) ⇒ Boolean
Returns true if the node has the same scope as the given node.
- #save! ⇒ Object
Methods included from SqlAbstraction
Constructor Details
#initialize(*args) ⇒ NestedSet
Returns a new instance of NestedSet.
35 36 37 38 39 40 |
# File 'lib/simple_nested_set/nested_set.rb', line 35 def initialize(*args) super(node_class, node_class.arel_table) @node = args.first if args.size == 1 @where_values = self.class.scope(args.first).instance_variable_get(:@where_values) if args.size == 1 # TODO how to set order(:lft) here? it's now being added on various scopes (see class methods), would be better to have it here. end |
Instance Attribute Details
#node ⇒ Object (readonly)
Returns the value of attribute node.
33 34 35 |
# File 'lib/simple_nested_set/nested_set.rb', line 33 def node @node end |
Class Method Details
.build_class(model, scopes) ⇒ Object
8 9 10 11 12 13 14 |
# File 'lib/simple_nested_set/nested_set.rb', line 8 def build_class(model, scopes) model.const_get(:NestedSet) rescue model.const_set(:NestedSet, Class.new(NestedSet)).tap do |node_class| node_class.node_class = model node_class.move_after_save = true node_class.scope_names = Array(scopes).map { |s| s.to_s =~ /_id$/ ? s.to_sym : :"#{s}_id" } end end |
.extract_attributes!(attributes) ⇒ Object
26 27 28 29 30 |
# File 'lib/simple_nested_set/nested_set.rb', line 26 def extract_attributes!(attributes) attributes.slice(*SimpleNestedSet::ATTRIBUTES).tap do attributes.except!(*(SimpleNestedSet::ATTRIBUTES - [:path, :parent, :parent_id])) end if attributes.respond_to?(:slice) end |
.scope(scope) ⇒ Object
16 17 18 |
# File 'lib/simple_nested_set/nested_set.rb', line 16 def scope(scope) scope.blank? ? node_class.scoped : node_class.where(scope_condition(scope)) end |
.scope_condition(scope) ⇒ Object
20 21 22 23 24 |
# File 'lib/simple_nested_set/nested_set.rb', line 20 def scope_condition(scope) scope_names.inject({}) do |c, name| c.merge(name => scope.respond_to?(name) ? scope.send(name) : scope[name]) end end |
Instance Method Details
#attribute_names ⇒ Object
127 128 129 |
# File 'lib/simple_nested_set/nested_set.rb', line 127 def attribute_names @attribute_names ||= node.attribute_names.select { |attribute| ATTRIBUTES.include?(attribute.to_sym) } end |
#db_adapter ⇒ Object
def denormalize_query_mysql(field)
synonym = field.to_s.reverse.to_sym
aliaz = arel_table.as("table_#{synonym}")
field_sql = yield aliaz
query = [
aliaz.project("#{field_sql} AS field_#{synonym}", aliaz[:lft], aliaz[:rgt]).to_sql,
' WHERE ',
where_clauses.map { |clause| clause.gsub(arel_table.name, aliaz.table_alias.to_s) }.join(' AND ')
].join
<<-sql
#{field} = (
SELECT #{aliaz.table_alias.to_s}.field_#{synonym}
FROM (#{query}) AS #{aliaz.table_alias.to_s}
WHERE #{aliaz[:lft].lt(arel_table[:lft]).to_sql}
AND #{aliaz[:rgt].gt(arel_table[:rgt]).to_sql}
)
sql
end
222 223 224 |
# File 'lib/simple_nested_set/nested_set.rb', line 222 def db_adapter node.class.connection.instance_variable_get('@config')[:adapter].to_sym end |
#denormalize! ⇒ Object
FIXME This needs to be abstracted away into the SqlAbstraction module FIXME we don’t always want to call this on after_save, do we? it’s only relevant when either the structure or the slug has changed
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/simple_nested_set/nested_set.rb', line 60 def denormalize! sql = [] case db_adapter when :mysql, :mysql2 sql << <<-sql if node.has_attribute?(:level) `level` = ( SELECT tmp.lev FROM ( SELECT n0.id, count(*) AS lev FROM #{arel_table.name} AS n0 CROSS JOIN #{arel_table.name} AS n1 WHERE n1.lft < n0.lft AND n1.rgt > n0.rgt GROUP BY n0.id ) AS tmp WHERE ( #{arel_table.name}.id = tmp.id ) ) sql scoping = [ 'n1.lft <= n0.lft AND n1.rgt >= n0.rgt', where_clauses.map { |clause| clause.gsub(arel_table.name, 'n1') } ].flatten.compact.join(' AND ') sql << <<-sql if node.has_attribute?(:path) `path` = ( SELECT tmp.pat FROM ( SELECT n0.id, #{group_concat(db_adapter, "n1`.`slug" )} AS pat FROM (SELECT * FROM #{arel_table.name} ORDER BY `lft`) AS n0 CROSS JOIN (SELECT * FROM #{arel_table.name} ORDER BY `lft`) AS n1 WHERE #{scoping} GROUP BY n0.id ) AS tmp WHERE ( #{arel_table.name}.id = tmp.id ) ) sql else sql << denormalize_level_query if node.has_attribute?(:level) sql << denormalize_path_query if node.has_attribute?(:path) end update_all(sql.join(',')) unless sql.blank? if [:mysql, :mysql2].include?(db_adapter) update_all("`level` = 0", "`level` IS NULL") if node.has_attribute?(:level) update_all("`path` = `slug`", "`path` IS NULL") if node.has_attribute?(:slug) && node.has_attribute?(:path) end end |
#denormalize_level_query ⇒ Object
sqlite3, postgresql
181 182 183 184 185 186 187 188 |
# File 'lib/simple_nested_set/nested_set.rb', line 181 def denormalize_level_query aliaz = arel_table.as(:l) query = aliaz.project(aliaz[:id].count). where(aliaz[:lft].lt(arel_table[:lft])). where(aliaz[:rgt].gt(arel_table[:rgt])) query = [query.to_sql] + where_clauses.map { |clause| clause.gsub(arel_table.name, 'l') } "level = (#{query.join(' AND ')})" end |
#denormalize_path_query ⇒ Object
sqlite3, postgresql
191 192 193 194 195 196 197 198 |
# File 'lib/simple_nested_set/nested_set.rb', line 191 def denormalize_path_query aliaz = arel_table.as(:l) query = aliaz.project(group_concat(db_adapter, :slug)). where(aliaz[:lft].lteq(arel_table[:lft])). where(aliaz[:rgt].gteq(arel_table[:rgt])) query = [query.to_sql] + where_clauses.map { |clause| clause.gsub(arel_table.name, 'l') } "path = (#{query.join(' AND ')})" end |
#init_as_node ⇒ Object
before validation set lft and rgt to the end of the tree
140 141 142 143 |
# File 'lib/simple_nested_set/nested_set.rb', line 140 def init_as_node max_right = maximum(:rgt) || 0 node.lft, node.rgt = max_right + 1, max_right + 2 end |
#move_by_attributes(attributes) ⇒ Object
158 159 160 |
# File 'lib/simple_nested_set/nested_set.rb', line 158 def move_by_attributes(attributes) Move::ByAttributes.new(node, attributes).perform end |
#move_to(target, position) ⇒ Object
168 169 170 |
# File 'lib/simple_nested_set/nested_set.rb', line 168 def move_to(target, position) Move::ToTarget.new(node, target, position).perform end |
#move_to_path(path) ⇒ Object
162 163 164 165 166 |
# File 'lib/simple_nested_set/nested_set.rb', line 162 def move_to_path(path) node.path, parent_path = path, path.split('/')[0..-2].join('/') parent = parent_path.empty? ? nil : node.nested_set.where(:path => parent_path).first node.move_to_child_of(parent) end |
#populate_associations(nodes) ⇒ Object
131 132 133 134 135 136 137 |
# File 'lib/simple_nested_set/nested_set.rb', line 131 def populate_associations(nodes) node.children.target = nodes.select do |child| next unless child.parent_id == node.id child.nested_set.populate_associations(nodes) child.parent = node end end |
#prune_branch ⇒ Object
Prunes a branch off of the tree, shifting all of the elements on the right back to the left so the counts still work.
147 148 149 150 151 152 153 154 155 156 |
# File 'lib/simple_nested_set/nested_set.rb', line 147 def prune_branch if node.rgt && node.lft transaction do diff = node.rgt - node.lft + 1 delete_all(['lft > ? AND rgt < ?', node.lft, node.rgt]) update_all(['lft = (lft - ?)', diff], ['lft >= ?', node.rgt]) update_all(['rgt = (rgt - ?)', diff], ['rgt >= ?', node.rgt]) end end end |
#rebuild_from_parents!(sort_order = :id) ⇒ Object
176 177 178 |
# File 'lib/simple_nested_set/nested_set.rb', line 176 def rebuild_from_parents!(sort_order = :id) Rebuild::FromParents.new.run(self, sort_order) end |
#rebuild_from_paths! ⇒ Object
172 173 174 |
# File 'lib/simple_nested_set/nested_set.rb', line 172 def rebuild_from_paths! Rebuild::FromPaths.new.run(self) end |
#reload ⇒ Object
reload nested set attributes
116 117 118 119 120 121 122 123 124 125 |
# File 'lib/simple_nested_set/nested_set.rb', line 116 def reload columns = [:parent_id, :lft, :rgt] columns << :level if node.has_attribute?(:level) columns << :path if node.has_attribute?(:path) reloaded = unscoped { find(node.id, :select => columns) } node.instance_eval { @attributes.merge!(reloaded.instance_variable_get(:@attributes)) } node.parent = nil if node.parent_id.nil? node.children.reset end |
#same_scope?(other) ⇒ Boolean
Returns true if the node has the same scope as the given node
111 112 113 |
# File 'lib/simple_nested_set/nested_set.rb', line 111 def same_scope?(other) scope_names.all? { |scope| node.send(scope) == other.send(scope) } end |
#save! ⇒ Object
42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/simple_nested_set/nested_set.rb', line 42 def save! attributes = node.instance_variable_get(:@_nested_set_attributes) || {} node.instance_variable_set(:@_nested_set_attributes, nil) attributes.merge!(:parent_id => node.parent_id) if node.parent_id_changed? if self.class.move_after_save move_by_attributes(attributes) unless attributes.blank? denormalize! elsif attributes attributes.except(:parent_id).each do |key, value| node.update_attribute(key, value) end end end |