Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 67 additions & 21 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,32 +1,78 @@
name: CI
on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
ruby: [2.6]
active_record: [5.2, 6.0]
dry_types: [1.0, 1.2]

ruby: ['2.6']
active_record: ['5.2', '6.0']
dry_types: ['1.0', '1.2']
include:
- ruby: 2.7
active_record: 6.1
dry_types: 1.5
# Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
- ruby: '2.7'
active_record: '6.1'
dry_types: '1.6'
- ruby: '2.7'
active_record: '7.0'
dry_types: '1.6'
- ruby: '2.7'
active_record: '7.1'
dry_types: '1.6'
- ruby: '3.0'
active_record: 6.1
dry_types: 1.5
- ruby: 3.1
active_record: 7.0.1
dry_types: 1.5
active_record: '6.1'
dry_types: '1.6'
- ruby: '3.0'
active_record: '7.0'
dry_types: '1.6'
- ruby: '3.0'
active_record: '7.1'
dry_types: '1.6'
- ruby: '3.1'
active_record: '7.0'
dry_types: '1.6'
- ruby: '3.1'
active_record: '7.1'
dry_types: '1.6'
- ruby: '3.1'
active_record: '7.2'
dry_types: '1.6'
- ruby: '3.2'
active_record: '7.0'
dry_types: '1.6'
- ruby: '3.2'
active_record: '7.1'
dry_types: '1.6'
- ruby: '3.2'
active_record: '7.2'
dry_types: '1.6'
- ruby: '3.2'
active_record: '8.0'
dry_types: '1.6'
- ruby: '3.3'
active_record: '7.1'
dry_types: '1.6'
- ruby: '3.3'
active_record: '7.2'
dry_types: '1.6'
- ruby: '3.3'
active_record: '8.0'
dry_types: '1.6'
- ruby: '3.4'
active_record: '7.2'
dry_types: '1.6'
- ruby: '3.4'
active_record: '8.0'
dry_types: '1.6'

runs-on: ubuntu-latest
env:
ACTIVERECORD: ${{ matrix.active_record }}
DRY_TYPES: ${{ matrix.dry_types }}
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
# bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- run: ACTIVERECORD=${{ matrix.active_record}} DRY_TYPES=${{ matrix.dry_types }} bundle install
- run: ACTIVERECORD=${{ matrix.active_record}} DRY_TYPES=${{ matrix.dry_types }} bundle exec rake
- uses: actions/checkout@v5
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- run: bundle install
- run: bundle exec rake
18 changes: 14 additions & 4 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,21 @@ source "https://rubygems.org"
gemspec
gem "minitest-line"

{ "dry-types" => ENV['DRY_TYPES'], "activerecord" => ENV['ACTIVERECORD']}.each do |gem_name, dependency|
next if dependency.nil?
gem gem_name, dependency
if ENV['DRY_TYPES']
gem "dry-types", ENV['DRY_TYPES']
end

gem "sqlite3", ENV.fetch('ACTIVERECORD', '5.2').to_f >= 6 ? '~> 1.4' : '~> 1.3.0'
if ENV['ACTIVERECORD']
gem "activerecord", "~> #{ENV['ACTIVERECORD']}.0"
end

activerecord_version = ENV.fetch('ACTIVERECORD', '5.2').to_f
if activerecord_version.to_f >= 8
gem "sqlite3", "~> 2.1"
elsif activerecord_version.to_f >= 6
gem "sqlite3", "~> 1.4"
else
gem "sqlite3", "~> 1.3.0"
end

# gem "declarative-builder"
6 changes: 3 additions & 3 deletions test/callback_group_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,11 @@ class Group < Disposable::Callback::Group

it do
expect(Group.hooks.size).must_equal 4
expect(Group.hooks[0].to_s).must_equal "[:on_change, :change!, {}]"
expect(normalize_inspect(Group.hooks[0].to_s)).must_equal "[:on_change, :change!, {}]"
# Group.hooks[1][1][:nested].hooks.to_s.must_equal "[[:on_add, [:notify_album!]],[:on_add, [:reset_song!]]]"
expect(Group.hooks[2].to_s).must_equal "[:on_change, :rehash_name!, {:property=>:title}]"
expect(normalize_inspect(Group.hooks[2].to_s)).must_equal "[:on_change, :rehash_name!, {:property=>:title}]"

