Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Virtus with dry-types #1920

Merged
merged 1 commit into from
Dec 2, 2019
Merged

Replace Virtus with dry-types #1920

merged 1 commit into from
Dec 2, 2019

Conversation

dnesteryuk
Copy link
Member

@dnesteryuk dnesteryuk commented Oct 18, 2019

As we know Virtus isn't maintained anymore, so it makes sense to use something different for coercing. There is a dry-types lib which was created as a replacement for Virtus (at least a part of it). Although, it is only about coercion.

This change gets rid of Virtus and adds dry-types to the stack. Almost, all tests pass. However, there are a few tests which were modified, actually, they didn't make any sense. Yeah, Virtus was providing
some "magic" to make them green, but I think they were unreal.

The structure inside didn't change match. I tried to integrate dry-types without huge refactoring.

If people want to use Virtus for custom types, it is possible after implementing a parse method which initializes a model.

class User
  include Virtus.model

  attribute :id, Integer
  attribute :name, String

  def self.parse(attrs)
    new(attrs)
  end
end

This PR is kind of "work in progress", I would like to gather feedback then proceed. If somebody can add this change to their project and launch tests, it will be awesome, the goal is to avoid breaking changes.

Missing parts:

  • changelog
  • upgrade instruction (at least about Virtus models as custom type)
  • solve an issue with Hashie::Mash
  • Grape offences
  • Deprecate Ruby 2.3

It closes #1347.

@myxoh
Copy link
Member

myxoh commented Oct 18, 2019

Following this, shout if you get stuck or need some help

@dblock
Copy link
Member

dblock commented Oct 20, 2019

👀

Gemfile Outdated
@@ -9,6 +9,7 @@ group :development, :test do
gem 'hashie'
gem 'rake'
gem 'rubocop', '0.51.0'
gem 'virtus'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can't be used at all in the final PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few tests which use Virtus to verify that it can be used as a custom type, it means I need to remove the 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I worry that we'll leave something that works in tests but not in prod. Maybe those can become integration tests with their own Gemfile?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, Virtus isn't needed anymore. I removed it and adjusted tests testing custom types 🙂

UPGRADING.md Outdated
@@ -1,6 +1,39 @@
Upgrading Grape
===============

### Upgrading to >= 1.2.5

