views:

30

answers:

1

I want to be able to add a record which acts like a symlink. In other words I want to be able to store a record/object which will defer most attributes to another record/object.

The same as on a file system where a symlink points to another file (it own data), yet defers everything else to another file.

class Document < ActiveRecord::Base
end

class Page < Document
end

class Folder < Document
end

class Symlink < ActiveRecord::Base
  set_table_name  :documents

  instance_methods.each { |m| undef_method m if (ActiveRecord::Base.instance_methods(false).include? m) && (!['link', 'link_id', 'link_id='].include? m) }

  def method_missing(sym, *args, &block)
    puts "Sending #{sym}(#{args.join(',')}) to obj"
    link.__send__(sym, *args, &block)
  end

  def save
    raise 'Symlink cant be saved' unless new_record?
    super
  end

  private

  def link
    @link ||= Document.find(self.link_id)
  end  
end

At the moment when I try and create a new record I get a stack level too deep for 'link'.

I thought this would be a neat way of solving the problem which could be extracted in to a gem (acts_as_symlink :column => 'parent_id').

Another thought was to keep a full copy of the original record and use a after_save callback to update any symlinks or if the symlink is changed, update the original. So not real symlinks more like sync'd copies.

Any thoughts on my code or an alternative?

A: 

Okay I seem to have cracked it, the key was to replace the methods which access the database column with methods which defer to another object. By over-riding the id method I can also determine if save will save the attributes to either the symlink or the original record. Document in my case is an acts_as_tree + acts_as_list so I am excluding parent_id and position columns from being deferred so I can assign the symlink to a different folder (useless otherwise) and position it within that folder.

class Symlink < Document
  attr_accessor :save_to  


  self.columns.map { |c| c.name }.reject { |c| %w(position parent_id sync_id).include? c }.each do | col |
    self.send :define_method, col.to_sym do   
      source.send(col)
    end
  end

  def id
    if save_to == :symlink
      return read_attribute(:id)
    else
      return source.send :id
    end
  end

  def copy?
    !sync_id.nil?
  end


  private

  def source
    if sync_id
      @source ||= Document.find(sync_id)
    else
      Document.new
    end
  end
end

Only tested in console so far, but reason why this should not work over the full stack.

If its successful I will think abstracting this out in to a general purpose acts_as_symlink gem.

Kris
To create a symlink you need to use save(false) to bypass validations.
Kris