expect(Group.definitions.get(Group.hooks[3][1])[:nested].hooks.to_s).must_equal "[[:on_change, :sing!, {}]]"
expect(normalize_inspect(Group.definitions.get(Group.hooks[3][1])[:nested].hooks.to_s)).must_equal "[[:on_change, :sing!, {}]]"
end

class EmptyGroup < Group
Expand Down
30 changes: 15 additions & 15 deletions test/rescheme_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,19 @@ def hello
expect(decorator.new(nil).hello).must_equal "hello"
expect(decorator.new(nil).ciao).must_equal "ciao"

expect(decorator.representable_attrs.get(:id).inspect).must_equal "#<Representable::Definition ==>id @options={:name=>\"id\", :parse_filter=>[], :render_filter=>[]}>"
expect(decorator.representable_attrs.get(:title).inspect).must_equal "#<Representable::Definition ==>title @options={:writeable=>false, :deserializer=>{:skip_parse=>\"skip lambda\"}, :name=>\"title\", :parse_filter=>[], :render_filter=>[], :skip_parse=>\"skip lambda\"}>"
expect(normalize_inspect(decorator.representable_attrs.get(:id).inspect)).must_equal "#<Representable::Definition ==>id @options={:name=>\"id\", :parse_filter=>[], :render_filter=>[]}>"
expect(normalize_inspect(decorator.representable_attrs.get(:title).inspect)).must_equal "#<Representable::Definition ==>title @options={:writeable=>false, :deserializer=>{:skip_parse=>\"skip lambda\"}, :name=>\"title\", :parse_filter=>[], :render_filter=>[], :skip_parse=>\"skip lambda\"}>"

songs = decorator.representable_attrs.get(:songs)
options = songs.instance_variable_get(:@options)
options[:nested].extend(Declarative::Inspect)
expect(options.inspect).must_equal "{:readable=>false, :deserializer=>{:skip_parse=>\"another lambda\", :music=>true, :writeable=>false}, :nested=>#<Class:>, :extend=>#<Class:>, :name=>\"songs\", :parse_filter=>[], :render_filter=>[], :skip_parse=>\"another lambda\", :music=>true, :writeable=>false}"
expect(normalize_inspect(options.inspect)).must_equal "{:readable=>false, :deserializer=>{:skip_parse=>\"another lambda\", :music=>true, :writeable=>false}, :nested=>#<Class:>, :extend=>#<Class:>, :name=>\"songs\", :parse_filter=>[], :render_filter=>[], :skip_parse=>\"another lambda\", :music=>true, :writeable=>false}"

# nested works.
expect(options[:nested].new(nil).hello).must_equal "hello"
expect(options[:nested].new(nil).ciao).must_equal "ciao"

expect(options[:nested].representable_attrs.get(:name).inspect).must_equal "#<Representable::Definition ==>name @options={:as=>\"Name\", :deserializer=>{:skip_parse=>\"a crazy cool instance method\"}, :name=>\"name\", :parse_filter=>[], :render_filter=>[], :skip_parse=>\"a crazy cool instance method\"}>"
expect(normalize_inspect(options[:nested].representable_attrs.get(:name).inspect)).must_equal "#<Representable::Definition ==>name @options={:as=>\"Name\", :deserializer=>{:skip_parse=>\"a crazy cool instance method\"}, :name=>\"name\", :parse_filter=>[], :render_filter=>[], :skip_parse=>\"a crazy cool instance method\"}>"
end

# :options_from and :include is optional
Expand All @@ -61,8 +61,8 @@ def hello
definitions_from: lambda { |nested| nested.definitions }
)

expect(decorator.representable_attrs.get(:id).inspect).must_equal "#<Representable::Definition ==>id @options={:name=>\"id\", :parse_filter=>[], :render_filter=>[]}>"
expect(decorator.representable_attrs.get(:title).inspect).must_equal "#<Representable::Definition ==>title @options={:writeable=>false, :deserializer=>{:skip_parse=>\"skip lambda\"}, :name=>\"title\", :parse_filter=>[], :render_filter=>[]}>"
expect(normalize_inspect(decorator.representable_attrs.get(:id).inspect)).must_equal "#<Representable::Definition ==>id @options={:name=>\"id\", :parse_filter=>[], :render_filter=>[]}>"
expect(normalize_inspect(decorator.representable_attrs.get(:title).inspect)).must_equal "#<Representable::Definition ==>title @options={:writeable=>false, :deserializer=>{:skip_parse=>\"skip lambda\"}, :name=>\"title\", :parse_filter=>[], :render_filter=>[]}>"
end


