sequel_deep_dup 0.1.1 → 0.2.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.
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: