radical 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -2
  3. data/CHANGELOG.md +46 -1
  4. data/Gemfile +4 -2
  5. data/README.md +5 -1
  6. data/exe/rad +41 -0
  7. data/lib/radical/app.rb +61 -27
  8. data/lib/radical/asset.rb +24 -0
  9. data/lib/radical/asset_compiler.rb +40 -0
  10. data/lib/radical/assets.rb +45 -0
  11. data/lib/radical/controller.rb +54 -11
  12. data/lib/radical/database.rb +43 -44
  13. data/lib/radical/env.rb +1 -0
  14. data/lib/radical/flash.rb +61 -0
  15. data/lib/radical/form.rb +75 -15
  16. data/lib/radical/generator/app/.env +5 -0
  17. data/lib/radical/generator/app/Gemfile +7 -0
  18. data/lib/radical/generator/app/app.rb +37 -0
  19. data/lib/radical/generator/app/config.ru +5 -0
  20. data/lib/radical/generator/app/controllers/controller.rb +4 -0
  21. data/lib/radical/generator/app/models/model.rb +4 -0
  22. data/lib/radical/generator/app/routes.rb +5 -0
  23. data/lib/radical/generator/blank_migration.rb +11 -0
  24. data/lib/radical/generator/controller.rb +59 -0
  25. data/lib/radical/generator/migration.rb +13 -0
  26. data/lib/radical/generator/model.rb +9 -0
  27. data/lib/radical/generator/views/_form.rb +6 -0
  28. data/lib/radical/generator/views/edit.rb +3 -0
  29. data/lib/radical/generator/views/index.rb +24 -0
  30. data/lib/radical/generator/views/new.rb +4 -0
  31. data/lib/radical/generator/views/show.rb +5 -0
  32. data/lib/radical/generator.rb +155 -0
  33. data/lib/radical/migration.rb +45 -0
  34. data/lib/radical/model.rb +3 -12
  35. data/lib/radical/router.rb +143 -42
  36. data/lib/radical/routes.rb +59 -0
  37. data/lib/radical/security_headers.rb +27 -0
  38. data/lib/radical/strings.rb +17 -0
  39. data/lib/radical/table.rb +2 -0
  40. data/lib/radical/view.rb +19 -9
  41. data/lib/radical.rb +11 -0
  42. data/radical.gemspec +4 -3
  43. metadata +44 -17
@@ -1,4 +1,6 @@
1
1
  # typed: true
2
+ # frozen_string_literal: true
3
+
2
4
  require 'rack'
3
5
  require 'sorbet-runtime'
4
6
 
@@ -45,6 +47,8 @@ module Radical
45
47
  ].freeze
46
48
 
47
49
  RESOURCE_ACTIONS = [
50
+ [:new, 'GET', '/new'],
51
+ [:create, 'POST', ''],
48
52
  [:show, 'GET', ''],
49
53
  [:edit, 'GET', '/edit'],
50
54
  [:update, 'PUT', ''],
@@ -66,54 +70,31 @@ module Radical
66
70
 
67
71
  sig { params(klass: Class).void }
68
72
  def add_root(klass)
69
- add_actions(klass, name: '')
73
+ add_routes(klass, name: '', actions: ACTIONS)
74
+ add_root_paths(klass)
70
75
  end
71
76
 
72
- sig { params(klass: Class, name: T.nilable(String), prefix: T.nilable(String), actions: Array).void }
73
- def add_actions(klass, name: nil, prefix: nil, actions: ACTIONS)
74
- name ||= klass.route_name
75
-
76
- actions.each do |method, http_method, suffix|
77
- next unless klass.method_defined?(method)
78
-
79
- path = "/#{prefix}#{name}#{suffix}"
80
- path = Regexp.new("^#{path.gsub(/:(\w+)/, '(?<\1>[a-zA-Z0-9_]+)')}$")
81
-
82
- if %i[index create show update destroy].include?(method) && !klass.method_defined?(:"#{klass.route_name}_path")
83
- klass.define_method :"#{klass.route_name}_path" do |obj = nil|
84
- if obj.is_a?(Model)
85
- "/#{klass.route_name}/#{obj.id}"
86
- else
87
- "/#{klass.route_name}"
88
- end
89
- end
90
- end
91
-
92
- if method == :new
93
- klass.define_method :"new_#{klass.route_name}_path" do
94
- "/#{klass.route_name}/new"
95
- end
96
- end
97
-
98
- if method == :edit
99
- klass.define_method :"edit_#{klass.route_name}_path" do |obj|
100
- "/#{klass.route_name}/#{obj.id}/edit"
101
- end
102
- end
103
-
104
- @routes[http_method] << [path, [klass, method]]
105
- end
77
+ sig { params(klass: Class).void }
78
+ def add_resource(klass)
79
+ add_routes(klass, actions: RESOURCE_ACTIONS)
80
+ add_resource_paths(klass)
106
81
  end
107
82
 
108
- sig { params(classes: T::Array[Class], prefix: T.nilable(String), actions: Array).void }
109
- def add_routes(classes, prefix: nil, actions: ACTIONS)
110
- classes.each do |klass|
111
- add_actions(klass, prefix: prefix, actions: actions)
83
+ sig { params(klass: Class, parents: T.nilable(T::Array[Class])).void }
84
+ def add_resources(klass, parents: nil)
85
+ if parents
86
+ parents.each do |scope|
87
+ add_routes(klass, actions: ACTIONS, scope: scope)
88
+ add_resources_paths(klass, scope: scope)
89
+ end
90
+ else
91
+ add_routes(klass, actions: ACTIONS)
92
+ add_resources_paths(klass)
112
93
  end
113
94
  end
114
95
 
115
- sig { params(request: Rack::Request).returns(Rack::Response) }
116
- def route(request)
96
+ sig { params(request: Rack::Request, options: T.nilable(Hash)).returns(Rack::Response) }
97
+ def route(request, options: {})
117
98
  params = T.let({}, T.nilable(Hash))
118
99
 
119
100
  route = @routes[request.request_method].find do |r|
@@ -128,7 +109,7 @@ module Radical
128
109
  request.update_param(k, v)
129
110
  end
130
111
 
131
- instance = klass.new(request)
112
+ instance = klass.new(request, options: options)
132
113
 
133
114
  response = instance.public_send(method)
134
115
 
@@ -140,5 +121,125 @@ module Radical
140
121
 
141
122
  Rack::Response.new(body, 200, { 'Content-Type' => 'text/html' })
142
123
  end
124
+
125
+ private
126
+
127
+ sig { params(klass: Class, actions: Array, name: T.nilable(String), scope: T.nilable(Class)).void }
128
+ def add_routes(klass, actions:, name: nil, scope: nil)
129
+ name ||= klass.route_name
130
+
131
+ actions.each do |method, http_method, suffix|
132
+ next unless klass.method_defined?(method)
133
+
134
+ path = if scope
135
+ if %i[index new create].include?(method)
136
+ "/#{scope.route_name}/:#{scope.route_name}_id/#{name}#{suffix}"
137
+ else
138
+ "/#{name}#{suffix}"
139
+ end
140
+ else
141
+ "/#{name}#{suffix}"
142
+ end
143
+
144
+ path = Regexp.new("^#{path.gsub(/:(\w+)/, '(?<\1>[a-zA-Z0-9_]+)')}$").freeze
145
+
146
+ @routes[http_method] << [path, [klass, method]]
147
+ end
148
+ end
149
+
150
+ sig { params(klass: Class).void }
151
+ def add_root_paths(klass)
152
+ route_name = klass.route_name
153
+
154
+ if %i[index create show update destroy].any? { |method| klass.method_defined?(method) }
155
+ Controller.define_method :"#{route_name}_path" do |obj = nil|
156
+ if obj
157
+ "/#{obj.id}"
158
+ else
159
+ '/'
160
+ end
161
+ end
162
+ end
163
+
164
+ if klass.method_defined?(:new)
165
+ Controller.define_method :"new_#{route_name}_path" do
166
+ '/new'
167
+ end
168
+ end
169
+
170
+ return unless klass.method_defined?(:edit)
171
+
172
+ Controller.define_method :"edit_#{route_name}_path" do |obj|
173
+ "/#{obj.id}/edit"
174
+ end
175
+ end
176
+
177
+ sig { params(klass: Class).void }
178
+ def add_resource_paths(klass)
179
+ name = klass.route_name
180
+
181
+ if %i[create show update destroy].any? { |method| klass.method_defined?(method) }
182
+ Controller.define_method :"#{name}_path" do
183
+ "/#{name}"
184
+ end
185
+ end
186
+
187
+ if klass.method_defined?(:new)
188
+ Controller.define_method :"new_#{name}_path" do
189
+ "/#{name}/new"
190
+ end
191
+ end
192
+
193
+ return unless klass.method_defined?(:edit)
194
+
195
+ Controller.define_method :"edit_#{name}_path" do
196
+ "/#{name}/edit"
197
+ end
198
+ end
199
+
200
+ sig { params(klass: Class, scope: T.nilable(Class)).void }
201
+ def add_resources_paths(klass, scope: nil)
202
+ path_name = klass.route_name
203
+ scope_path_name = [scope&.route_name, klass.route_name].compact.join('_')
204
+ name = klass.route_name
205
+
206
+ if %i[index create show update destroy].any? { |method| klass.method_defined?(method) }
207
+ if scope
208
+ Controller.define_method :"#{scope_path_name}_path" do |parent|
209
+ "/#{scope.route_name}/#{parent.id}/#{name}"
210
+ end
211
+
212
+ Controller.define_method :"#{path_name}_path" do |obj|
213
+ "/#{name}/#{obj.id}"
214
+ end
215
+ else
216
+ Controller.define_method :"#{path_name}_path" do |obj = nil|
217
+ if obj
218
+ "/#{name}/#{obj.id}"
219
+ else
220
+ "/#{name}"
221
+ end
222
+ end
223
+ end
224
+ end
225
+
226
+ if klass.method_defined?(:new)
227
+ if scope
228
+ Controller.define_method :"new_#{scope_path_name}_path" do |parent|
229
+ "/#{scope.route_name}/#{parent.id}/#{name}/new"
230
+ end
231
+ else
232
+ Controller.define_method :"new_#{path_name}_path" do
233
+ "/#{name}/new"
234
+ end
235
+ end
236
+ end
237
+
238
+ return unless klass.method_defined?(:edit)
239
+
240
+ Controller.define_method :"edit_#{path_name}_path" do |obj|
241
+ "/#{name}/#{obj.id}/edit"
242
+ end
243
+ end
143
244
  end
144
245
  end
@@ -0,0 +1,59 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'router'
5
+
6
+ module Radical
7
+ class Routes
8
+ class << self
9
+ extend T::Sig
10
+
11
+ sig { returns(Router) }
12
+ def router
13
+ @router ||= Router.new
14
+ end
15
+
16
+ def parents
17
+ @parents ||= []
18
+ end
19
+
20
+ sig { params(name: T.any(String, Symbol)).void }
21
+ def root(name)
22
+ klass = Object.const_get(name)
23
+
24
+ router.add_root(klass)
25
+ end
26
+
27
+ sig { params(names: T.any(String, Symbol)).void }
28
+ def resource(*names)
29
+ classes = names.map { |c| Object.const_get(c) }
30
+
31
+ classes.each do |klass|
32
+ router.add_resource(klass)
33
+ end
34
+ end
35
+
36
+ sig { params(names: T.any(String, Symbol), block: T.nilable(T.proc.void)).void }
37
+ def resources(*names, &block)
38
+ classes = names.map { |c| Object.const_get(c) }
39
+
40
+ classes.each do |klass|
41
+ if parents.any?
42
+ router.add_resources(klass, parents: @parents)
43
+
44
+ # only one level of nesting
45
+ @parents = []
46
+ else
47
+ router.add_resources(klass)
48
+ end
49
+ end
50
+
51
+ return unless block
52
+
53
+ @parents = classes
54
+
55
+ block.call
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Radical
4
+ class SecurityHeaders
5
+ DEFAULT_HEADERS = {
6
+ 'X-Content-Type-Options' => 'nosniff',
7
+ 'X-Frame-Options' => 'deny',
8
+ 'X-XSS-Protection' => '1; mode=block',
9
+ 'X-Permitted-Cross-Domain-Policies' => 'none',
10
+ 'Strict-Transport-Security' => 'max-age=31536000;, max-age=31536000; includeSubdomains',
11
+ 'Content-Security-Policy' => "default-src 'none'; style-src 'self'; script-src 'self'; connect-src 'self'; img-src 'self'; font-src 'self'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'; block-all-mixed-content;"
12
+ }.freeze
13
+
14
+ def initialize(app, headers)
15
+ @app = app
16
+ @headers = DEFAULT_HEADERS.merge(headers)
17
+ end
18
+
19
+ def call(env)
20
+ @app.call(env).tap do |_, headers|
21
+ @headers.each do |k, v|
22
+ headers[k] ||= v
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Radical
4
+ class Strings
5
+ SNAKE_CASE_REGEX = /\B([A-Z])/.freeze
6
+
7
+ class << self
8
+ def snake_case(str)
9
+ str.gsub(SNAKE_CASE_REGEX, '_\1').downcase
10
+ end
11
+
12
+ def camel_case(str)
13
+ str.split('_').map(&:capitalize).join
14
+ end
15
+ end
16
+ end
17
+ end
data/lib/radical/table.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Radical
2
4
  class Table
3
5
  attr_accessor :columns
data/lib/radical/view.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erubi'
2
4
  require 'tilt'
3
5
 
@@ -20,20 +22,28 @@ module Radical
20
22
  class << self
21
23
  attr_accessor :_views_path, :_layout
22
24
 
23
- def view_path(dir, name)
24
- filename = File.join(@_views_path, 'views', dir, "#{name}.erb")
25
+ def view_path!(dir, name)
26
+ filename = view_path(dir, name)
25
27
 
26
28
  raise "Could not find view file: #{filename}. You need to create it." unless File.exist?(filename)
27
29
 
28
30
  filename
29
31
  end
30
32
 
33
+ def view_path(dir, name)
34
+ File.join(@_views_path || '.', 'views', dir, "#{name}.erb")
35
+ end
36
+
31
37
  def template(dir, name)
32
38
  Tilt.new(view_path(dir, name), engine_class: CaptureEngine, escape_html: true)
33
39
  end
34
40
 
35
- def path(path = nil, test = Env.test?)
36
- @_views_path = path || ((test ? 'test' : '') + __dir__)
41
+ def template!(dir, name)
42
+ Tilt.new(view_path!(dir, name), engine_class: CaptureEngine, escape_html: true)
43
+ end
44
+
45
+ def path(path = nil)
46
+ @_views_path = path
37
47
  end
38
48
 
39
49
  def layout(name)
@@ -41,13 +51,13 @@ module Radical
41
51
  end
42
52
 
43
53
  def render(dir, name, scope, options = {})
44
- t = template(dir, name)
54
+ t = template!(dir, name)
45
55
 
46
- if @_layout && options[:layout] != false
47
- layout = template('', @_layout)
56
+ layout = template('', @_layout || 'layout') unless options[:layout] != false
48
57
 
49
- layout.render(scope, {}) do
50
- t.render scope
58
+ if layout
59
+ layout.render scope, {} do
60
+ t.render scope, options[:locals] || {}
51
61
  end
52
62
  else
53
63
  t.render scope
data/lib/radical.rb CHANGED
@@ -1,4 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'radical/app'
2
4
  require_relative 'radical/controller'
3
5
  require_relative 'radical/view'
4
6
  require_relative 'radical/model'
7
+ require_relative 'radical/migration'
8
+ require_relative 'radical/routes'
9
+ require_relative 'radical/env'
10
+
11
+ module Radical
12
+ def self.env
13
+ Radical::Env
14
+ end
15
+ end
data/radical.gemspec CHANGED
@@ -3,7 +3,7 @@
3
3
  Gem::Specification.new do |spec|
4
4
  spec.platform = Gem::Platform::RUBY
5
5
  spec.name = 'radical'
6
- spec.version = '1.0.0'
6
+ spec.version = '1.2.0'
7
7
  spec.author = 'Sean Walker'
8
8
  spec.email = 'sean@swlkr.com'
9
9
 
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
15
15
  if spec.respond_to?(:metadata)
16
16
  spec.metadata['homepage_uri'] = spec.homepage
17
17
  spec.metadata['source_code_uri'] = 'https://github.com/swlkr/radical'
18
- spec.metadata['changelog_uri'] = 'https://github.com/swlkr/radical/CHANGELOG.md'
18
+ spec.metadata['changelog_uri'] = 'https://github.com/swlkr/radical/blob/main/CHANGELOG.md'
19
19
  else
20
20
  raise 'RubyGems 2.0 or newer is required to protect against ' \
21
21
  'public gem pushes.'
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.bindir = 'exe'
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ['lib']
32
+ spec.required_ruby_version = '>= 2.7.0'
32
33
 
33
34
  spec.add_development_dependency 'minitest', '~> 5.14'
34
35
  spec.add_development_dependency 'puma', '~> 5.5'
@@ -36,10 +37,10 @@ Gem::Specification.new do |spec|
36
37
  spec.add_development_dependency 'rake', '~> 13.0'
37
38
  spec.add_development_dependency 'sorbet', '~> 0.5'
38
39
 
40
+ spec.add_dependency 'brotli', '~> 0.4'
39
41
  spec.add_dependency 'erubi', '~> 1.10'
40
42
  spec.add_dependency 'rack', '~> 2.2'
41
43
  spec.add_dependency 'rack_csrf', '~> 2.6'
42
- spec.add_dependency 'rack-flash3', '~> 1.0'
43
44
  spec.add_dependency 'sorbet-runtime', '~> 0.5'
44
45
  spec.add_dependency 'sqlite3', '~> 1.4'
45
46
  spec.add_dependency 'tilt', '~> 2.0'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: radical
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Walker
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-03 00:00:00.000000000 Z
11
+ date: 2021-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -81,61 +81,61 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0.5'
83
83
  - !ruby/object:Gem::Dependency
84
- name: erubi
84
+ name: brotli
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '1.10'
89
+ version: '0.4'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '1.10'
96
+ version: '0.4'
97
97
  - !ruby/object:Gem::Dependency
98
- name: rack
98
+ name: erubi
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '2.2'
103
+ version: '1.10'
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '2.2'
110
+ version: '1.10'
111
111
  - !ruby/object:Gem::Dependency
112
- name: rack_csrf
112
+ name: rack
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '2.6'
117
+ version: '2.2'
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '2.6'
124
+ version: '2.2'
125
125
  - !ruby/object:Gem::Dependency
126
- name: rack-flash3
126
+ name: rack_csrf
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '1.0'
131
+ version: '2.6'
132
132
  type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: '1.0'
138
+ version: '2.6'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: sorbet-runtime
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -180,7 +180,8 @@ dependencies:
180
180
  version: '2.0'
181
181
  description: This gem helps you write rails-like web applications
182
182
  email: sean@swlkr.com
183
- executables: []
183
+ executables:
184
+ - rad
184
185
  extensions: []
185
186
  extra_rdoc_files: []
186
187
  files:
@@ -192,14 +193,40 @@ files:
192
193
  - LICENSE
193
194
  - README.md
194
195
  - Rakefile
196
+ - exe/rad
195
197
  - lib/radical.rb
196
198
  - lib/radical/app.rb
199
+ - lib/radical/asset.rb
200
+ - lib/radical/asset_compiler.rb
201
+ - lib/radical/assets.rb
197
202
  - lib/radical/controller.rb
198
203
  - lib/radical/database.rb
199
204
  - lib/radical/env.rb
205
+ - lib/radical/flash.rb
200
206
  - lib/radical/form.rb
207
+ - lib/radical/generator.rb
208
+ - lib/radical/generator/app/.env
209
+ - lib/radical/generator/app/Gemfile
210
+ - lib/radical/generator/app/app.rb
211
+ - lib/radical/generator/app/config.ru
212
+ - lib/radical/generator/app/controllers/controller.rb
213
+ - lib/radical/generator/app/models/model.rb
214
+ - lib/radical/generator/app/routes.rb
215
+ - lib/radical/generator/blank_migration.rb
216
+ - lib/radical/generator/controller.rb
217
+ - lib/radical/generator/migration.rb
218
+ - lib/radical/generator/model.rb
219
+ - lib/radical/generator/views/_form.rb
220
+ - lib/radical/generator/views/edit.rb
221
+ - lib/radical/generator/views/index.rb
222
+ - lib/radical/generator/views/new.rb
223
+ - lib/radical/generator/views/show.rb
224
+ - lib/radical/migration.rb
201
225
  - lib/radical/model.rb
202
226
  - lib/radical/router.rb
227
+ - lib/radical/routes.rb
228
+ - lib/radical/security_headers.rb
229
+ - lib/radical/strings.rb
203
230
  - lib/radical/table.rb
204
231
  - lib/radical/view.rb
205
232
  - radical.gemspec
@@ -212,7 +239,7 @@ licenses:
212
239
  metadata:
213
240
  homepage_uri: https://github.com/swlkr/radical
214
241
  source_code_uri: https://github.com/swlkr/radical
215
- changelog_uri: https://github.com/swlkr/radical/CHANGELOG.md
242
+ changelog_uri: https://github.com/swlkr/radical/blob/main/CHANGELOG.md
216
243
  post_install_message:
217
244
  rdoc_options: []
218
245
  require_paths:
@@ -221,7 +248,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
221
248
  requirements:
222
249
  - - ">="
223
250
  - !ruby/object:Gem::Version
224
- version: '0'
251
+ version: 2.7.0
225
252
  required_rubygems_version: !ruby/object:Gem::Requirement
226
253
  requirements:
227
254
  - - ">="