Expand All @@ -73,9 +73,9 @@ def hello
exclude_options: [:deserializer]
)

expect(decorator.representable_attrs.get(:id).inspect).must_equal "#<Representable::Definition ==>id @options={:name=>\"id\", :parse_filter=>[], :render_filter=>[]}>"
expect(decorator.representable_attrs.get(:title).inspect).must_equal "#<Representable::Definition ==>title @options={:writeable=>false, :name=>\"title\", :parse_filter=>[], :render_filter=>[]}>"
expect(decorator.representable_attrs.get(:songs).representer_module.representable_attrs.get(:name).inspect).must_equal "#<Representable::Definition ==>name @options={:as=>\"Name\", :name=>\"name\", :parse_filter=>[], :render_filter=>[]}>"
expect(normalize_inspect(decorator.representable_attrs.get(:id).inspect)).must_equal "#<Representable::Definition ==>id @options={:name=>\"id\", :parse_filter=>[], :render_filter=>[]}>"
expect(normalize_inspect(decorator.representable_attrs.get(:title).inspect)).must_equal "#<Representable::Definition ==>title @options={:writeable=>false, :name=>\"title\", :parse_filter=>[], :render_filter=>[]}>"
expect(normalize_inspect(decorator.representable_attrs.get(:songs).representer_module.representable_attrs.get(:name).inspect)).must_equal "#<Representable::Definition ==>name @options={:as=>\"Name\", :name=>\"name\", :parse_filter=>[], :render_filter=>[]}>"
end


Expand All @@ -85,8 +85,8 @@ def hello
definitions_from: lambda { |nested| nested.definitions },
) { |dfn| dfn.merge!(amazing: true) }

expect(decorator.representable_attrs.get(:id).inspect).must_equal "#<Representable::Definition ==>id @options={:name=>\"id\", :parse_filter=>[], :render_filter=>[], :amazing=>true}>"
expect(decorator.representable_attrs.get(:songs).representer_module.representable_attrs.get(:name).inspect).must_equal "#<Representable::Definition ==>name @options={:as=>\"Name\", :deserializer=>{:skip_parse=>\"a crazy cool instance method\"}, :name=>\"name\", :parse_filter=>[], :render_filter=>[], :amazing=>true}>"
expect(normalize_inspect(decorator.representable_attrs.get(:id).inspect)).must_equal "#<Representable::Definition ==>id @options={:name=>\"id\", :parse_filter=>[], :render_filter=>[], :amazing=>true}>"
expect(normalize_inspect(decorator.representable_attrs.get(:songs).representer_module.representable_attrs.get(:name).inspect)).must_equal "#<Representable::Definition ==>name @options={:as=>\"Name\", :deserializer=>{:skip_parse=>\"a crazy cool instance method\"}, :name=>\"name\", :parse_filter=>[], :render_filter=>[], :amazing=>true}>"
end

it "recursive: false only copies first level" do
Expand All @@ -97,8 +97,8 @@ def hello
exclude_options: [:deserializer]
)

expect(decorator.representable_attrs.get(:title).inspect).must_equal "#<Representable::Definition ==>title @options={:writeable=>false, :name=>\"title\", :parse_filter=>[], :render_filter=>[]}>"
expect(decorator.representable_attrs.get(:songs).representer_module.representable_attrs.get(:name).inspect).must_equal "#<Representable::Definition ==>name @options={:as=>\"Name\", :deserializer=>{:skip_parse=>\"a crazy cool instance method\"}, :name=>\"name\", :parse_filter=>[], :render_filter=>[]}>"
expect(normalize_inspect(decorator.representable_attrs.get(:title).inspect)).must_equal "#<Representable::Definition ==>title @options={:writeable=>false, :name=>\"title\", :parse_filter=>[], :render_filter=>[]}>"
expect(normalize_inspect(decorator.representable_attrs.get(:songs).representer_module.representable_attrs.get(:name).inspect)).must_equal "#<Representable::Definition ==>name @options={:as=>\"Name\", :deserializer=>{:skip_parse=>\"a crazy cool instance method\"}, :name=>\"name\", :parse_filter=>[], :render_filter=>[]}>"
end

