diff --git a/README.md b/README.md index 30daafac..e3678eb1 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,46 @@ To find out who made a `version`'s object look that way, use `version.originator >> last_version.terminator # 'Bob' +## Has-Many-Through Associations + +PaperTrail can track most changes to the join table. Specifically it can track all additions but it can only track removals which fire the `after_destroy` callback on the join table. Here are some examples: + +Given these models: + + class Book < ActiveRecord::Base + has_many :authorships, :dependent => :destroy + has_many :authors, :through => :authorships, :source => :person + has_paper_trail + end + + class Authorship < ActiveRecord::Base + belongs_to :book + belongs_to :person + has_paper_trail # NOTE + end + + class Person < ActiveRecord::Base + has_many :authorships, :dependent => :destroy + has_many :books, :through => :authorships + has_paper_trail + end + +Then each of the following will store authorship versions: + + >> @book.authors << @dostoyevsky + >> @book.authors.create :name => 'Tolstoy' + >> @book.authorships.last.destroy + >> @book.authorships.clear + +But none of these will: + + >> @book.authors.delete @tolstoy + >> @book.author_ids = [@solzhenistyn.id, @dostoyevsky.id] + >> @book.authors = [] + +There may be a way to store authorship versions, probably using association callbacks, no matter how the collection is manipulated but I haven't found it yet. Let me know if you do. + + ## Storing metadata You can store arbitrary model-level metadata alongside each version like this: diff --git a/test/paper_trail_model_test.rb b/test/paper_trail_model_test.rb index f59c3495..633d9851 100644 --- a/test/paper_trail_model_test.rb +++ b/test/paper_trail_model_test.rb @@ -24,6 +24,24 @@ class Article < ActiveRecord::Base :article_id => Proc.new { |article| article.id } } end +class Book < ActiveRecord::Base + has_many :authorships, :dependent => :destroy + has_many :authors, :through => :authorships, :source => :person + has_paper_trail +end + +class Authorship < ActiveRecord::Base + belongs_to :book + belongs_to :person + has_paper_trail +end + +class Person < ActiveRecord::Base + has_many :authorships, :dependent => :destroy + has_many :books, :through => :authorships + has_paper_trail +end + class HasPaperTrailModelTest < Test::Unit::TestCase load_schema @@ -586,5 +604,44 @@ class HasPaperTrailModelTest < Test::Unit::TestCase end end + context ":has_many :through" do + setup do + @book = Book.create :title => 'War and Peace' + @dostoyevsky = Person.create :name => 'Dostoyevsky' + @solzhenitsyn = Person.create :name => 'Solzhenitsyn' + end + + should 'store version on source <<' do + count = Version.count + @book.authors << @dostoyevsky + assert_equal 1, Version.count - count + assert_equal Version.last, @book.authorships.first.versions.first + end + + should 'store version on source create' do + count = Version.count + @book.authors.create :name => 'Tolstoy' + assert_equal 2, Version.count - count + assert_same_elements [Person.last, Authorship.last], [Version.all[-2].item, Version.last.item] + end + + should 'store version on join destroy' do + @book.authors << @dostoyevsky + count = Version.count + @book.authorships(true).last.destroy + assert_equal 1, Version.count - count + assert_equal @book, Version.last.reify.book + assert_equal @dostoyevsky, Version.last.reify.person + end + + should 'store version on join clear' do + @book.authors << @dostoyevsky + count = Version.count + @book.authorships(true).clear + assert_equal 1, Version.count - count + assert_equal @book, Version.last.reify.book + assert_equal @dostoyevsky, Version.last.reify.person + end + end end diff --git a/test/schema.rb b/test/schema.rb index 5650e130..f7c183bf 100644 --- a/test/schema.rb +++ b/test/schema.rb @@ -49,4 +49,17 @@ ActiveRecord::Schema.define(:version => 0) do t.string :content end + create_table :books, :force => true do |t| + t.string :title + end + + create_table :authorships, :force => true do |t| + t.integer :book_id + t.integer :person_id + end + + create_table :people, :force => true do |t| + t.string :name + end + end