temping 3.9.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +89 -29
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,112 +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
|
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.
|
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.
|
120
|
+
version: '0.5'
|
69
121
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
122
|
+
name: rspec
|
71
123
|
requirement: !ruby/object:Gem::Requirement
|
72
124
|
requirements:
|
73
125
|
- - "~>"
|
74
126
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
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:
|
134
|
+
version: '3.12'
|
83
135
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
136
|
+
name: rake
|
85
137
|
requirement: !ruby/object:Gem::Requirement
|
86
138
|
requirements:
|
87
139
|
- - "~>"
|
88
140
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0
|
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
|
148
|
+
version: '13.0'
|
97
149
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
150
|
+
name: simplecov
|
99
151
|
requirement: !ruby/object:Gem::Requirement
|
100
152
|
requirements:
|
101
|
-
- - "
|
153
|
+
- - "~>"
|
102
154
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
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:
|
162
|
+
version: '0.17'
|
111
163
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
164
|
+
name: standard
|
113
165
|
requirement: !ruby/object:Gem::Requirement
|
114
166
|
requirements:
|
115
167
|
- - ">="
|
116
168
|
- !ruby/object:Gem::Version
|
117
|
-
version:
|
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:
|
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:
|
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
|
-
|
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
|