describe ":exclude_properties" do
Expand Down Expand Up @@ -145,8 +145,8 @@ class Album < Disposable::Twin
artist = decorator.representable_attrs.get(:artist)
options = artist.instance_variable_get(:@options)
nested_extend = options[:nested]
expect(options.extend(Declarative::Inspect).inspect).must_equal "{:private_name=>:artist, :nested=>#<Class:>, :name=>\"artist\", :extend=>#<Class:>, :parse_filter=>[], :render_filter=>[]}"
expect(normalize_inspect(options.extend(Declarative::Inspect).inspect)).must_equal "{:private_name=>:artist, :nested=>#<Class:>, :name=>\"artist\", :extend=>#<Class:>, :parse_filter=>[], :render_filter=>[]}"
assert nested_extend < Representable::Decorator
expect(nested_extend.representable_attrs.get(:name).inspect).must_equal "#<Representable::Definition ==>name @options={:private_name=>:name, :name=>\"name\", :parse_filter=>[], :render_filter=>[]}>"
expect(normalize_inspect(nested_extend.representable_attrs.get(:name).inspect)).must_equal "#<Representable::Definition ==>name @options={:private_name=>:name, :name=>\"name\", :parse_filter=>[], :render_filter=>[]}>"
end
end
8 changes: 8 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
# require "pp"
require "declarative/testing"

