From 07303c9be5e3e75f72426a65913c197995e34415 Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Sun, 8 Jun 2025 13:58:33 -0700 Subject: [PATCH] Replace Virtus with dry-struct for attributes and type coercion - Convert all model classes to Dry::Struct - Add Types module with StringOrInteger and BooleanFlag --- instapaper.gemspec | 3 ++- lib/instapaper/bookmark.rb | 32 ++++++++++++++++---------------- lib/instapaper/bookmark_list.rb | 17 +++++++++-------- lib/instapaper/credentials.rb | 14 +++++++------- lib/instapaper/folder.rb | 24 ++++++++++++------------ lib/instapaper/highlight.rb | 22 +++++++++++----------- lib/instapaper/types.rb | 22 ++++++++++++++++++++++ lib/instapaper/user.rb | 18 +++++++++--------- 8 files changed, 88 insertions(+), 64 deletions(-) create mode 100644 lib/instapaper/types.rb diff --git a/instapaper.gemspec b/instapaper.gemspec index 671489a..8514108 100644 --- a/instapaper.gemspec +++ b/instapaper.gemspec @@ -4,10 +4,11 @@ require 'instapaper/version' Gem::Specification.new do |spec| spec.add_dependency 'addressable', '~> 2.3' + spec.add_dependency 'dry-struct', '~> 1.0' + spec.add_dependency 'dry-types', '~> 1.0' spec.add_dependency 'http', '>= 2', '< 6' spec.add_dependency 'multi_json', '~> 1' spec.add_dependency 'simple_oauth', '~> 0.3' - spec.add_dependency 'virtus', '~> 1' spec.author = 'Steve Agalloco' spec.description = "Ruby Client for Instapaper's Full API" spec.email = 'steve.agalloco@gmail.com' diff --git a/lib/instapaper/bookmark.rb b/lib/instapaper/bookmark.rb index 5828454..1e5c432 100644 --- a/lib/instapaper/bookmark.rb +++ b/lib/instapaper/bookmark.rb @@ -1,21 +1,21 @@ -require 'virtus' +require 'dry-struct' +require 'instapaper/types' module Instapaper - class Bookmark - include Virtus.value_object + class Bookmark < Dry::Struct + include Types + transform_keys(&:to_sym) - values do - attribute :instapaper_hash, String - attribute :description, String - attribute :bookmark_id, Integer - attribute :private_source, String - attribute :title, String - attribute :url, String - attribute :progress_timestamp, DateTime - attribute :time, DateTime - attribute :progress, String - attribute :starred, String - attribute :type, String - end + attribute :type, Types::String + attribute :bookmark_id, Types::Integer + attribute :url, Types::String + attribute :title, Types::String + attribute? :description, Types::String + attribute? :instapaper_hash, Types::String + attribute? :private_source, Types::String + attribute? :progress_timestamp, Types::Integer.optional + attribute? :time, Types::Integer.optional + attribute? :progress, Types::StringOrInteger + attribute? :starred, Types::StringOrInteger end end diff --git a/lib/instapaper/bookmark_list.rb b/lib/instapaper/bookmark_list.rb index 5307a56..e604a21 100644 --- a/lib/instapaper/bookmark_list.rb +++ b/lib/instapaper/bookmark_list.rb @@ -1,17 +1,18 @@ +require 'dry-struct' +require 'instapaper/types' require 'instapaper/bookmark' require 'instapaper/highlight' require 'instapaper/user' module Instapaper - class BookmarkList - include Virtus.value_object + class BookmarkList < Dry::Struct + include Types + transform_keys(&:to_sym) - values do - attribute :user, Instapaper::User - attribute :bookmarks, [Instapaper::Bookmark] - attribute :highlights, [Instapaper::Highlight] - attribute :delete_ids, [Integer] - end + attribute :user, Instapaper::User + attribute :bookmarks, Types::Array.of(Instapaper::Bookmark) + attribute :highlights, Types::Array.of(Instapaper::Highlight) + attribute? :delete_ids, Types::Array.of(Types::Integer).optional.default([].freeze) def each(&block) bookmarks.each(&block) diff --git a/lib/instapaper/credentials.rb b/lib/instapaper/credentials.rb index 85db341..c80d12f 100644 --- a/lib/instapaper/credentials.rb +++ b/lib/instapaper/credentials.rb @@ -1,12 +1,12 @@ -require 'virtus' +require 'dry-struct' +require 'instapaper/types' module Instapaper - class Credentials - include Virtus.value_object + class Credentials < Dry::Struct + include Types + transform_keys(&:to_sym) - values do - attribute :oauth_token, String - attribute :oauth_token_secret, String - end + attribute :oauth_token, Types::String + attribute :oauth_token_secret, Types::String end end diff --git a/lib/instapaper/folder.rb b/lib/instapaper/folder.rb index cbd9516..66eb9b3 100644 --- a/lib/instapaper/folder.rb +++ b/lib/instapaper/folder.rb @@ -1,17 +1,17 @@ -require 'virtus' +require 'dry-struct' +require 'instapaper/types' module Instapaper - class Folder - include Virtus.value_object + class Folder < Dry::Struct + include Types + transform_keys(&:to_sym) - values do - attribute :title, String - attribute :display_title, String - attribute :sync_to_mobile, Axiom::Types::Boolean - attribute :folder_id, Integer - attribute :position, String - attribute :type, String - attribute :slug, String - end + attribute :title, Types::String + attribute? :display_title, Types::String + attribute :sync_to_mobile, Types::BooleanFlag + attribute :folder_id, Types::Integer + attribute :position, Types::Coercible::Float + attribute :type, Types::String + attribute? :slug, Types::String end end diff --git a/lib/instapaper/highlight.rb b/lib/instapaper/highlight.rb index 28b323c..2f82762 100644 --- a/lib/instapaper/highlight.rb +++ b/lib/instapaper/highlight.rb @@ -1,16 +1,16 @@ -require 'virtus' +require 'dry-struct' +require 'instapaper/types' module Instapaper - class Highlight - include Virtus.value_object + class Highlight < Dry::Struct + include Types + transform_keys(&:to_sym) - values do - attribute :type, String - attribute :highlight_id, String - attribute :bookmark_id, String - attribute :text, String - attribute :position, String - attribute :time, String - end + attribute :type, Types::String + attribute :highlight_id, Types::Integer + attribute :bookmark_id, Types::Integer + attribute :text, Types::String + attribute :position, Types::Integer + attribute :time, Types::Integer.optional end end diff --git a/lib/instapaper/types.rb b/lib/instapaper/types.rb new file mode 100644 index 0000000..2781b5c --- /dev/null +++ b/lib/instapaper/types.rb @@ -0,0 +1,22 @@ +require 'dry-types' + +module Instapaper + module Types + include Dry.Types() + + # Coerces any value to string (replaces custom StringOrInteger union type) + StringOrInteger = Types::Coercible::String + + # Handles boolean flags from API that come as "0"/"1" strings or 0/1 integers. + BooleanFlag = Types::Constructor(Types::Bool) do |value| + case value + when '1', 1, 'true', true + true + when '0', 0, 'false', false, nil + false + else + !!value + end + end + end +end diff --git a/lib/instapaper/user.rb b/lib/instapaper/user.rb index fde035e..9fd7065 100644 --- a/lib/instapaper/user.rb +++ b/lib/instapaper/user.rb @@ -1,14 +1,14 @@ -require 'virtus' +require 'dry-struct' +require 'instapaper/types' module Instapaper - class User - include Virtus.value_object + class User < Dry::Struct + include Types + transform_keys(&:to_sym) - values do - attribute :username, String - attribute :user_id, Integer - attribute :type, String - attribute :subscription_is_active, Axiom::Types::Boolean - end + attribute :username, Types::String + attribute :user_id, Types::Integer + attribute :type, Types::String + attribute? :subscription_is_active, Types::BooleanFlag.optional end end