temping 3.10.0 → 4.0.0

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
- SHA1:
3
- metadata.gz: 3f37e4189f710e304a6ec94e01d8691f84482b31
4
- data.tar.gz: ea35560ebafd11b1249279607720f55a87efc25b
2
+ SHA256:
3
+ metadata.gz: 17a3e61694c280ee41ecaba6d9ba29f7815dd0ae7126bb931872819669ee03e7
4
+ data.tar.gz: ce7e225a2e34ed393ca11fe8d5cb1a5695806142282d940339fbf991f24a45b9
5
5
  SHA512:
6
- metadata.gz: ab0d022c53993e077864c178b90c788bc383560c379ce9f74c6c15f43896b7803d68340846b88f51c7a3aeb8433879f74c192a45a1e95902632b1ced28316782
7
- data.tar.gz: 9b0d042c72114558ee70d33ea7bbce9a4611a309f1c75dcc494040f035b25daa9728290a61c6bc1f1cb58b338238bab72537bbde9eca40d6f6ce02b7bf856278
6
+ metadata.gz: 9aaf7aee2a796eb3a19df188097c548619665b1d37923d86a702657a28d9f0339daa0e48d62dfd2b2b27b31fe5a27704ff3103b6c342ecd3eaeacaadc3c46906
7
+ data.tar.gz: f0406caf8f32fcf950a5328eae4f46bb46573d50e0b1e58247fdeb0622552471c1a6ed0faa047b0535e35674fd79fd1b8d6ea0a73a200cf158fb4b3dfc7c4675
@@ -0,0 +1,69 @@
1
+ class Temping::ModelFactory
2
+ DEFAULT_OPTIONS = {temporary: true}
3
+
4
+ def initialize(name, namespace, options = {}, &block)
5
+ @name = name
6
+ @namespace = namespace
7
+ @options = options
8
+ klass.class_eval(&block) if block
9
+ klass.reset_column_information
10
+ end
11
+
12
+ def klass
13
+ @klass ||= @namespace.const_get(@name)
14
+ rescue NameError
15
+ @klass = build
16
+ end
17
+
18
+ private
19
+
20
+ def build
21
+ Class.new(parent_class_name).tap do |klass|
22
+ @namespace.const_set(@name, klass)
23
+ klass.primary_key = @options[:primary_key] || :id
24
+ create_table(@options)
25
+ add_methods
26
+ klass.namespace = @namespace
27
+ end
28
+ end
29
+
30
+ def parent_class_name
31
+ @options.fetch(:parent_class, default_parent_class_name)
32
+ end
33
+
34
+ def default_parent_class_name
35
+ if defined?(ApplicationRecord)
36
+ ApplicationRecord
37
+ else
38
+ ActiveRecord::Base
39
+ end
40
+ end
41
+
42
+ def create_table(options = {})
43
+ connection.create_table(table_name, **DEFAULT_OPTIONS.merge(options))
44
+ end
45
+
46
+ def add_methods
47
+ class << klass
48
+ attr_accessor :namespace
49
+
50
+ def with_columns
51
+ connection.change_table(table_name) do |table|
52
+ yield(table)
53
+ end
54
+ end
55
+
56
+ def table_exists?
57
+ true
58
+ end
59
+ end
60
+ end
61
+
62
+ def connection
63
+ klass.connection
64
+ end
65
+
66
+ def table_name
67
+ klass.table_name
68
+ end
69
+ end
@@ -0,0 +1,24 @@
1
+ class Temping::NamespaceFactory
2
+ def initialize(name)
3
+ @name = name
4
+ end
5
+
6
+ def klass
7
+ @klass ||= @name.split("::").reduce(Object) { |parent, name_part| build(parent, name_part) }
8
+ end
9
+
10
+ private
11
+
12
+ def build(parent, name_part)
13
+ parent.const_get(name_part)
14
+ rescue NameError
15
+ parent.const_set(
16
+ name_part,
17
+ Module.new do
18
+ def self.defined_by_temping?
19
+ true
20
+ end
21
+ end
22
+ )
23
+ end
24
+ end
data/lib/temping.rb CHANGED
@@ -1,94 +1,149 @@
1
1
  require "active_record"
2
2
  require "active_support/core_ext/string"
3
3
 
4
+ class Temping; end
5
+
6
+ require "temping/namespace_factory"
7
+ require "temping/model_factory"
8
+
4
9
  class Temping
5
- @model_klasses = []
10
+ @namespaces = []
11
+ @models = []
6
12
 
7
13
  class << self