# Helper method to normalize hash inspect format for cross-Ruby version compatibility
# Ruby 3.4 changed from {:key => value} to {key: value} in inspect output
def normalize_inspect(inspect_string)
inspect_string
.gsub(/" => /, '"=>') # Remove spaces around => for string keys: "key" => value → "key"=>value

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Cool, we have this https://github.com/trailblazer/trailblazer-core-utils/blob/master/lib/trailblazer/core/utils/inspect.rb#L14 which we could use here, but I will test your version! 💚

Would you mind making the tests assert_equal asserted, expected?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

For now, I changed only the parts related to this diff to use the assert_equal expected, actual format. Let me know if you want me to update all the other tests as well.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@apotonick Any thoughts on this?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Hi @willnet, thanks! I tested your gsub magic! 🎉 I will release trailblazer-core-utils soon and we can start using our inspect helper here. Are you happy to change the tests accordingly?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@apotonick Done! 😃

.gsub(/([{,\[]\s*)(\w+): /, '\1:\2=>') # Convert symbol keys: {key: value} → {:key=>value} (only after {, [, or ,)
end

require "disposable/twin/coercion"
DRY_TYPES_CONSTANT = Disposable::Twin::Coercion::DRY_TYPES_CONSTANT
DRY_TYPES_INT_CONSTANT = Disposable::Twin::Coercion::DRY_TYPES_VERSION < Gem::Version.new("0.13.0") ? 'Int' : 'Integer'
Expand Down
8 changes: 4 additions & 4 deletions test/twin/hash_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class Song < Disposable::Twin

song.sync

expect(model.inspect).must_equal "#<struct HashTest::Model id=nil, content={\"band\"=>{\"label\"=>{\"location\"=>\"San Francisco\"}}, \"releases\"=>[]}>"
expect(normalize_inspect(model.inspect)).must_equal "#<struct HashTest::Model id=nil, content={\"band\"=>{\"label\"=>{\"location\"=>\"San Francisco\"}}, \"releases\"=>[]}>"
end

it "#appends to collections" do
Expand All @@ -76,7 +76,7 @@ class Song < Disposable::Twin

song.sync

expect(model.inspect).must_equal "#<struct HashTest::Model id=nil, content={\"band\"=>{\"label\"=>{}}, \"releases\"=>[{\"version\"=>1}]}>"
expect(normalize_inspect(model.inspect)).must_equal "#<struct HashTest::Model id=nil, content={\"band\"=>{\"label\"=>{}}, \"releases\"=>[{\"version\"=>1}]}>"
end

it "doesn't erase existing, undeclared content" do
Expand All @@ -88,7 +88,7 @@ class Song < Disposable::Twin
# puts song.content.class.ancestors
song.sync

expect(model.inspect).must_equal "#<struct HashTest::Model id=nil, content={\"artist\"=>{}, \"band\"=>{\"label\"=>{\"location\"=>\"San Francisco\"}}, \"releases\"=>[]}>"
expect(normalize_inspect(model.inspect)).must_equal "#<struct HashTest::Model id=nil, content={\"artist\"=>{}, \"band\"=>{\"label\"=>{\"location\"=>\"San Francisco\"}}, \"releases\"=>[]}>"
end

it "doesn't erase existing, undeclared content in existing content" do
Expand All @@ -99,7 +99,7 @@ class Song < Disposable::Twin

song.sync

expect(model.inspect).must_equal "#<struct HashTest::Model id=nil, content={\"band\"=>{\"label\"=>{\"owner\"=>\"Brett Gurewitz\", \"location\"=>\"San Francisco\"}, \"genre\"=>\"Punkrock\"}, \"releases\"=>[]}>"
expect(normalize_inspect(model.inspect)).must_equal "#<struct HashTest::Model id=nil, content={\"band\"=>{\"label\"=>{\"owner\"=>\"Brett Gurewitz\", \"location\"=>\"San Francisco\"}, \"genre\"=>\"Punkrock\"}, \"releases\"=>[]}>"
end


Expand Down
4 changes: 2 additions & 2 deletions test/twin/inherit_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ class Compilation < Album

# definitions are not shared.
it do
expect(Twin::Album.definitions.get(:name).extend(Declarative::Inspect).inspect).must_equal "#<Disposable::Twin::Definition: @options={:fromage=>:_name, :private_name=>:name, :name=>\"name\"}>"
expect(Twin::Compilation.definitions.get(:name).extend(Declarative::Inspect).inspect).must_equal "#<Disposable::Twin::Definition: @options={:fromage=>:_name, :private_name=>:name, :name=>\"name\", :writeable=>false}>" # FIXME: where did :inherit go?
expect(normalize_inspect(Twin::Album.definitions.get(:name).extend(Declarative::Inspect).inspect)).must_equal "#<Disposable::Twin::Definition: @options={:fromage=>:_name, :private_name=>:name, :name=>\"name\"}>"
expect(normalize_inspect(Twin::Compilation.definitions.get(:name).extend(Declarative::Inspect).inspect)).must_equal "#<Disposable::Twin::Definition: @options={:fromage=>:_name, :private_name=>:name, :name=>\"name\", :writeable=>false}>" # FIXME: where did :inherit go?
end


Expand Down
6 changes: 3 additions & 3 deletions test/twin/unnest_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ class Twin < Disposable::Twin
end

it "copies property option" do
expect(Twin.definitions.get(:id).extend(Declarative::Inspect).inspect).must_equal %{#<Disposable::Twin::Definition: @options={:nice=>\"yes\", :private_name=>:id, :name=>\"id\", :readable=>false, :writeable=>false}>}
expect(Twin.definitions.get(:ids).extend(Declarative::Inspect).inspect).must_equal %{#<Disposable::Twin::Definition: @options={:status=>\"healthy\", :collection=>true, :private_name=>:ids, :name=>\"ids\", :readable=>false, :writeable=>false}>}
expect(normalize_inspect(Twin.definitions.get(:id).extend(Declarative::Inspect).inspect)).must_equal %{#<Disposable::Twin::Definition: @options={:nice=>\"yes\", :private_name=>:id, :name=>\"id\", :readable=>false, :writeable=>false}>}
expect(normalize_inspect(Twin.definitions.get(:ids).extend(Declarative::Inspect).inspect)).must_equal %{#<Disposable::Twin::Definition: @options={:status=>\"healthy\", :collection=>true, :private_name=>:ids, :name=>\"ids\", :readable=>false, :writeable=>false}>}
# also copies :nested.
expect(Twin.definitions.get(:email).extend(Declarative::Inspect).inspect).must_equal %{#<Disposable::Twin::Definition: @options={:private_name=>:email, :nested=>#<Class:>, :name=>\"email\", :readable=>false, :writeable=>false}>}
expect(normalize_inspect(Twin.definitions.get(:email).extend(Declarative::Inspect).inspect)).must_equal %{#<Disposable::Twin::Definition: @options={:private_name=>:email, :nested=>#<Class:>, :name=>\"email\", :readable=>false, :writeable=>false}>}
end

it "exposes accessors on top-level twin" do
Expand Down