temping 3.9.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: 854682e673db78262df86716d1f51432af84aa72
4
- data.tar.gz: f8e95dc7625c179eecf3a9891e01bfac673205ab
2
+ SHA256:
3
+ metadata.gz: 17a3e61694c280ee41ecaba6d9ba29f7815dd0ae7126bb931872819669ee03e7
4
+ data.tar.gz: ce7e225a2e34ed393ca11fe8d5cb1a5695806142282d940339fbf991f24a45b9
5
5
  SHA512:
6
- metadata.gz: 08cbb126b85f7452c3a986edf1679a99a18b4b8dfcd9b9e8af874208dea32ec3044bcd83239840ce0a88bfe323388a31b63c031248f15c1be0925fb97144e210
7
- data.tar.gz: 65e0a023f58fcc3baae537f1c073565d74844bdcb192559ad41077b7f13d83dd9a1247031c123d331a9eaf44ce6d126d0e9f3ec0a43c56ab1377f0b77bfa19dd
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.9.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-03-16 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,112 +16,170 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '3.1'
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: '3.1'
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: '3.1'
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: '3.1'
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
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '1.2'
94
+ - - "<"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
97
+ type: :development
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '1.2'
104
+ - - "<"
105
+ - !ruby/object:Gem::Version
106
+ version: '2.0'
107
+ - !ruby/object:Gem::Dependency
108
+ name: mysql2
57
109
  requirement: !ruby/object:Gem::Requirement
58
110
  requirements:
59
111
  - - "~>"
60
112
  - !ruby/object:Gem::Version
61
- version: 0.18.2
113
+ version: '0.5'
62
114
  type: :development
63
115
  prerelease: false
64
116
  version_requirements: !ruby/object:Gem::Requirement
65
117
  requirements:
66
118
  - - "~>"
67
119
  - !ruby/object:Gem::Version
68
- version: 0.18.2
120
+ version: '0.5'
69
121
  - !ruby/object:Gem::Dependency
70
- name: mysql
122
+ name: rspec
71
123
  requirement: !ruby/object:Gem::Requirement
72
124
  requirements:
73
125
  - - "~>"
74
126
  - !ruby/object:Gem::Version
75
- version: 2.9.1
127
+ version: '3.12'
76
128
  type: :development
77
129
  prerelease: false
78
130
  version_requirements: !ruby/object:Gem::Requirement
79
131
  requirements:
80
132
  - - "~>"
81
133
  - !ruby/object:Gem::Version
82
- version: 2.9.1
134
+ version: '3.12'
83
135
  - !ruby/object:Gem::Dependency
84
- name: mysql2
136
+ name: rake
85
137
  requirement: !ruby/object:Gem::Requirement
86
138
  requirements:
87
139
  - - "~>"
88
140
  - !ruby/object:Gem::Version
89
- version: 0.3.18
141
+ version: '13.0'
90
142
  type: :development
91
143
  prerelease: false
92
144
  version_requirements: !ruby/object:Gem::Requirement
93
145
  requirements:
94
146
  - - "~>"
95
147
  - !ruby/object:Gem::Version
96
- version: 0.3.18
148
+ version: '13.0'
97
149
  - !ruby/object:Gem::Dependency
98
- name: rspec
150
+ name: simplecov
99
151
  requirement: !ruby/object:Gem::Requirement
100
152
  requirements:
101
- - - ">="
153
+ - - "~>"
102
154
  - !ruby/object:Gem::Version
103
- version: 3.4.0
155
+ version: '0.17'
104
156
  type: :development
105
157
  prerelease: false
106
158
  version_requirements: !ruby/object:Gem::Requirement
107
159
  requirements:
108
- - - ">="
160
+ - - "~>"
109
161
  - !ruby/object:Gem::Version
110
- version: 3.4.0
162
+ version: '0.17'
111
163
  - !ruby/object:Gem::Dependency
112
- name: rake
164
+ name: standard
113
165
  requirement: !ruby/object:Gem::Requirement
114
166
  requirements:
115
167
  - - ">="
116
168
  - !ruby/object:Gem::Version
117
- version: 10.0.4
169
+ version: 0.0.1
170
+ - - "<"
171
+ - !ruby/object:Gem::Version
172
+ version: '2.0'
118
173
  type: :development
119
174
  prerelease: false
120
175
  version_requirements: !ruby/object:Gem::Requirement
121
176
  requirements:
122
177
  - - ">="
123
178
  - !ruby/object:Gem::Version
124
- version: 10.0.4
179
+ version: 0.0.1
180
+ - - "<"
181
+ - !ruby/object:Gem::Version
182
+ version: '2.0'
125
183
  description:
126
184
  email: john@pignata.com
127
185
  executables: []
@@ -129,8 +187,11 @@ extensions: []
129
187
  extra_rdoc_files: []
130
188
  files:
131
189
  - lib/temping.rb
190
+ - lib/temping/model_factory.rb
191
+ - lib/temping/namespace_factory.rb
132
192
  homepage: http://github.com/jpignata/temping
133
- licenses: []
193
+ licenses:
194
+ - MIT
134
195
  metadata: {}
135
196
  post_install_message:
136
197
  rdoc_options: []
@@ -140,15 +201,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
140
201
  requirements:
141
202
  - - ">="
142
203
  - !ruby/object:Gem::Version
143
- version: '0'
204
+ version: 2.2.2
144
205
  required_rubygems_version: !ruby/object:Gem::Requirement
145
206
  requirements:
146
207
  - - ">="
147
208
  - !ruby/object:Gem::Version
148
209
  version: '0'
149
210
  requirements: []
150
- rubyforge_project:
151
- rubygems_version: 2.6.10
211
+ rubygems_version: 3.0.3
152
212
  signing_key:
153
213
  specification_version: 4
154
214
  summary: Create temporary table-backed ActiveRecord models for use in tests