sequel_deep_dup 0.1.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dde7ced04065758f9cc497072032be96caf0795d
4
- data.tar.gz: 5aa9acc6054864fdd7fa8360217edcafa36fb2eb
3
+ metadata.gz: 490afcfe5a51e4e24da754246b8a88d4f76d6ac9
4
+ data.tar.gz: 8473904ed8790f08ab4808abb55843ae329851ba
5
5
  SHA512:
6
- metadata.gz: e7531307317303235bcd0596701770aad8b126b04c9cc1da3d42663dcb15dc59acccb357a6cd9caf92b95971e8d66cb5547b68a06abf538bc70910a610216b75
7
- data.tar.gz: a7868f64e6cec794d6d4867d3794d954920e16787da4a60b74932b31433620914b8459e7c543c3c0e9273cf3d8a096a22949865e489f20af29cab0587eaf81b6
6
+ metadata.gz: 57d4409e59d8a47cc099bc24db359628b3f9962b15985dbc201fb61becf9845a3fd3add1c34a810f0106ab05ca79ff6172aedfcf54fccb73d766f469a50423f1
7
+ data.tar.gz: 3a50f39744efb83cd67c43b3e4d053ddce87356c11d4d9e8fa394bc33c7e75597d6110bf77a01be7e400f368c345eacd5e766d8df73538f9833b0f21cf422155
data/Gemfile CHANGED
@@ -6,3 +6,4 @@ gemspec
6
6
  gem 'rspec'
7
7
  gem 'sqlite3'
8
8
  gem 'sequel'
9
+ gem 'factory_girl'
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  module Plugins
3
3
  module DeepDup
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.1"
5
5
  end
6
6
  end
7
7
  end
@@ -5,24 +5,18 @@ module Sequel
5
5
  module Plugins
6
6
  module DeepDup
7
7
  class DeepDupper
8
- attr_reader :instance, :omit_records
8
+ attr_reader :instance, :omit_records, :associations
9
9
 
10
- def initialize instance, omit = []
10
+ def initialize instance, opts = {}
11
11
  @instance = instance
12
- @omit_records = omit
13
- end
14
-
15
- def dup_associations instance, copy, associations
16
- associations.each do |name|
17
- next unless refl = instance.class.association_reflection(name)
18
- [*instance.send(name)].compact.each { |rec| instantiate_associated(copy, refl, rec) }
19
- end
12
+ @associations = opts[:associations]
13
+ @omit_records = opts[:omit_records] || []
20
14
  end
21
15
 
22
16
  def dup
23
17
  copy = shallow_dup.extend(InstanceHooks::InstanceMethods)
24
18
  omit_records << instance
25
- dup_associations(instance, copy, instance.class.associations)
19
+ dup_associations(instance, copy, associations)
26
20
  copy
27
21
  end
28
22
 
@@ -33,12 +27,38 @@ module Sequel
33
27
  klass.new attributes
34
28
  end
35
29
 
30
+ def dup_associations instance, copy, includes = nil
31
+ includes &&= includes.map { |item| [*item].flatten }
32
+ associations = instance.class.associations
33
+
34
+ ([*includes].map{ |i| i.first } - associations).each do |assoc|
35
+ raise(Error, "no association named #{assoc} for #{instance}")
36
+ end
37
+
38
+ associations.each do |name|
39
+ next unless refl = instance.class.association_reflection(name)
40
+ [*instance.send(name)].compact.each do |rec|
41
+ if includes
42
+ next unless graph = includes.assoc(refl[:name])
43
+ instantiate_associated(copy, refl, rec, graph[1..-1])
44
+ else
45
+ next copy.values.delete(refl[:key]) if refl[:type] == :many_to_one
46
+ instantiate_associated(copy, refl, rec, nil)
47
+ end
48
+ end
49
+ end
50
+ end
51
+
36
52
  private
