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 +5 -5
- data/lib/temping/model_factory.rb +69 -0
- data/lib/temping/namespace_factory.rb +24 -0
- data/lib/temping.rb +116 -61
- metadata +97 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 17a3e61694c280ee41ecaba6d9ba29f7815dd0ae7126bb931872819669ee03e7
|
4
|
+
data.tar.gz: ce7e225a2e34ed393ca11fe8d5cb1a5695806142282d940339fbf991f24a45b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
@
|
10
|
+
@namespaces = []
|
11
|
+
@models = []
|
6
12
|
|
7
13
|
class << self
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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 @
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
@
|
52
|
+
@models.reverse_each(&:destroy_all)
|
30
53
|
end
|
31
|
-
end
|
32
54
|
|
33
|
-
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
81
|
-
|
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
|
-
|
87
|
-
|
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
|
-
|
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:
|
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:
|
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: '
|
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: '
|
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: '
|
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: '
|
49
|
+
version: '5.2'
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '7.1'
|
41
53
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
54
|
+
name: appraisal
|
43
55
|
requirement: !ruby/object:Gem::Requirement
|
44
56
|
requirements:
|
45
57
|
- - "~>"
|
46
58
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
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:
|
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:
|
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:
|
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.
|
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.
|
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.
|
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.
|
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:
|
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:
|
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:
|
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
|
-
|
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
|