Skip to content

Commit 69ce0df

Browse files
authored
Merge pull request #1920 from dnesteryuk/chore/bye-virstus-hello-dry-types
Replace Virtus with dry-types
2 parents fb11882 + 9b352a8 commit 69ce0df

30 files changed

+473
-388
lines changed

.travis.yml

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,41 @@ gemfile:
88

99
matrix:
1010
include:
11-
- rvm: 2.5.3
11+
- rvm: 2.6.5
1212
script:
1313
- bundle exec danger
14-
- rvm: 2.5.3
14+
- rvm: 2.6.5
1515
gemfile: Gemfile
16-
- rvm: 2.5.3
16+
- rvm: 2.6.5
1717
gemfile: gemfiles/rack_edge.gemfile
18-
- rvm: 2.5.3
18+
- rvm: 2.6.5
1919
gemfile: gemfiles/rails_edge.gemfile
20-
- rvm: 2.5.3
20+
- rvm: 2.6.5
2121
gemfile: gemfiles/rails_5.gemfile
22-
- rvm: 2.5.3
22+
- rvm: 2.6.5
2323
gemfile: gemfiles/multi_json.gemfile
2424
script:
2525
- bundle exec rake
2626
- bundle exec rspec spec/integration/multi_json
27-
- rvm: 2.5.3
27+
- rvm: 2.6.5
2828
gemfile: gemfiles/multi_xml.gemfile
2929
script:
3030
- bundle exec rake
3131
- bundle exec rspec spec/integration/multi_xml
32-
- rvm: 2.4.5
32+
- rvm: 2.5.7
3333
gemfile: Gemfile
34-
- rvm: 2.4.5
34+
- rvm: 2.5.7
3535
gemfile: gemfiles/rails_5.gemfile
36-
- rvm: 2.3.8
36+
- rvm: 2.4.9
3737
gemfile: Gemfile
38-
- rvm: 2.3.8
38+
- rvm: 2.4.9
3939
gemfile: gemfiles/rails_5.gemfile
40-
- rvm: 2.2.10
4140
- rvm: ruby-head
4241
- rvm: jruby-head
4342
- rvm: rbx-3
4443
allow_failures:
45-
- rvm: 2.2.10
4644
- rvm: ruby-head
4745
- rvm: jruby-head
4846
- rvm: rbx-3
49-
- rvm: 2.5.3
50-
gemfile: gemfiles/rack_edge.gemfile
5147

5248
bundler_args: --without development

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
### 1.2.6 (Next)
1+
### 1.3.0 (Next)
22

33
#### Features
44

