DoubleCompil
DoubleCompil

Reputation: 125

Private new and create methods in a Rails ActiveRecord?

I have an ActiveRecord which is a node of a n-ary tree. There are many class methods in the model to create a root, append leaves and so on...

As each insertion modify the whole tree, I want to avoid external classes to instantiate my model through new and create methods.

Any idea how to make it ? (I am in Rails 4.0.2)

UPDATE

So the representation of tree structure I am using is a non-recursive representation - by intervals. The implementation is described there, but it's in French.

Basically, each node have a left_tree and a right_tree that represent an interval. The left_tree and right_tree of the child of a node are inside the interval of the parent. This representation allow me to make very fast select on the tree, but on the other side have heavy insertion procedure.

# Task schema
create_table "tasks", force: true do |t|
   t.string   "label"
   t.integer  "tree_level"
   t.integer  "left_tree"
   t.integer  "right_tree"
end

Then, for insertion I need to the intervals index for all the tree.

# Model Task

# Create the root of the tree. Only static method of the model
def self.create_root! label
  Task.create! do |task|
      task.tree_level = 1
      task.left_tree = 1
      task.right_tree = 2
      task.label = label
  end
end

# Method to add a child for a node. Task model
def create_child! label
  new_task = Task.new

  Task.transaction do
    # Prepare the new task to be inserted in the intervals
    new_task.left_tree = right_tree
    new_task.right_tree = right_tree + 1
    new_task.tree_level = tree_level + 1
    new_task.label = label

    # create an empty space in the tree
    Task.where(
      'right_tree >= :right_tree', 
      { right_tree: right_tree }).update_all('right_tree = right_tree + 2')
    Task.where(
      'left_tree >= :right_tree', 
      { right_tree: right_tree }).update_all('left_tree = left_tree + 2')

    # Save the task, which have now a place in the tree.
    new_task.save!
  end

  reload
  return new_task
end

As you can see, the model should never be instantiated outside my model Task. We should create a root and then from this root create the whole tree through the method create_child!

Upvotes: 1

Views: 1565

Answers (1)

limekin
limekin

Reputation: 1944

You have an option to change the interface like this :

# Just makin a sample base class.
# In your case it would be ActiveRecord::Base
class RecordBase
  def self.create(label)
    record = new
    record.label = label
    record.save
    record
  end

  def save
    true
  end
end

class Task < RecordBase
  # Make the given class methods private.
  private_class_method :new, :create

  # Creates and returns the root.
  def self.create_root(label)
    # Task.create won't work here since create is private.
    create(label)
  end

  def self.create_child(parent, label)
    # Task.new won't work here since new is private.
    child = new
    child.label = label
    # other stuffs like :
    # child.attr = parent.attr
    child.save
    child
  end

  def create_child(label)
    self.class.create_child(self, label)
  end
end

# Things that don't work :
# task = Task.new
# task = Task.create('label')

# Working part :
# Creates and gets the root.
root = Task.create_root('label')

# Creates a child
child = root.create_child('label')

Users are able to call Task.create_child(parent, label), but I don't think it would be problem since it uses the same algorithm as task.create_child(label).

Upvotes: 1

Related Questions