activerecord-duplicate 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/activerecord-duplicate.gemspec +1 -1
- data/gemfiles/Rails_master.gemfile +3 -2
- data/lib/activerecord-duplicate.rb +1 -1
- data/lib/activerecord-duplicate/duplicate.rb +95 -0
- data/spec/callbacks_spec.rb +2 -2
- data/spec/integration_spec.rb +27 -29
- metadata +15 -16
- data/lib/activerecord-duplicate/active_record/duplicate.rb +0 -71
- data/lib/activerecord-duplicate/active_record/duplicate/callbacks.rb +0 -49
@@ -1,10 +1,11 @@
|
|
1
1
|
source :rubygems
|
2
2
|
|
3
|
-
gem 'activerecord',
|
3
|
+
gem 'activerecord', github: 'rails/rails'
|
4
|
+
gem 'active_record_deprecated_finders', github: 'rails/active_record_deprecated_finders'
|
4
5
|
|
5
6
|
group :development do
|
6
7
|
gem 'minitest'
|
7
8
|
gem 'purdytest'
|
8
|
-
gem 'rails',
|
9
|
+
gem 'rails', github: 'rails/rails'
|
9
10
|
gem 'sqlite3'
|
10
11
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module ActiveRecord::Duplicate
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
define_callbacks :duplication, terminator: "result == false", scope: [:kind, :name]
|
6
|
+
|
7
|
+
class_attribute :_duplicatable_attributes
|
8
|
+
attr_accessor :duplication_parent, :duplication_context
|
9
|
+
end
|
10
|
+
|
11
|
+
def duplicate
|
12
|
+
self.duplication_context = :original
|
13
|
+
self.run_callbacks(:duplication) do
|
14
|
+
dup.tap do |duplicate|
|
15
|
+
duplicate.duplication_context = :duplicate
|
16
|
+
duplicate.run_callbacks(:duplication) do
|
17
|
+
attributes.each do |key, value|
|
18
|
+
value = case true
|
19
|
+
|
20
|
+
# Duplicate attribute if whitelisted
|
21
|
+
when self.class.attr_duplicatable.include?(key.to_sym)
|
22
|
+
value
|
23
|
+
|
24
|
+
# If not whitelisted, set to default value
|
25
|
+
when (column = self.class.columns.detect { |c| c.name == key }).present?
|
26
|
+
column.default
|
27
|
+
|
28
|
+
else
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
duplicate.send(:"#{key}=", value)
|
33
|
+
end
|
34
|
+
|
35
|
+
self.class.reflect_on_all_associations.each do |association|
|
36
|
+
name = association.name
|
37
|
+
macro = association.macro
|
38
|
+
association = self.association(association.name)
|
39
|
+
|
40
|
+
case macro
|
41
|
+
when :belongs_to
|
42
|
+
# Duplicate all belongs_to associations.
|
43
|
+
if duplication_parent.is_a?(association.klass)
|
44
|
+
duplicate.send(:"#{name}=", duplication_parent)
|
45
|
+
duplicate.send(:"#{name}_id=", nil)
|
46
|
+
else
|
47
|
+
duplicate.send(:"#{name}=", send(name))
|
48
|
+
end
|
49
|
+
|
50
|
+
when :has_many
|
51
|
+
# Duplicate all has_many associations.
|
52
|
+
duplicate.send(:"#{name}=", send(name).map do |object|
|
53
|
+
object.duplication_parent = duplicate
|
54
|
+
object = object.duplicate
|
55
|
+
next unless object.present?
|
56
|
+
|
57
|
+
object
|
58
|
+
end.compact)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module ClassMethods
|
67
|
+
def attr_duplicatable(*attributes)
|
68
|
+
self._duplicatable_attributes = attributes.map(&:to_sym) if attributes.present?
|
69
|
+
self._duplicatable_attributes || []
|
70
|
+
end
|
71
|
+
|
72
|
+
# Duplicated from activemodel/lib/active_model/validations/callbacks.rb
|
73
|
+
def before_duplication(*args, &block)
|
74
|
+
options = args.last
|
75
|
+
|
76
|
+
if options.is_a?(Hash) && options[:on]
|
77
|
+
options[:if] = Array.wrap(options[:if])
|
78
|
+
options[:if].unshift("self.duplication_context == :#{options[:on]}")
|
79
|
+
end
|
80
|
+
|
81
|
+
set_callback(:duplication, :before, *args, &block)
|
82
|
+
end
|
83
|
+
|
84
|
+
def after_duplication(*args, &block)
|
85
|
+
options = args.extract_options!
|
86
|
+
|
87
|
+
options[:prepend] = true
|
88
|
+
options[:if] = Array.wrap(options[:if])
|
89
|
+
options[:if] << "!halted"
|
90
|
+
options[:if].unshift("self.duplication_context == :#{options[:on]}") if options[:on]
|
91
|
+
|
92
|
+
set_callback(:duplication, :after, *(args << options), &block)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/spec/callbacks_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.expand_path('../spec_helper', __FILE__)
|
2
2
|
|
3
|
-
describe ActiveRecord::Duplicate
|
3
|
+
describe ActiveRecord::Duplicate do
|
4
4
|
let(:klass) { Class.new(ActiveRecord::Base) { self.table_name = 'records' } }
|
5
5
|
|
6
6
|
|
@@ -16,7 +16,7 @@ describe ActiveRecord::Duplicate::Callbacks do
|
|
16
16
|
mock.expect(:after_duplication, nil, [:original])
|
17
17
|
mock.expect(:before_duplication, nil, [:duplicate])
|
18
18
|
mock.expect(:after_duplication, nil, [:duplicate])
|
19
|
-
|
19
|
+
|
20
20
|
klass.create.duplicate
|
21
21
|
mock.verify
|
22
22
|
end
|
data/spec/integration_spec.rb
CHANGED
@@ -32,23 +32,16 @@ describe 'Integration' do
|
|
32
32
|
class Post < ActiveRecord::Base
|
33
33
|
# Don't duplicate copyrighted posts
|
34
34
|
before_duplication { !self.is_copyrighted? }
|
35
|
-
after_duplication(
|
35
|
+
after_duplication(on: :duplicate) { self.title = title.match(/\d+$/) ? title.succ : "#{title} 2" }
|
36
36
|
|
37
37
|
belongs_to :blog
|
38
38
|
has_many :comments
|
39
39
|
has_many :ratings, as: :parent
|
40
40
|
|
41
|
-
attr_duplicatable :content
|
41
|
+
attr_duplicatable :title, :content
|
42
42
|
class_attribute :counter
|
43
43
|
|
44
44
|
validates :title, presence: true, uniqueness: true
|
45
|
-
|
46
|
-
private
|
47
|
-
def increase_counter
|
48
|
-
self.class.counter ||= 0
|
49
|
-
self.class.counter += 1
|
50
|
-
self.title = "Lorem #{self.class.counter}"
|
51
|
-
end
|
52
45
|
end
|
53
46
|
|
54
47
|
class Comment < ActiveRecord::Base
|
@@ -64,7 +57,7 @@ describe 'Integration' do
|
|
64
57
|
end
|
65
58
|
|
66
59
|
|
67
|
-
let(:blog) { Blog.create }
|
60
|
+
let(:blog) { Blog.create! }
|
68
61
|
|
69
62
|
|
70
63
|
describe 'duplicating blog' do
|
@@ -78,15 +71,15 @@ describe 'Integration' do
|
|
78
71
|
|
79
72
|
|
80
73
|
it 'duplicates posts too' do
|
81
|
-
|
82
|
-
post = blog.posts.create(title:
|
83
|
-
rating = post.ratings.create(value: 5)
|
74
|
+
%w[Sample Blog Post].each do |title|
|
75
|
+
post = blog.posts.create!(title: title)
|
76
|
+
rating = post.ratings.create!(value: 5)
|
84
77
|
end
|
85
78
|
|
86
79
|
subject.posts.all?(&:new_record?).must_equal(true)
|
87
80
|
subject.posts.size.must_equal(3)
|
88
81
|
|
89
|
-
subject.save
|
82
|
+
subject.save!
|
90
83
|
|
91
84
|
Blog.count.must_equal(2)
|
92
85
|
Post.count.must_equal(6)
|
@@ -95,28 +88,30 @@ describe 'Integration' do
|
|
95
88
|
|
96
89
|
|
97
90
|
it 'sets blog association' do
|
98
|
-
|
99
|
-
post = blog.posts.create(title:
|
100
|
-
rating = post.ratings.create(value: 5)
|
91
|
+
%w[Sample Blog Post].each do |title|
|
92
|
+
post = blog.posts.create!(title: title)
|
93
|
+
rating = post.ratings.create!(value: 5)
|
101
94
|
end
|
102
95
|
|
103
96
|
subject.posts.each do |post|
|
104
97
|
post.blog.must_equal(subject)
|
105
98
|
post.blog_id.must_be_nil
|
106
99
|
end
|
100
|
+
|
101
|
+
subject.save!
|
107
102
|
end
|
108
103
|
|
109
104
|
|
110
105
|
it 'ignores copyrighted posts' do
|
111
|
-
|
112
|
-
post = blog.posts.create(title:
|
113
|
-
rating = post.ratings.create(value: 5)
|
106
|
+
%w[Sample Blog Post].each do |title|
|
107
|
+
post = blog.posts.create!(title: title, is_copyrighted: title == 'Sample')
|
108
|
+
rating = post.ratings.create!(value: 5)
|
114
109
|
end
|
115
110
|
|
116
111
|
subject.posts.all?(&:new_record?).must_equal(true)
|
117
112
|
subject.posts.size.must_equal(2)
|
118
113
|
|
119
|
-
subject.save
|
114
|
+
subject.save!
|
120
115
|
|
121
116
|
Blog.count.must_equal(2)
|
122
117
|
Post.count.must_equal(5)
|
@@ -124,27 +119,29 @@ describe 'Integration' do
|
|
124
119
|
end
|
125
120
|
|
126
121
|
|
127
|
-
it 'ignores posts
|
128
|
-
post = blog.posts.create(title: 'Post', content: 'Lorem', published_at: Time.now)
|
129
|
-
rating = post.ratings.create(value: 5)
|
122
|
+
it 'ignores posts published_at timestamp' do
|
123
|
+
post = blog.posts.create!(title: 'Post', content: 'Lorem', published_at: Time.now)
|
124
|
+
rating = post.ratings.create!(value: 5)
|
130
125
|
|
131
126
|
post = subject.posts.first
|
132
127
|
post.wont_be_nil
|
133
128
|
post.content.must_equal('Lorem')
|
134
129
|
post.published_at.must_be_nil
|
130
|
+
|
131
|
+
subject.save!
|
135
132
|
end
|
136
133
|
|
137
134
|
|
138
135
|
it 'wont duplicate comments' do
|
139
|
-
post = blog.posts.create(title: 'Post')
|
140
|
-
rating = post.ratings.create(value: 5)
|
141
|
-
3.times { post.comments.create }
|
136
|
+
post = blog.posts.create!(title: 'Post')
|
137
|
+
rating = post.ratings.create!(value: 5)
|
138
|
+
3.times { post.comments.create! }
|
142
139
|
|
143
140
|
post = subject.posts.first
|
144
141
|
post.wont_be_nil
|
145
142
|
post.comments.size.must_equal(0)
|
146
143
|
|
147
|
-
subject.save
|
144
|
+
subject.save!
|
148
145
|
|
149
146
|
Blog.count.must_equal(2)
|
150
147
|
Post.count.must_equal(2)
|
@@ -156,9 +153,10 @@ describe 'Integration' do
|
|
156
153
|
|
157
154
|
describe 'duplicating post' do
|
158
155
|
it 'sets blog association' do
|
159
|
-
post = blog.posts.create
|
156
|
+
post = blog.posts.create!(title: 'Post')
|
160
157
|
|
161
158
|
post = post.duplicate
|
159
|
+
post.save!
|
162
160
|
|
163
161
|
post.blog.must_equal(blog)
|
164
162
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-duplicate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-04-
|
12
|
+
date: 2012-04-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
16
|
-
requirement: &
|
16
|
+
requirement: &70282697881420 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '3.1'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70282697881420
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: minitest
|
27
|
-
requirement: &
|
27
|
+
requirement: &70282697880800 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70282697880800
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: purdytest
|
38
|
-
requirement: &
|
38
|
+
requirement: &70282697880060 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70282697880060
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rails
|
49
|
-
requirement: &
|
49
|
+
requirement: &70282697879480 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '3.1'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70282697879480
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rake
|
60
|
-
requirement: &
|
60
|
+
requirement: &70282697879060 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70282697879060
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: sqlite3
|
71
|
-
requirement: &
|
71
|
+
requirement: &70282697878580 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,7 +76,7 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70282697878580
|
80
80
|
description: Duplicating ActiveRecords is easy again.
|
81
81
|
email: uher.mario@gmail.com
|
82
82
|
executables: []
|
@@ -92,8 +92,7 @@ files:
|
|
92
92
|
- gemfiles/Rails_3_2.gemfile
|
93
93
|
- gemfiles/Rails_master.gemfile
|
94
94
|
- lib/activerecord-duplicate.rb
|
95
|
-
- lib/activerecord-duplicate/
|
96
|
-
- lib/activerecord-duplicate/active_record/duplicate/callbacks.rb
|
95
|
+
- lib/activerecord-duplicate/duplicate.rb
|
97
96
|
- lib/activerecord-duplicate/railtie.rb
|
98
97
|
- spec/acts_as_duplicator_spec.rb
|
99
98
|
- spec/callbacks_spec.rb
|
@@ -1,71 +0,0 @@
|
|
1
|
-
require 'activerecord-duplicate/active_record/duplicate/callbacks'
|
2
|
-
|
3
|
-
module ActiveRecord
|
4
|
-
module Duplicate
|
5
|
-
extend ActiveSupport::Concern
|
6
|
-
|
7
|
-
included do
|
8
|
-
class_attribute :_duplicatable_attributes
|
9
|
-
|
10
|
-
attr_accessor :duplication_parent
|
11
|
-
|
12
|
-
include ActiveRecord::Duplicate::Callbacks
|
13
|
-
end
|
14
|
-
|
15
|
-
def duplicate
|
16
|
-
dup.tap do |duplicate|
|
17
|
-
attributes.each do |key, value|
|
18
|
-
value = case true
|
19
|
-
|
20
|
-
# Duplicate attribute if whitelisted
|
21
|
-
when self.class.attr_duplicatable.include?(key.to_sym)
|
22
|
-
value
|
23
|
-
|
24
|
-
# If not whitelisted, set to default value
|
25
|
-
when (column = self.class.columns.detect { |c| c.name == key }).present?
|
26
|
-
column.default
|
27
|
-
|
28
|
-
else
|
29
|
-
nil
|
30
|
-
end
|
31
|
-
|
32
|
-
duplicate.send(:"#{key}=", value)
|
33
|
-
end
|
34
|
-
|
35
|
-
self.class.reflect_on_all_associations.each do |association|
|
36
|
-
name = association.name
|
37
|
-
macro = association.macro
|
38
|
-
association = self.association(association.name)
|
39
|
-
|
40
|
-
case macro
|
41
|
-
when :belongs_to
|
42
|
-
# Duplicate all belongs_to associations.
|
43
|
-
if duplication_parent.is_a?(association.klass)
|
44
|
-
duplicate.send(:"#{name}=", duplication_parent)
|
45
|
-
duplicate.send(:"#{name}_id=", nil)
|
46
|
-
else
|
47
|
-
duplicate.send(:"#{name}=", send(name))
|
48
|
-
end
|
49
|
-
|
50
|
-
when :has_many
|
51
|
-
# Duplicate all has_many associations.
|
52
|
-
duplicate.send(:"#{name}=", send(name).map do |object|
|
53
|
-
object.duplication_parent = duplicate
|
54
|
-
object = object.duplicate
|
55
|
-
next unless object.present?
|
56
|
-
|
57
|
-
object
|
58
|
-
end.compact)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
module ClassMethods
|
65
|
-
def attr_duplicatable(*attributes)
|
66
|
-
self._duplicatable_attributes = attributes.map(&:to_sym) if attributes.present?
|
67
|
-
self._duplicatable_attributes || []
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
@@ -1,49 +0,0 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Duplicate
|
3
|
-
module Callbacks
|
4
|
-
extend ActiveSupport::Concern
|
5
|
-
|
6
|
-
included do
|
7
|
-
define_callbacks :duplication, terminator: "result == false", scope: [:kind, :name]
|
8
|
-
attr_accessor :duplication_context
|
9
|
-
end
|
10
|
-
|
11
|
-
def dup
|
12
|
-
super.tap do |duplicate|
|
13
|
-
duplicate.duplication_context = :duplicate
|
14
|
-
duplicate.run_callbacks(:duplication)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def duplicate
|
19
|
-
self.duplication_context = :original
|
20
|
-
self.run_callbacks(:duplication) { super }
|
21
|
-
end
|
22
|
-
|
23
|
-
module ClassMethods
|
24
|
-
# Duplicated from activemodel/lib/active_model/validations/callbacks.rb
|
25
|
-
def before_duplication(*args, &block)
|
26
|
-
options = args.last
|
27
|
-
|
28
|
-
if options.is_a?(Hash) && options[:on]
|
29
|
-
options[:if] = Array.wrap(options[:if])
|
30
|
-
options[:if].unshift("self.duplication_context == :#{options[:on]}")
|
31
|
-
end
|
32
|
-
|
33
|
-
set_callback(:duplication, :before, *args, &block)
|
34
|
-
end
|
35
|
-
|
36
|
-
def after_duplication(*args, &block)
|
37
|
-
options = args.extract_options!
|
38
|
-
|
39
|
-
options[:prepend] = true
|
40
|
-
options[:if] = Array.wrap(options[:if])
|
41
|
-
options[:if] << "!halted"
|
42
|
-
options[:if].unshift("self.duplication_context == :#{options[:on]}") if options[:on]
|
43
|
-
|
44
|
-
set_callback(:duplication, :after, *(args << options), &block)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|