55
* Your contribution here.
66
* [#1938](https://github.com/ruby-grape/grape/pull/1938): Add project metadata to the gemspec - [@orien](https://github.com/orien).
7+
* [#1920](https://github.com/ruby-grape/grape/pull/1920): Replace Virtus with dry-types - [@dnesteryuk](https://github.com/dnesteryuk).
78

89
#### Fixes
910

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ content negotiation, versioning and much more.
154154

155155
## Stable Release
156156

157-
You're reading the documentation for the next release of Grape, which should be **1.2.6**.
157+
You're reading the documentation for the next release of Grape, which should be **1.3.0**.
158158
Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
159159
The current stable release is [1.2.5](https://github.com/ruby-grape/grape/blob/v1.2.5/README.md).
160160

@@ -167,6 +167,8 @@ The current stable release is [1.2.5](https://github.com/ruby-grape/grape/blob/v
167167

168168
## Installation
169169

170+
Ruby 2.4 or newer is required.
171+
170172
Grape is available as a gem, to install it just install the gem:
171173

172174
gem install grape

UPGRADING.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,49 @@
11
Upgrading Grape
22
===============
33

4+
### Upgrading to >= 1.3.0
5+
6+
#### Ruby
7+
8+
After adding dry-types, Ruby 2.4 or newer is required.
9+
10+
#### Coercion
11+
12+
[Virtus](https://github.com/solnic/virtus) has been replaced by [dry-types](https://dry-rb.org/gems/dry-types/1.2/) for parameter coercion. If your project depends on Virtus, explicitly add it to your `Gemfile`. Also, if Virtus is used for defining custom types
13+
14+
```ruby
15+
class User
16+
include Virtus.model
17+
18+
attribute :id, Integer
19+
attribute :name, String
20+
end
21+
22+
# somewhere in your API
23+
params do
24+
requires :user, type: User
25+
end
26+
```
27+
28+
Add a class-level `parse` method to the model:
29+
30+
```ruby
31+
class User
32+
include Virtus.model
33+
34+
attribute :id, Integer
35+
attribute :name, String
36+
37+
def self.parse(attrs)
38+
new(attrs)
39+
end
40+
end
41+
```
42+
43+
Custom types which don't depend on Virtus don't require any changes.
44+
45+
For more information see [#1920](https://github.com/ruby-grape/grape/pull/1920).
46+
447
### Upgrading to >= 1.2.4
548

649
#### Headers in `error!` call

grape.gemspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ Gem::Specification.new do |s|
2020

2121
s.add_runtime_dependency 'activesupport'
2222
s.add_runtime_dependency 'builder'
23+
s.add_runtime_dependency 'dry-types', '~> 1.1.1'
2324
s.add_runtime_dependency 'mustermann-grape', '~> 1.0.0'
2425
s.add_runtime_dependency 'rack', '>= 1.3.0'
2526
s.add_runtime_dependency 'rack-accept'
26-
s.add_runtime_dependency 'virtus', '>= 1.0.0'
2727

2828
s.files = %w[CHANGELOG.md CONTRIBUTING.md README.md grape.png UPGRADING.md LICENSE]
2929
s.files += %w[grape.gemspec]
3030
s.files += Dir['lib/**/*']
3131
s.test_files = Dir['spec/**/*']
3232
s.require_paths = ['lib']
33+
s.required_ruby_version = '>= 2.4.0'
3334
end

lib/grape.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
require 'i18n'
2121
require 'thread'
2222

23-
require 'virtus'
24-
2523
I18n.load_path << File.expand_path('../grape/locale/en.yml', __FILE__)
2624

2725
module Grape

lib/grape/dsl/helpers.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def include_all_in_scope
6565

6666
def define_boolean_in_mod(mod)
6767
return if defined? mod::Boolean
68-
mod.const_set('Boolean', Virtus::Attribute::Boolean)
68+
mod.const_set('Boolean', Grape::API::Boolean)
6969
end
7070

7171
def inject_api_helpers_to_mod(mod, &_block)

lib/grape/validations/params_scope.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,8 @@ def validate_value_coercion(coerce_type, *values_list)
427427
values_list.each do |values|
428428
next if !values || values.is_a?(Proc)
429429
value_types = values.is_a?(Range) ? [values.begin, values.end] : values
430-
if coerce_type == Virtus::Attribute::Boolean
431-
value_types = value_types.map { |type| Virtus::Attribute.build(type) }
430+
if coerce_type == Grape::API::Boolean
431+
value_types = value_types.map { |type| Grape::API::Boolean.build(type) }
432432
end
433433
unless value_types.all? { |v| v.is_a? coerce_type }
434434
raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)

lib/grape/validations/types.rb

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@
66
require_relative 'types/json'
77
require_relative 'types/file'
88

9-
# Patch for Virtus::Attribute::Collection
10-
# See the file for more details
11-
require_relative 'types/virtus_collection_patch'
12-
139
module Grape
1410
module Validations
1511
# Module for code related to grape's system for
@@ -27,8 +23,7 @@ module Types
2723
# a parameter value could not be coerced.
2824
class InvalidValue; end
2925

30-
# Types representing a single value, which are coerced through Virtus
31-
# or special logic in Grape.
26+
# Types representing a single value, which are coerced.
3227
PRIMITIVES = [
3328
# Numerical
3429
Integer,
@@ -42,10 +37,12 @@ class InvalidValue; end
4237
Time,
4338

4439
# Misc
45-
Virtus::Attribute::Boolean,
40+
Grape::API::Boolean,
4641
String,
4742
Symbol,
48-
Rack::Multipart::UploadedFile
43+
Rack::Multipart::UploadedFile,
44+
TrueClass,
45+
FalseClass
4946
].freeze
5047

5148
# Types representing data structures.
@@ -86,8 +83,6 @@ def self.primitive?(type)
8683
# @param type [Class] type to check
8784
# @return [Boolean] whether or not the type is known by Grape as a valid
8885
# data structure type
89-
# @note This method does not yet consider 'complex types', which inherit
90-
# Virtus.model.
9186
def self.structure?(type)
9287
STRUCTURES.include?(type)
9388
end
@@ -104,25 +99,6 @@ def self.multiple?(type)
10499
(type.is_a?(Array) || type.is_a?(Set)) && type.size > 1
105100
end
106101

107-
# Does the given class implement a type system that Grape
108-
# (i.e. the underlying virtus attribute system) supports
109-
# out-of-the-box? Currently supported are +axiom-types+
110-
# and +virtus+.
111-
#
112-
# The type will be passed to +Virtus::Attribute.build+,
113-
# and the resulting attribute object will be expected to
114-
# respond correctly to +coerce+ and +value_coerced?+.
115-
#
116-
# @param type [Class] type to check
117-
# @return [Boolean] +true+ where the type is recognized
118-
def self.recognized?(type)
119-
return false if type.is_a?(Array) || type.is_a?(Set)
120-
121-
type.is_a?(Virtus::Attribute) ||
122-
type.ancestors.include?(Axiom::Types::Type) ||
123-
type.include?(Virtus::Model::Core)
124-
end
125-
126102
# Does Grape provide special coercion and validation
127103
# routines for the given class? This does not include
128104
# automatic handling for primitives, structures and
@@ -152,7 +128,6 @@ def self.custom?(type)
152128
!primitive?(type) &&
153129
!structure?(type) &&
154130
!multiple?(type) &&
155-
!recognized?(type) &&
156131
!special?(type) &&
157132
type.respond_to?(:parse) &&
158133
type.method(:parse).arity == 1
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
require_relative 'dry_type_coercer'
2+
3+
module Grape
4+
module Validations
5+
module Types
6+
# Coerces elements in an array. It might be an array of strings or integers or
7+
# anything else.
8+
#
9+
# It could've been possible to use an +of+
10+
# method (https://dry-rb.org/gems/dry-types/1.2/array-with-member/)
11+
# provided by dry-types. Unfortunately, it doesn't work for Grape because of
12+
# behavior of Virtus which was used earlier, a `Grape::Validations::Types::PrimitiveCoercer`
13+
# maintains Virtus behavior in coercing.
14+
class ArrayCoercer < DryTypeCoercer
15+
def initialize(type, strict = false)
16+
super
17+
18+
@coercer = scope::Array
19+
@elem_coercer = PrimitiveCoercer.new(type.first, strict)
20+
end
21+
22+
def call(_val)
23+
collection = super
24+
25+
return collection if collection.is_a?(InvalidValue)
26+
27+
coerce_elements collection
28+
end
29+
30+
protected
31+
32+
def coerce_elements(collection)
33+
collection.each_with_index do |elem, index|
34+
return InvalidValue.new if reject?(elem)
35+
36+
coerced_elem = @elem_coercer.call(elem)
37+
38+
return coerced_elem if coerced_elem.is_a?(InvalidValue)
39+
40+
collection[index] = coerced_elem
41+
end
42+
43+
collection
44+
end
45+
46+
# This method maintaine logic which was defined by Virtus for arrays.
47+
# Virtus doesn't allow nil in arrays.
48+
def reject?(val)
49+
val.nil?
50+
end
51+
end
52+
end
53+
end
54+
end

0 commit comments

Comments
 (0)