8
- def create(model_name, options = {}, &block)
9
- factory = ModelFactory.new(model_name.to_s.classify, options, &block)
10
- klass = factory.klass
11
- @model_klasses << klass
12
- klass
14
+ # Create a new temporary ActiveRecord model with a name specified by `name`.
15
+ #
16
+ # Provided `options` are all passed to the inner `create_table` call so anything
17
+ # acceptable by `create_table` method can be passed here.
18
+ # In addition `options` can include `parent_class` key to specify parent class for the model.
19
+ # When `block` is passed, it is evaluated in the context of the class. This means anything you
20
+ # do in an ActiveRecord model class body can be accomplished in `block` including method
21
+ # definitions, validations, module includes, etc.
22
+ # Additional database columns can be specified via `with_columns` method inside `block`,
23
+ # which uses Rails migration syntax.
24
+ def create(name, options = {}, &block)
25
+ namespace_name, model_name = split_name(name)
26
+ namespace = namespace_name ? NamespaceFactory.new(namespace_name).klass : Object
27
+ @namespaces << namespace if namespace_name
28
+ model = ModelFactory.new(model_name, namespace, options, &block).klass
29
+ @models << model
30
+ model
13
31
  end
14
32
 
33
+ # Completely destroy everything created by Temping.
34
+ #
35
+ # This includes:
36
+ # * removing all the records in the models created by Temping and dropping their tables
37
+ # from the database;
38
+ # * undefining model constants so they cannot be pointed to anymore in the code.
15
39
  def teardown
16
- if @model_klasses.any?
17
- @model_klasses.each do |klass|
18
- if Object.const_defined?(klass.name)
19
- klass.connection.drop_table(klass.table_name)
20
- Object.send(:remove_const, klass.name)
21
- end
22
- end
23
- @model_klasses.clear
24
- ActiveSupport::Dependencies::Reference.clear!
40
+ if @models.any?
41
+ teardown_models
42
+ teardown_namespaces
43
+ ActiveSupport::Dependencies::Reference.clear! if ActiveRecord::VERSION::MAJOR < 7
25
44
  end
26
45
  end
27
46
 
47
+ # Destroy all records from each of the models created by Temping.
48
+ #
49
+ # This does not undefine the models themselves or drop their tables.
50
+ # This method is an alternative to `teardown` if you want to keep the models and tables.
28
51
  def cleanup
29
- @model_klasses.each(&:destroy_all)
52
+ @models.reverse_each(&:destroy_all)
30
53
  end
31
- end
32
54
 
33
- class ModelFactory
34
- def initialize(model_name, options = {}, &block)
35
- @model_name = model_name
36
- @options = options
37
- klass.class_eval(&block) if block_given?
38
- klass.reset_column_information
39
- end
55
+ # Split the provided name finding the namespace (if any) and the model name without namespace.
56
+ def split_name(name)
57
+ classified_name = name.to_s.classify
58
+ name_parts = classified_name.split("::")
59
+ namespace_name = name_parts[0...-1].join("::")
60
+ return [nil, classified_name] if namespace_name.empty?
40
61
 
41
- def klass
42
- @klass ||= Object.const_get(@model_name)
43
- rescue NameError
44
- @klass = build
62
+ [namespace_name, name_parts.last]
45
63
  end
64
+ private :split_name
46
65
 
47
- private
48
-
49
- def build
50
- Class.new(@options.fetch(:parent_class, default_parent_class)).tap do |klass|
51
- Object.const_set(@model_name, klass)
52
-
53
- klass.primary_key = @options[:primary_key] || :id
54
- create_table(@options)
55
- add_methods
66
+ # Iterate over `@models`, undefine model constants, drop tables, and remove the constants
67
+ # from the array one by one starting with the models defined last. (Models defined later
68
+ # can point to older models by using foreign keys so they have to be removed first).
69
+ def teardown_models
70
+ @models.reverse_each do |model|
71
+ model_name_without_namespace = model.name.split("::").last
72
+ if model.namespace.const_defined?(model_name_without_namespace)
73
+ model.connection.drop_table(model.table_name)
74
+ model.namespace.send(:remove_const, model_name_without_namespace)
75
+ end
56
76
  end
77
+ @models.clear
57
78
  end
79
+ private :teardown_models
58
80
 
59
- def default_parent_class
60
- if ActiveRecord::VERSION::MAJOR > 4 && defined?(ApplicationRecord)
61
- ApplicationRecord
62
- else
63
- ActiveRecord::Base
81
+ # Iterate over `@namespaces`, undefine modules and remove them from the array one by one
82
+ # starting with the deepest ones first.
83
+ def teardown_namespaces
84
+ @namespaces.select! { |namespace| namespace_still_defined?(namespace) }
85
+ until @namespaces.empty?
86
+ namespace, index = @namespaces.each_with_index.max_by { |n, _i| n.name.split("::").length }
87
+ parts = namespace.name.split("::")
88
+ parent = parts.length == 1 ? Object : parts[0...-1].join("::").constantize
89
+ parent.send(:remove_const, parts.last) if namespace_removable?(namespace, parts, parent)
90
+ delete_or_trim_in_namespaces(parent, parts, index)
64
91
  end