[Virtus](https://github.com/solnic/virtus) isn't used for coercing anymore. If your project depends on Virtus, you have to add it to your `Gemfile`. Also, if Virtus is used as a custom type
Copy link
Member

@dblock dblock Oct 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simlify and shorten, "has been replaced by dry-types for parameter coercion" and just ", add it to your Gemfile"

end
end
```

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to explain how to rewrite a custom type with dry-types here, first. If I am upgrading I'd rather not use Virtus unless I can't.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dry-types cannot be used in place of Virtus for custom types, dry-schema might be. So, if users need something like Virtus they need to keep it or migrate to dry-schema. I guess it is up to them to decide, the goal is to avoid breaking changes, so if they use Virtus, they can still use it until they have time to migrate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds like you're saying that if you want a custom type you have to use Virtus? Or are you saying you can use Virtus? Either way let's make this very clear in the upgrading/readme doc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, Virtus isn't required for custom types. As it was before, any class implementing the parse method might be used. 🙂 I've added an additional sentence to the upgrading doc.


# This method maintaine logic which was defined by Virtus for arrays.
# Virtus doesn't allow nil in arrays.
def would_virtus_reject?(val)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's Virtus related, but I would just call this reject?. It's about the current coercer, not some library-specific behavior.

Copy link
Member

@dblock dblock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This LGTM once it's cleaned up & al. Nice work.

end
rescue StandardError => _e
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is too broad of a catch-all, I think should be a parser error only.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't know which error might be raised by a custom type. Users define it. 🙂

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why it's a problem. Catch all could be something you actually don't expect and there's no way to know if you catch all. We should not be catching all here at all or having an explicit contract with a coercer. If you want the latter, introduce an exception such as CoercionError and rescue that. Document that coercers should throw this or a child of this exception.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, this begin/rescue isn't required. Most likely I added to fix some broken tests, after refactoring, it became useless.

require 'dry-types'

module DryTypes
include Dry.Types()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove (), or maybe just use Dry.Types.... everywhere. It's liberally used below as either.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love to do that, but it is how dry-types should be used. After calling Dry.Types() registered types get added to a container, it involves adding constants and some other meta-programming.

Copy link
Member

@dblock dblock Oct 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I think I am missing something. I'm probably just bad at Ruby :) I am staring at dry-types types.rb code and I don't get it. How does it make Dry.Types different from Dry.Types()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dry.Types is a method which returns an instance of a module to be included. 🙂 Here is that module.

@dnesteryuk dnesteryuk changed the title [WIP] replace Virtus with dry-types Replace Virtus with dry-types Oct 29, 2019
@dnesteryuk
Copy link
Member Author

Dry-types requires Ruby 2.4 or greater 😞

@dblock
Copy link
Member

dblock commented Oct 31, 2019

Dry-types requires Ruby 2.4 or greater 😞

Deprecate Ruby 2.3 with a note, changelog, readme change, etc.

@dnesteryuk
Copy link
Member Author

Hi @dblock I made recommended changes, please, have a look again. ☺️ Thanks for your patience! 😄

Copy link
Member

@dblock dblock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am good with merging this via a squash with some minor changes in docs, see above.

Since it's a big one, @myxoh, care to take another look?

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

## Installation

After **1.2.5**, Ruby 2.4.0 or newer is required.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should go into UPGRADING, for this doc just state that Ruby 2.4 or newer is required.

UPGRADING.md Outdated
end
```

you need to add a class-level `parse` method to the model:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Start with a cap, "Add a ..."

UPGRADING.md Outdated

#### Ruby

After adding dry-types, Ruby 2.4.0 or newer is required.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's put this section on top of the list?

end
```

Custom types which don't depend on Virtus don't require any changes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a "For more information see ..." and link to this PR.

@dnesteryuk
Copy link
Member Author

@dblock I've made changes to README and UPGRADING. Please, have a look.

@dblock
Copy link
Member

dblock commented Nov 6, 2019

I'm good with this! @myxoh want to confirm and merge?

@dblock
Copy link
Member

dblock commented Dec 1, 2019

Let's get this build to green? There's some legit looking failure with rack-edge. If it's rack's fault we can skip or mark pending and open an issue. I also released 1.2.5 so this will need an update in CHANGELOG and a rebase.

UPGRADING.md Outdated
@@ -1,6 +1,49 @@
Upgrading Grape
===============

### Upgrading to >= 1.2.5
Copy link
Member

@dblock dblock Dec 1, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this 1.3.0. Also change in README/version.rb.

@dnesteryuk
Copy link
Member Author

dnesteryuk commented Dec 2, 2019

@dblock that commit broke Grape tests 😞

As we know Virtus isn't maintained anymore, so it makes sense to use something
different for coercing. There is a dry-types lib which was created as
a replacement for Virtus (at least a part of it). Although, it is only
about coercion.

This change gets rid of Virtus and adds dry-types to the stack.

The structure inside didn't change match. Dry-types was integrated
without huge refactoring.

If people want to use Virtus for custom types, it is possible after
implementing a parse method which initializes a model.

      class User
        include Virtus.model

        attribute :id, Integer
        attribute :name, String

        def self.parse(*args)
          new(*args)
        end
      end
@@ -1133,7 +1133,7 @@ class DummyFormatClass

subject.use Rack::Chunked
subject.get('/stream') { stream test_stream }
get '/stream', {}, 'HTTP_VERSION' => 'HTTP/1.1'
get '/stream', {}, 'HTTP_VERSION' => 'HTTP/1.1', 'SERVER_PROTOCOL' => 'HTTP/1.1'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dblock Here is how I fixed the problem with Rack

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's totally fine, we just want to make sure Grape is compatible.

@dblock dblock merged commit 69ce0df into ruby-grape:master Dec 2, 2019
@dblock
Copy link
Member

dblock commented Dec 2, 2019

I merged this, thank you so much for doing this work @dnesteryuk, it's a big one. I suggest you email the mailing list describing this change and asking people to give the master branch a try, especially if they do custom type checking.

@flvrone
Copy link

flvrone commented Dec 2, 2019

@dblock I've just tried to run a test suite on one of my projects against your master branch, and all green 😁 There's no custom type checking though.

@dnesteryuk dnesteryuk deleted the chore/bye-virstus-hello-dry-types branch December 4, 2019 07:32
@dnesteryuk
Copy link
Member Author

@dblock where is the mailing list? 😬

@dblock
Copy link
Member

dblock commented Dec 4, 2019

@dblock where is the mailing list? 😬

http://groups.google.com/group/ruby-grape

@nbulaj
Copy link
Contributor

nbulaj commented Dec 26, 2019

Any reason why we tied to version 1.1.x of dry-types? @dnesteryuk I know gem changes even with patch version and can bring some incompatibilities, but ... I don't think it's a good solution btw

I tried Grape from the master branch with Ruby 2.7 on my current project and see a lot of deprecation messages like:

/.../ruby-2.7.0/gems/dry-types-1.1.1/lib/dry/types/any.rb:18: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call

Even if it's already fixed or would be fixed in the future versions of Dry-types we can't update to it due to version restrictions :(

@dblock
Copy link
Member

dblock commented Dec 26, 2019

Any reason why we tied to version 1.1.x of dry-types? @dnesteryuk I know it changes even with patch version changes, but ... I don't think it's a good solution btw

We should relax it, please PR.

ghiculescu added a commit to ghiculescu/sorbet-typed that referenced this pull request May 22, 2020
It was removed in Grape 1.3: ruby-grape/grape#1920 / https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#upgrading-to--130

I'm not sure this particular constant definition is useful anyway.
jez pushed a commit to sorbet/sorbet-typed that referenced this pull request May 26, 2020
It was removed in Grape 1.3: ruby-grape/grape#1920 / https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#upgrading-to--130

I'm not sure this particular constant definition is useful anyway.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Switch from virtus to dry-types
5 participants