temping 3.10.0 → 4.0.0

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
- 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