65
92
  end
93
+ private :teardown_namespaces
66
94
 
67
- DEFAULT_OPTIONS = { :temporary => true }
68
- def create_table(options = {})
69
- connection.create_table(table_name, DEFAULT_OPTIONS.merge(options))
70
- end
71
-
72
- def add_methods
73
- class << klass
74
- def with_columns
75
- connection.change_table(table_name) do |table|
76
- yield(table)
77
- end
78
- end
95
+ # Check if namespace is still defined.
96
+ # It could be already removed if it were inside a model created by Temping.
97
+ # Since `@models` are teared down first, it means that in such a case all modules that were
98
+ # inside that model are no longer defined.
99
+ def namespace_still_defined?(namespace)
100
+ parent = Object
101
+ outer_namespace_parts = []
102
+ namespace.to_s.split("::").each do |part|
103
+ return false unless parent.const_defined?(part)
79
104
 
80
- def table_exists?
81
- true
82
- end
105
+ outer_namespace_parts.push(part)
106
+ parent = outer_namespace_parts.join("::").constantize
83
107
  end
108
+ true
84
109
  end
110
+ private :namespace_still_defined?
85
111
 
86
- def connection
87
- klass.connection
112
+ # Namespace can be removed only if it's still defined and if it was created by Temping,
113
+ # we use `defined_by_temping?` to indicate the latter.
114
+ def namespace_removable?(namespace, parts, parent)
115
+ parent.const_defined?(parts.last) && namespace.defined_by_temping?
116
+ rescue NoMethodError
117
+ false
88
118
  end
119
+ private :namespace_removable?
120
+
121
+ # Clean `@namespaces` array by either removing current namespace or replacing it with its
122
+ # parent.
123
+ #
124
+ # Case 1: @namespaces = [A, B, C, D]; index = 3
125
+ # This is an outer-most module, we just remove it from `@namespaces`.
126
+ #
127
+ # Case 2: @namespaces = [A::B, A::B::C, A::D]; index = 1
128
+ # Once we remove C from A::B::C, it becomes A::B, but we already have A::B, so just remove it.
129
+ #
130
+ # Case 3: @namespaces = [A::B, A::D]; index = 1
131
+ # Once we remove D from A::D, it becomes A, replace A::D with A.
132
+ def delete_or_trim_in_namespaces(parent, parts, index)
133
+ is_last_module = parts.length == 1
134
+ if is_last_module
135
+ @namespaces.delete_at(index)
136
+ return
137
+ end
138
+
139
+ parent_already_in_namespaces = @namespaces.include?(parent)
140
+ if parent_already_in_namespaces
141
+ @namespaces.delete_at(index)
142
+ return
143
+ end
89
144
 
90
- def table_name
91
- klass.table_name
145
+ @namespaces[index] = parent
92
146
  end
147
+ private :delete_or_trim_in_namespaces
93
148
  end
94
149
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: temping
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.10.0
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Pignata
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-27 00:00:00.000000000 Z
11
+ date: 2022-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,98 +16,170 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.2'
19
+ version: '5.2'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '7.1'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - ">="
25
28
  - !ruby/object:Gem::Version
26
- version: '4.2'
29
+ version: '5.2'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '7.1'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: activesupport
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
37
  - - ">="
32
38
  - !ruby/object:Gem::Version
33
- version: '4.2'
39
+ version: '5.2'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '7.1'
34
43
  type: :runtime
35
44
  prerelease: false
36
45
  version_requirements: !ruby/object:Gem::Requirement
37
46
  requirements:
38
47
  - - ">="
39
48
  - !ruby/object:Gem::Version
40
- version: '4.2'
49
+ version: '5.2'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '7.1'
41
53
  - !ruby/object:Gem::Dependency
42
- name: sqlite3
54
+ name: appraisal
43
55
  requirement: !ruby/object:Gem::Requirement
44
56
  requirements:
45
57
  - - "~>"
46
58
  - !ruby/object:Gem::Version
47
- version: 1.3.10
59
+ version: '2.2'
48
60
  type: :development
49
61
  prerelease: false
50
62
  version_requirements: !ruby/object:Gem::Requirement
51
63
  requirements:
52
64
  - - "~>"
53
65
  - !ruby/object:Gem::Version
54
- version: 1.3.10
66
+ version: '2.2'
67
+ - !ruby/object:Gem::Dependency
68
+ name: sqlite3
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '1.3'
74
+ - - "<"
75
+ - !ruby/object:Gem::Version
76
+ version: '2.0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '1.3'
84
+ - - "<"
85
+ - !ruby/object:Gem::Version
86
+ version: '2.0'
55
87
  - !ruby/object:Gem::Dependency
56
88
  name: pg
57
89
  requirement: !ruby/object:Gem::Requirement
58
90
  requirements:
59
- - - "~>"
91
+ - - ">="
60
92
  - !ruby/object:Gem::Version
61
- version: 0.18.2
93
+ version: '1.2'
94
+ - - "<"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
62
97
  type: :development
63
98
  prerelease: false
64
99
  version_requirements: !ruby/object:Gem::Requirement
65
100
  requirements:
66
- - - "~>"
101
+ - - ">="
67
102
  - !ruby/object:Gem::Version
68
- version: 0.18.2
103
+ version: '1.2'
104
+ - - "<"
105
+ - !ruby/object:Gem::Version
106
+ version: '2.0'
69
107
  - !ruby/object:Gem::Dependency
70
108
  name: mysql2
71
109
  requirement: !ruby/object:Gem::Requirement
72
110
  requirements:
73
111
  - - "~>"
74
112
  - !ruby/object:Gem::Version
75
- version: 0.3.18
113
+ version: '0.5'
76
114
  type: :development
77
115
  prerelease: false
78
116
  version_requirements: !ruby/object:Gem::Requirement
79
117
  requirements:
80
118
  - - "~>"
81
119
  - !ruby/object:Gem::Version
82
- version: 0.3.18
120
+ version: '0.5'
83
121
  - !ruby/object:Gem::Dependency
84
122
  name: rspec
85
123
  requirement: !ruby/object:Gem::Requirement
86
124
  requirements:
87
- - - ">="
125
+ - - "~>"
88
126
  - !ruby/object:Gem::Version
89
- version: 3.4.0
127
+ version: '3.12'
90
128
  type: :development
91
129
  prerelease: false
92
130
  version_requirements: !ruby/object:Gem::Requirement
93
131
  requirements:
94
- - - ">="
132
+ - - "~>"
95
133
  - !ruby/object:Gem::Version
96
- version: 3.4.0
134
+ version: '3.12'
97
135
  - !ruby/object:Gem::Dependency
98
136
  name: rake
137
+ requirement: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - "~>"
140
+ - !ruby/object:Gem::Version
141
+ version: '13.0'
142
+ type: :development
143
+ prerelease: false
144
+ version_requirements: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - "~>"
147
+ - !ruby/object:Gem::Version
148
+ version: '13.0'
149
+ - !ruby/object:Gem::Dependency
150
+ name: simplecov
151
+ requirement: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - "~>"
154
+ - !ruby/object:Gem::Version
155
+ version: '0.17'
156
+ type: :development
157
+ prerelease: false
158
+ version_requirements: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - "~>"
161
+ - !ruby/object:Gem::Version
162
+ version: '0.17'
163
+ - !ruby/object:Gem::Dependency
164
+ name: standard
99
165
  requirement: !ruby/object:Gem::Requirement
100
166
  requirements:
101
167
  - - ">="
102
168
  - !ruby/object:Gem::Version
103
- version: 10.0.4
169
+ version: 0.0.1
170
+ - - "<"
171
+ - !ruby/object:Gem::Version
172
+ version: '2.0'
104
173
  type: :development
105
174
  prerelease: false
106
175
  version_requirements: !ruby/object:Gem::Requirement
107
176
  requirements:
108
177
  - - ">="
109
178
  - !ruby/object:Gem::Version
110
- version: 10.0.4
179
+ version: 0.0.1
180
+ - - "<"
181
+ - !ruby/object:Gem::Version
182
+ version: '2.0'
111
183
  description:
112
184
  email: john@pignata.com
113
185
  executables: []
@@ -115,6 +187,8 @@ extensions: []
115
187
  extra_rdoc_files: []
116
188
  files:
117
189
  - lib/temping.rb
190
+ - lib/temping/model_factory.rb
191
+ - lib/temping/namespace_factory.rb
118
192
  homepage: http://github.com/jpignata/temping
119
193
  licenses:
120
194
  - MIT
@@ -127,15 +201,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
127
201
  requirements:
128
202
  - - ">="
129
203
  - !ruby/object:Gem::Version
130
- version: '0'
204
+ version: 2.2.2
131
205
  required_rubygems_version: !ruby/object:Gem::Requirement
132
206
  requirements:
133
207
  - - ">="
134
208
  - !ruby/object:Gem::Version
135
209
  version: '0'
136
210
  requirements: []
137
- rubyforge_project:
138
- rubygems_version: 2.6.10
211
+ rubygems_version: 3.0.3
139
212
  signing_key:
140
213
  specification_version: 4
141
214
  summary: Create temporary table-backed ActiveRecord models for use in tests