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