Make it easy and efficient to copy an active record tree
Ruby
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
db
lib
spec
.gitignore
.travis.yml
Gemfile
LICENSE.txt
README.md
Rakefile
acts_as_brand_new_copy.gemspec

README.md

acts_as_brand_new_copy

Codacy Badge Gem Version Build Status Code Climate Test Coverage

Copy an active record with its associated records are not easy.

For example, if we have defined following classes:

class Grade < ActiveRecord::Base
  has_and_belongs_to_many :teachers, :join_table => ::GradeTeacherAssignment.table_name
  has_and_belongs_to_many :students, :join_table => ::GradeStudentAssignment.table_name
end

class Teacher < ActiveRecord::Base
  has_many :student_teacher_assignments
  has_many :students,
    :through => :student_teacher_assignments,
    :source  => :student
end

class Student < ActiveRecord::Base
  has_many :student_teacher_assignments
  has_many :teachers,
    :through => :student_teacher_assignments,
    :source  => :teacher
  has_many :scores
end

class Score < ActiveRecord::Base
  belongs_to :student
end

Can you copy a grade with its teachers and students to another grade in a few lines of code, keeping the relationships between teachers and students? To me, it's no, consequently acts_as_brand_new_copy was born.

Usage

copy an active record with its associations

# copy student itself, return the id for copied student
copy_id = @student.brand_new_copy

# copy student with their scores
copy_id = @student.brand_new_copy({:associations => [:scores]})

# copy the whole grade and all the relationships between grade to students, teachers to students
# NOTE here shows the convenience bought by this gem, we've ensured that a same student won't be copied twice!
copy_id = @grade.brand_new_copy({:associations => [{:teachers => [:students]}, :students]})

i'd like to do some modifications to records during copy process

Don't worry, we've already supported that!

# prefix student name with a 'Copy Of ' during copy
# a callback defined as a class method is needed
Student.class_eval do
  def self.update_name_when_copy(hash_origin, hash_copy, full_context)
    hash_copy['name'] = 'Copy of ' + hash_origin['name']
    true
  end
end
copy_id = @student.brand_new_copy({:callbacks => [:update_name_when_copy]})

# prefix grade, students, teachers name with 'Copy of ', and reset students score to nil during copy
[Grade, Teacher, Student].each do |klass|
  klass.class_eval do
    def self.update_name_when_copy(hash_origin, hash_copy, full_context)
      hash_copy['name'] = 'Copy Of ' + hash_origin['name']
      true
    end
  end
end

Score.class_eval do
  def self.reset_value_when_copy(hash_origin, hash_copy, full_context)
    hash_copy['value'] = nil
    true
  end
end
copy_id = @grade.brand_new_copy({
  :associations => [{:teachers => [:students]}, {:students => [:scores]}],
  :callbacks => [
    :update_name_when_copy,
    {:teachers => [:update_name_when_copy]},
    {:students => [:update_name_when_copy, {:scores => [:reset_value_when_copy]}]}
  ]
})

Installation

Add this line to your application's Gemfile:

gem 'acts_as_brand_new_copy'

And then execute:

$ bundle

Or install it yourself as:

$ gem install acts_as_brand_new_copy

Current Limitation

  • do not support has_many_and_belongs_to_many associations when join table class has a strange table_name(I mean, table_name not in [Class.name.underscore, Class.name.underscore.pluralize])

Contribute

You're highly welcome to improve this gem.

Checkout source code to local

say you git clone the source code to /tmp/acts_as_brand_new_copy

Install dev bundle

$ cd /tmp/acts_as_brand_new_copy
$ bundle install

Do some changes

$ vi lib/acts_as_brand_new_copy.rb

Run test

$ bundle exec rspec spec