37
- def instantiate_associated copy, reflection, record
53
+ def instantiate_associated copy, reflection, record, associations
38
54
  return if omit_records.detect { |to_omit| record.pk == to_omit.pk && record.class == to_omit.class }
39
55
 
40
56
  unless reflection[:type] == :many_to_many
41
- record = DeepDupper.new(record, omit_records).dup
57
+ record = DeepDupper.new(
58
+ record,
59
+ :omit_records => omit_records,
60
+ :associations => associations
61
+ ).dup
42
62
  end
43
63
 
44
64
  if reflection.returns_array?
@@ -46,12 +66,11 @@ module Sequel
46
66
  copy.after_save_hook{ copy.send(reflection.add_method, record) }
47
67
  else
48
68
  copy.associations[reflection[:name]] = record
49
-
50
69
  copy.instance_variable_set :@set_associated_object_if_same, true
51
70
 
52
- if reflection[:type] == :many_to_one
71
+ if reflection[:type] == :many_to_one
53
72
  copy.before_save_hook {
54
- copy.send reflection.setter_method, record.save(:validate=>false)
73
+ copy.send reflection.setter_method, record.save
55
74
  }
56
75
  else
57
76
  copy.after_save_hook{
@@ -63,8 +82,9 @@ module Sequel
63
82
  end
64
83
 
65
84
  module InstanceMethods
66
- def deep_dup
67
- DeepDupper.new(self).dup
85
+ def deep_dup *associations
86
+ associations = nil if associations.empty?
87
+ DeepDupper.new(self, :associations => associations).dup
68
88
  end
69
89
  end
70
90
  end
@@ -1,127 +1,165 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Sequel::Plugins::DeepDup do
4
- let(:program) { Program.create name: 'CS' }
4
+ let(:program) { create :program, :name =>'CS' }
5
5
  let(:program_copy) { program.deep_dup }
6
- let(:course) { Course.create name: 'Ruby', program: program }
6
+ let(:course) { create :course, :name => 'Ruby', :program => program }
7
7
  let(:course_copy) { course.deep_dup }
8
- let(:student) { Student.create name: 'Macario' }
8
+ let(:student) { create :student, :name => 'Macario' }
9
9
  let(:student_copy) { student.deep_dup }
10
- let(:enrollment) { Enrollment.create(student_id: student.id, course_id: course.id) }
10
+ let(:enrollment) { create :enrollment, :student_id => student.id, :course_id => course.id }
11
11
  let(:enrollment_copy) { enrollment.deep_dup }
12
12
 
13
- describe 'duplicating plain record' do
14
-
13
+ context 'no explicit assoc graph is passed' do
15
14
  before do
16
- Program.plugin :deep_dup
17
- Enrollment.plugin :deep_dup
15
+ enable_deep_dup_for Program, Course, Enrollment, Student, Profile, Account
18
16
  end
19
17
 
20
- it { program_copy.name.should == 'CS' }
21
- it { program_copy.pk.should be_nil }
22
- it { program_copy.should be_new }
23
- it { enrollment_copy.pk.should == [nil, nil] }
24
- it { enrollment_copy.should be_new }
25
- end
18
+ describe 'duplication plain record' do
19
+ context 'with regular pk' do
20
+ it { program_copy.name.should == 'CS' }
21
+ it { program_copy.pk.should be_nil }
22
+ end
26
23
 
27
- describe 'duplicating record with a one to many association' do
28
- before do
29
- Program.plugin :deep_dup
30
- 3.times { |num| program.add_course name: "Course #{num}" }
24
+ context 'with composite pks' do
25
+ it { program_copy.should be_new }
26
+ it { enrollment_copy.pk.should == [nil, nil] }
27
+ it { enrollment_copy.should be_new }
28
+ end
31
29
  end
32
30
 
33
- it { program_copy.should have(3).courses }
34
- it { program_copy.courses.map(&:pk).should be_none }
35
- it { expect { program_copy.save }.to change{ Course.count }.to(6) }
36
- end
31
+ describe 'duplicating one to many children' do
32
+ before do
33
+ 3.times { |num| program.add_course :name => "Course #{num}" }
34
+ end
37
35
 
38
- describe 'duplicating record with a many to many association' do
39
- before do
40
- Course.plugin :deep_dup
41
- 3.times { |num| course.add_category name: "Category #{num}" }
36
+ context 'with no nesting' do
37
+ it { program_copy.should have(3).courses }
38
+ it { program_copy.courses.map(&:pk).should be_none }
39
+ it { expect { program_copy.save }.to change{ Course.count }.to(6) }
40
+ end
41
+
42
+ context 'with nesting' do
43
+ before do
44
+ program.courses.each do |course|
45
+ 3.times { |num| course.add_assignment :name => "Assignment #{num}" }
46
+ end
47
+ end
48
+
49
+ it { program_copy.should have(3).courses }
50
+ it { program_copy.courses.first.should have(3).assignments }
51
+ it { expect { program_copy.save }.to change{ Course.count }.to(6) }
52
+ it { expect { program_copy.save }.to change{ Assignment.count }.to(18) }
53
+ end
42
54
  end
43
55
 
44
- it { course_copy.should have(3).categories }
45
- it { course_copy.categories.should == course.categories }
46
- it { expect { course_copy.save }.not_to change{ Category.count } }
47
- end
56
+ describe 'reasociating to many to many' do
57
+ context 'when has many to many children' do
58
+ before do
59
+ 3.times { |num| course.add_category :name => "Category #{num}" }
60
+ end
48
61
 
49
- describe 'duplicating record with nested many to one' do
50
- before do
51
- Program.plugin :deep_dup
52
- 3.times { |num| program.add_course name: "Course #{num}" }
62
+ it { course_copy.should have(3).categories }
63
+ it { course_copy.categories.should == course.categories }
64
+ it { expect { course_copy.save }.not_to change{ Category.count } }
65
+ end
53
66
 
54
- program.courses.each do |course|
55
- 3.times { |num| course.add_assignment name: "Assignment #{num}" }
67
+ context 'when child has many to many children' do
68
+ before do
69
+ 3.times { |num| program.add_course :name => "Course #{num}" }
70
+ program.courses.each do |course|
71
+ 3.times { |num| course.add_category :name => "Category #{num}" }
72
+ end
73
+ end
74
+
75
+ it { program_copy.should have(3).courses }
76
+ it { program_copy.courses.first.should have(3).categories }
77
+ it { program_copy.courses.last.should have(3).categories }
78
+ it { expect { program_copy.save }.not_to change{ Category.count } }
56
79
  end
57
80
  end
58
81
 
59
- it { program_copy.should have(3).courses }
60
- it { program_copy.courses.first.should have(3).assignments }
61
- it { expect { program_copy.save }.to change{ Course.count }.to(6) }
62
- it { expect { program_copy.save }.to change{ Assignment.count }.to(18) }
63
- end
82
+ describe 'duplicating record with one to one' do
83
+ context 'when regular fk' do
84
+ before { student.account = Account.new(:email => 'mail@makarius.me') }
85
+ it { student_copy.account.pk.should be_nil }
86
+ it { student_copy.account.email.should == 'mail@makarius.me' }
87
+ it { expect { student_copy.save }.to change { Account.count }.to(2) }
88
+ end
64
89
 
65
- describe 'duplicating record with a many to many association after a many to one' do
66
- before do
67
- Program.plugin :deep_dup
68
- 3.times { |num| program.add_course name: "Course #{num}" }
69
- program.courses.each do |course|
70
- 3.times { |num| course.add_category name: "Category #{num}" }
90
+ context 'when foreign key is also pk' do
91
+ before do
92
+ student.profile = create(:profile, :bio => 'likes sequel, rides bycicle', :student => student)
93
+ end
94
+ it { student_copy.profile.pk.should be_nil }
95
+ it { student_copy.profile.bio.should == 'likes sequel, rides bycicle' }
96
+ it { expect { student_copy.save }.to change { Profile.count }.to(2) }
71
97
  end
72
98
  end
73
-
74
- it { program_copy.should have(3).courses }
75
- it { program_copy.courses.first.should have(3).categories }
76
- it { program_copy.courses.last.should have(3).categories }
77
- it { expect { program_copy.save }.not_to change{ Category.count } }
78
- end
79
99
 
80
- describe 'duplicating record with one to one' do
81
- before do
82
- Student.plugin :deep_dup
83
- student.account = Account.new(email: 'mail@makarius.me')
84
- end
100
+ describe 'omits many to one association' do
101
+ context 'when regular fk' do
102
+ let(:account) { create(:account, :email => 'mail@makarius.me', :student => student) }
103
+ let(:account_copy) { account.deep_dup }
104
+ it { account_copy.student_id.should be_nil }
105
+ it { account_copy.student.should be_nil }
106
+ it { account_copy.email.should == 'mail@makarius.me' }
107
+ end
85
108
 
86
- it { student_copy.account.pk.should be_nil }
87
- it { student_copy.account.email.should == 'mail@makarius.me' }
88
- it { expect { student_copy.save }.to change { Account.count }.to(2) }
109
+ context 'when foreign key is also pk' do
110
+ let(:profile) { create(:profile, :bio => 'likes sequel, rides bycicle', :student => student) }
111
+ let(:profile_copy) { profile.deep_dup }
112
+ it { profile_copy.student.should be_nil }
113
+ it { profile_copy.student_id.should be_nil }
114
+ it { profile_copy.bio.should == 'likes sequel, rides bycicle' }
115
+ end
116
+ end
89
117
  end
90
118
 
91
- describe 'duplicating record with many to one association' do
119
+ describe 'restrictions' do
92
120
  before do
93
- Account.plugin :deep_dup
121
+ Program.plugin :deep_dup
94
122
  end
95
123
 
96
- let(:account) { Account.create(email: 'mail@makarius.me', student: student) }
97
- let(:account_copy) { account.deep_dup }
98
-
99
- it { account_copy.student.pk.should be_nil }
100
- it { account_copy.student.name.should == 'Macario' }
101
- it { expect { account_copy.save }.to change { Student.count }.to(2) }
102
- end
124
+ let!(:program) { create :program, :with_graph }
103
125
 
104
- describe 'duplicating record with one to one when foreign key is pk' do
105
- before do
106
- Student.plugin :deep_dup
107
- student.profile = Profile.create(bio: 'likes sequel, rides bycicle', student: student)
126
+ describe 'validate graph' do
127
+ it { Course.count.should be 3 }
128
+ it { Assignment.count.should be 9 }
129
+ it { Enrollment.count.should be 9 }
130
+ it { Student.count.should be 9 }
131
+ it { Account.count.should be 9 }
132
+ it { Profile.count.should be 9 }
133
+ it { Category.count.should be 9 }
108
134
  end
109
135
 
110
- it { student_copy.profile.pk.should be_nil }
111
- it { student_copy.profile.bio.should == 'likes sequel, rides bycicle' }
112
- it { expect { student_copy.save }.to change { Profile.count }.to(2) }
113
- end
114
-
115
- describe 'duplicating record with many to one association when foreign key is pk' do
116
- before do
117
- Profile.plugin :deep_dup
136
+ describe 'restricts to children' do
137
+ let(:program_copy) { program.deep_dup :courses }
138
+ it { expect { program_copy.save }.to change{ Course.count }.by(3) }
139
+ it { expect { program_copy.save }.not_to change{ Enrollment.count } }
140
+ end
141
+
142
+ describe 'restricts to children of children' do
143
+ let(:program_copy) { program.deep_dup :courses => :assignments }
144
+ it { expect { program_copy.save }.to change{ Course.count }.by(3) }
145
+ it { expect { program_copy.save }.to change{ Assignment.count }.by(9) }
146
+ it { expect { program_copy.save }.not_to change{ Enrollment.count } }
118
147
  end
119
148
 
120
- let(:profile) { Profile.create(bio: 'likes sequel, rides bycicle', student: student) }
121
- let(:profile_copy) { profile.deep_dup }
149
+ describe 'restricts to graph' do
150
+ let(:program_copy) { program.deep_dup :courses => [:assignments, :categories, {:enrollments => {:student => [:profile, :account]}}] }
151
+ it { expect { program_copy.save }.to change { Course.count }.by(3) }
152
+ it { expect { program_copy.save }.to change { Assignment.count }.by(9) }
153
+ it { expect { program_copy.save }.to change { Enrollment.count }.by(9) }
154
+ it { expect { program_copy.save }.to change { Student.count }.by(9) }
155
+ it { expect { program_copy.save }.to change { Account.count }.by(9) }
156
+ it { expect { program_copy.save }.to change { Profile.count }.by(9) }
157
+ it { expect { program_copy.save }.to change { DB[:course_categories].count }.by(9) }
158
+ it { expect { program_copy.save }.not_to change { Category.count } }
159
+ end
122
160
 
123
- it { profile_copy.student.pk.should be_nil }
124
- it { profile_copy.student.name.should == 'Macario' }
125
- it { expect { profile_copy.save }.to change { Student.count }.to(2) }
161
+ it 'raises exception when association present in graph is not defined in model' do
162
+ expect { program.deep_dup :potatoes }.to raise_error(Sequel::Error)
163
+ end
126
164
  end
127
165
  end
data/spec/spec_helper.rb CHANGED
@@ -9,8 +9,16 @@ load 'support/migrations.rb'
9
9
  DB = Sequel.sqlite
10
10
  Sequel::Migration.descendants.each { |m| m.apply(DB, :up) }
11
11
 
12
+ module Helpers
13
+ def enable_deep_dup_for *models
14
+ models.each { |model| model.plugin :deep_dup }
15
+ end
16
+ end
12
17
 
13
18
  RSpec.configure do |config|
19
+ config.include FactoryGirl::Syntax::Methods
20
+ config.include Helpers
21
+
14
22
  config.around :each do |example|
15
23
  load 'support/models.rb'
16
24
 
@@ -24,3 +32,6 @@ RSpec.configure do |config|
24
32
  end
25
33
  end
26
34
  end
35
+
36
+ FactoryGirl.definition_file_paths = %w(spec/support)
37
+ FactoryGirl.reload
@@ -0,0 +1,59 @@
1
+ FactoryGirl.define do
2
+ factory :program do
3
+ to_create { |instance| instance.save }
4
+ sequence(:name) { |num| "Program #{num}" }
5
+
6
+ trait :with_graph do
7
+ courses_attributes { attributes_for_list(:course, 3, :with_graph) }
8
+ end
9
+ end
10
+
11
+ factory :course do
12
+ to_create { |instance| instance.save }
13
+ sequence(:name) { |num| "Course #{num}" }
14
+
15
+ trait :with_graph do
16
+ assignments_attributes { attributes_for_list(:assignment, 3) }
17
+ enrollments_attributes { attributes_for_list(:enrollment, 3, :with_graph) }
18
+ categories_attributes { attributes_for_list(:category, 3) }
19
+ end
20
+ end
21
+
22
+ factory :assignment do
23
+ to_create { |instance| instance.save }
24
+ sequence(:name) { |num| "Assignment #{num}" }
25
+ end
26
+
27
+ factory :student do
28
+ to_create { |instance| instance.save }
29
+ sequence(:name) { |num| "Student #{num}" }
30
+
31
+ trait :with_graph do
32
+ profile_attributes { attributes_for(:profile) }
33
+ account_attributes { attributes_for(:account) }
34
+ end
35
+ end
36
+
37
+ factory :account do
38
+ to_create { |instance| instance.save }
39
+ sequence(:email) { |num| "student-#{num}@example.com" }
40
+ end
41
+
42
+ factory :profile do
43
+ to_create { |instance| instance.save }
44
+ sequence(:bio) { |num| "Student #{num}: lorem ipsum..." }
45
+ end
46
+
47
+ factory :enrollment do
48
+ to_create { |instance| instance.save }
49
+
50
+ trait :with_graph do
51
+ student_attributes { attributes_for(:student, :with_graph) }
52
+ end
53
+ end
54
+
55
+ factory :category do
56
+ to_create { |instance| instance.save }
57
+ sequence(:name) { |num| "Category #{num}" }
58
+ end
59
+ end
@@ -7,7 +7,7 @@ Sequel.migration do
7
7
 
8
8
  create_table :courses do
9
9
  primary_key :id
10
- foreign_key :program_id, :programs, :on_delete => :restrict, :on_update => :restrict, :null => false
10
+ foreign_key :program_id, :programs, :on_delete => :restrict, :on_update => :restrict
11
11
  String :name, null: false
12
12
  Date :starts
13
13
  Date :ends
@@ -1,21 +1,44 @@
1
+ Sequel::Model.plugin :nested_attributes
2
+ Sequel::Model.plugin :auto_validations
3
+
1
4
  class Program < Sequel::Model
2
5
  one_to_many :courses
6
+
7
+ nested_attributes :courses
3
8
  end
4
9
 
5
10
  class Course < Sequel::Model
6
11
  many_to_one :program
7
12
  one_to_many :assignments
13
+ one_to_many :enrollments
14
+
8
15
  many_to_many :categories, join_table: :course_categories
16
+
17
+ nested_attributes :assignments
18
+ nested_attributes :enrollments
19
+ nested_attributes :categories
9
20
  end
10
21
 
11
22
  class Assignment < Sequel::Model
12
23
  many_to_one :course
13
24
  end
14
25
 
26
+ class Enrollment < Sequel::Model
27
+ unrestrict_primary_key
28
+ set_primary_key [:student_id, :course_id]
29
+ many_to_one :student
30
+ many_to_one :course
31
+
32
+ nested_attributes :student
33
+ end
34
+
15
35
  class Student < Sequel::Model
16
36
  one_to_many :enrollments
17
37
  one_to_one :account
18
38
  one_to_one :profile
39
+
40
+ nested_attributes :account
41
+ nested_attributes :profile
19
42
  end
20
43
 
21
44
  class Account < Sequel::Model
@@ -27,13 +50,6 @@ class Profile < Sequel::Model
27
50
  many_to_one :student
28
51
  end
29
52
 
30
- class Enrollment < Sequel::Model
31
- unrestrict_primary_key
32
- set_primary_key [:student_id, :course_id]
33
- many_to_one :student
34
- many_to_one :course
35
- end
36
-
37
53
  class Category < Sequel::Model
38
54
  many_to_many :courses, join_table: :course_categories
39
55
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel_deep_dup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - macario
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-31 00:00:00.000000000 Z
11
+ date: 2014-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -57,6 +57,7 @@ files:
57
57
  - sequel_deep_dup.gemspec
58
58
  - spec/deep_dup_spec.rb
59
59
  - spec/spec_helper.rb
60
+ - spec/support/factories.rb
60
61
  - spec/support/migrations.rb
61
62
  - spec/support/models.rb
62
63
  homepage: ''
@@ -87,6 +88,7 @@ summary: Makes deep copies of existing sequel models into a new record, along wi
87
88
  test_files:
88
89
  - spec/deep_dup_spec.rb
89
90
  - spec/spec_helper.rb
91
+ - spec/support/factories.rb
90
92
  - spec/support/migrations.rb
91
93
  - spec/support/models.rb
92
94
  has_rdoc: