Skip to content
Browse files

Implemented compatibility with :through associations

  • Loading branch information...
1 parent 5668a3a commit 72a116d2a1592a666b3321d993b1e6150c5f1469 @rocksolidwebdesign rocksolidwebdesign committed
Showing with 126 additions and 18 deletions.
  1. +21 −12 README.md
  2. +8 −1 amoeba.gemspec
  3. +17 −3 lib/amoeba.rb
  4. +1 −1 lib/amoeba/version.rb
  5. +13 −0 spec/lib/amoeba_spec.rb
  6. +11 −1 spec/support/data.rb
  7. +31 −0 spec/support/models.rb
  8. +24 −0 spec/support/schema.rb
  9. BIN spec/test.sqlite3
View
33 README.md
@@ -1,28 +1,37 @@
# Amoeba
-Many thanks to [Caleb Hanson](http://github.com/calebhanson) for this awesome logo:
+Easy copying of rails associations such as `has_many`.
![amoebalogo](http://rocksolidwebdesign.com/wp_cms/wp-content/uploads/2012/02/amoeba_logo.jpg)
-## Overview
+## Background
-Easy copying of rails associations such as `has_many`.
+The goal was to be able to easily and quickly reproduce ActiveRecord objects including their children, for example copying a blog post maintaining its associated tags or categories.
-I named this gem Amoeba because amoebas are (small life forms that are) good at reproducing. Their children and grandchildren also reproduce themselves quickly and easily.
+I named this gem "Amoeba" because amoebas are (small life forms that are) good at reproducing. Their children and grandchildren also reproduce themselves quickly and easily.
## Details
An ActiveRecord extension gem to allow the duplication of associated child record objects when duplicating an active record model. This gem overrides and adds to the built in `ActiveRecord::Base#dup` method.
-Rails 3 compatible.
+Rails 3.2 compatible.
## Features
- - Supports automatic recursive duplication of associated `has_one`, `has_many` and `has_and_belongs_to_many` child records.
- - Allows configuration of which fields to copy through a simple DSL applied to your rails models.
- - Supports multiple configuration styles such as inclusive, exclusive and indiscriminate (aka copy everything).
- - Supports preprocessing of fields to help indicate uniqueness and ensure the integrity of your data depending on your business logic needs.
- - Supports per instance configuration to override configuration and behavior on the fly.
+- Supports the following association types
+ - `has_many`
+ - `has_one :through`
+ - `has_many :through`
+ - `has_and_belongs_to_many`
+- A simple DSL for configuration of which fields to copy. The DSL can be applied to your rails models or used on the fly.
+- Multiple configuration styles such as inclusive, exclusive and indiscriminate (aka copy everything).
+- Supports recursive copying of child and grandchild records.
+- Supports preprocessing of fields to help indicate uniqueness and ensure the integrity of your data depending on your business logic needs, e.g. prepending "Copy of " or similar text.
+- Amoeba can perform the following preprocessing operations on fields of copied records
+ - prepend
+ - append
+ - nullify
+ - regex
## Installation
@@ -71,7 +80,7 @@ If you have some models for a blog about like this:
belongs_to :post
end
-Add the amoeba configuration block to your model and call the enable method to enable the copying of child records, like this:
+simply add the amoeba configuration block to your model and call the enable method to enable the copying of child records, like this:
class Post < ActiveRecord::Base
has_many :comments
@@ -435,7 +444,7 @@ Globally search and replace the field for a given pattern. Accepts a hash of fie
## Known Limitations and Issues
-Amoeba does not yet recognize advanced versions of `has_and_belongs_to_many` such as `has_and_belongs_to_many :foo, :through => :bar`. Amoeba does not copy the actual HABMT child records but rather simply adds records to the M:M breakout table to associate the new parent copy with the same records that the original parent were associated with. In other words, it doesn't duplicate your tags or categories, but merely reassociates your parent copy with the same tags or categories that the old parent had.
+Amoeba does not copy the actual HABMT child records but rather simply adds records to the M:M breakout table to associate the new parent copy with the same records that the original parent were associated with. In other words, it doesn't duplicate your tags or categories, but merely reassociates your parent copy with the same tags or categories that the old parent had.
The regular expression preprocessor uses case-sensitive `String#gsub`. Given the performance decreases inherrent in using regular expressions already, the fact that character classes can essentially account for case-insensitive searches, the desire to keep the DSL simple and the general use cases for this gem, I don't see a good reason to add yet more decision based conditional syntax to accommodate using case-insensitive searches or singular replacements with `String#sub`. If you find yourself wanting either of these features, by all means fork the code base and if you like your changes, submit a pull request.
View
9 amoeba.gemspec
@@ -10,7 +10,14 @@ Gem::Specification.new do |s|
s.homepage = "http://github.com/rocksolidwebdesign/amoeba"
s.license = "BSD"
s.summary = %q{Easy copying of rails models and their child associations.}
- s.description = %q{An extension to ActiveRecord to allow the duplication method to also copy associated children, with recursive support for nested of grandchildren. The behavior is controllable with a simple DSL both on your rails models and on the fly, i.e. per instance. Numerous configuration options and styles and preprocessing directives are included for power and flexibility. Tags: copy child associations, copy nested children, copy associated child records, deep_copy, nested copy, copy associations, copy relations, copy relationships, has_one, has_many, has_and_belongs_to_many, duplicate associations, duplicate associated records, duplicate child records, duplicate children, copy all, duplicate all.}
+
+ s.description = <<-EOF
+ An extension to ActiveRecord to allow the duplication method to also copy associated children, with recursive support for nested of grandchildren. The behavior is controllable with a simple DSL both on your rails models and on the fly, i.e. per instance. Numerous configuration options and styles and preprocessing directives are included for power and flexibility.
+
+ Tags: copy child associations, copy nested children, copy associated child records, deep_copy, nested copy, copy associations, copy relations, copy relationships, has_one, has_many, has_and_belongs_to_many, duplicate associations, duplicate associated records, duplicate child records, duplicate children, copy all, duplicate all.
+ EOF
+
+ s.description = %q{}
s.rubyforge_project = "amoeba"
View
20 lib/amoeba.rb
@@ -181,13 +181,27 @@ def amo_process_association(relation_name, settings)
case settings.macro
when :has_one
+ if settings.is_a?(ActiveRecord::Reflection::ThroughReflection)
+ return
+ end
+
old_obj = self.send(relation_name)
- copy_of_obj = old_obj.dup
- copy_of_obj[:"#{settings.foreign_key}"] = nil
+ if not old_obj.nil?
+ copy_of_obj = old_obj.dup
+ copy_of_obj[:"#{settings.foreign_key}"] = nil
- @result.send(:"#{relation_name}=", copy_of_obj)
+ @result.send(:"#{relation_name}=", copy_of_obj)
+ end
when :has_many
+ # copying the children of the regular has many will
+ # effectively do what is desired anyway, the through
+ # association is really just for convenience usage
+ # on the model
+ if settings.is_a?(ActiveRecord::Reflection::ThroughReflection)
+ return
+ end
+
self.send(relation_name).each do |old_obj|
copy_of_obj = old_obj.dup
copy_of_obj[:"#{settings.foreign_key}"] = nil
View
2 lib/amoeba/version.rb
@@ -1,3 +1,3 @@
module Amoeba
- VERSION = "0.0.2"
+ VERSION = "0.1.0"
end
View
13 spec/lib/amoeba_spec.rb
@@ -13,6 +13,10 @@
new_post = old_post.dup
+ start_account_count = Account.all.count
+ start_history_count = History.all.count
+ start_cat_count = Category.all.count
+ start_supercat_count = Supercat.all.count
start_tag_count = Tag.all.count
start_post_count = Post.all.count
start_comment_count = Comment.all.count
@@ -23,6 +27,10 @@
new_post.save
+ end_account_count = Account.all.count
+ end_history_count = History.all.count
+ end_cat_count = Category.all.count
+ end_supercat_count = Supercat.all.count
end_tag_count = Tag.all.count
end_post_count = Post.all.count
end_comment_count = Comment.all.count
@@ -32,12 +40,17 @@
end_posttag_count = rs["tag_count"]
end_tag_count.should == start_tag_count
+ end_cat_count.should == start_cat_count
+ end_account_count.should == start_account_count * 2
+ end_history_count.should == start_history_count * 2
+ end_supercat_count.should == start_supercat_count * 2
end_post_count.should == start_post_count * 2
end_comment_count.should == start_comment_count * 2
end_rating_count.should == start_rating_count * 2
end_postconfig_count.should == start_postconfig_count * 2
end_posttag_count.should == start_posttag_count * 2
+ new_post.supercats.map(&:ramblings).include?("Copy of zomg").should be true
new_post.title.should == "Copy of #{old_post.title}"
new_post.contents.should == "Here's a copy: #{old_post.contents.gsub(/dog/, 'cat')} (copied version)"
end
View
12 spec/support/data.rb
@@ -6,6 +6,8 @@
# First Post {{{
p1 = t.posts.create(:author => u1, :title => "My little pony", :contents => "Lorum ipsum dolor rainbow bright. I like dogs, dogs are awesome.")
f1 = p1.create_post_config(:is_visible => true, :is_open => false, :password => 'abcdefg123')
+a1 = p1.create_account(:title => "Foo")
+h1 = p1.account.create_history(:some_stuff => "Bar")
c1 = p1.comments.create(:contents => "I love it!")
c1.ratings.create(:num_stars => 5)
c1.ratings.create(:num_stars => 5)
@@ -22,7 +24,7 @@
c2.ratings.create(:num_stars => 1)
c2.ratings.create(:num_stars => 2)
-c3 = p1.comments.create(:contents => "zomg kthxbbq!!11!!!1!eleven!!")
+c3 = p1.comments.create(:contents => "kthxbbq!!11!!!1!eleven!!")
c3.ratings.create(:num_stars => 0)
c3.ratings.create(:num_stars => 0)
c3.ratings.create(:num_stars => 1)
@@ -38,4 +40,12 @@
p1.tags << t2
p1.tags << t3
p1.save
+
+c1 = Category.create(:title => "Umbrellas", :description => "Clown fart")
+c2 = Category.create(:title => "Widgets", :description => "Humpty dumpty")
+c3 = Category.create(:title => "Wombats", :description => "Slushy mushy")
+
+Supercat.create(:post => p1, :category => c1, :ramblings => "zomg")
+Supercat.create(:post => p1, :category => c2, :ramblings => "why")
+Supercat.create(:post => p1, :category => c3, :ramblings => "ohnoes")
# }}}
View
31 spec/support/models.rb
@@ -6,7 +6,11 @@ class Post < ActiveRecord::Base
belongs_to :topic
belongs_to :author, :class_name => 'User'
has_one :post_config
+ has_one :account
+ has_one :history, :through => :account
has_many :comments
+ has_many :supercats
+ has_many :categories, :through => :supercats
has_and_belongs_to_many :tags
amoeba do
@@ -17,6 +21,33 @@ class Post < ActiveRecord::Base
end
end
+class Account < ActiveRecord::Base
+ belongs_to :post
+ has_one :history
+
+ amoeba do
+ enable
+ end
+end
+
+class History < ActiveRecord::Base
+ belongs_to :account
+end
+
+class Category < ActiveRecord::Base
+ has_many :supercats
+ has_many :posts, :through => :supercats
+end
+
+class Supercat < ActiveRecord::Base
+ belongs_to :post
+ belongs_to :category
+
+ amoeba do
+ prepend :ramblings => "Copy of "
+ end
+end
+
class PostConfig < ActiveRecord::Base
belongs_to :post
end
View
24 spec/support/schema.rb
@@ -58,4 +58,28 @@
t.integer :post_id
t.integer :tag_id
end
+
+ create_table :categories, :force => true do |t|
+ t.string :title
+ t.string :description
+ end
+
+ create_table :supercats, :force => true do |t|
+ t.integer :post_id
+ t.integer :category_id
+ t.string :ramblings
+ t.timestamps
+ end
+
+ create_table :accounts, :force => true do |t|
+ t.integer :post_id
+ t.string :title
+ t.timestamps
+ end
+
+ create_table :histories, :force => true do |t|
+ t.integer :account_id
+ t.string :some_stuff
+ t.timestamps
+ end
end
View
BIN spec/test.sqlite3
Binary file not shown.

0 comments on commit 72a116d

Please sign in to comment.
Something went wrong with that request. Please try again.