Git Product home page Git Product logo

dry-initializer's People

Contributors

actions-user avatar amatsuda avatar artanikin avatar belousovav avatar bjeanes avatar deepj avatar dry-bot avatar flash-gordon avatar gustavocaso avatar jeremyf avatar kml avatar maxim avatar mensfeld avatar nepalez avatar oborba avatar olleolleolle avatar petergoldstein avatar rickenharp avatar skryukov avatar solnic avatar splattael avatar spone avatar stgeneral avatar swerling avatar timriley avatar whitethunder avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dry-initializer's Issues

Option "as" feature not working

Describe the bug

Using the "as" feature described in the documentation does not work and renders an error.
https://dry-rb.org/gems/dry-initializer/3.0/params-and-options/

To Reproduce
gem install dry-initializer -v=3.0.3 (also tried 3.0.0 with the same results)
running the following in IRB:

require 'dry-initializer'

class User
  extend Dry::Initializer

  option :phone
  option :telephone, as: :phone
  option :name, optional: true
end

User.new(phone: '1234567890').phone

renders:

KeyError: User: option 'telephone' is required
	from (eval):7:in `block in __dry_initializer_initialize__'
	from (eval):7:in `fetch'
	from (eval):7:in `__dry_initializer_initialize__'
	from /home/......../.rvm/gems/ruby-2.4.6@atlas/gems/dry-initializer-3.0.0/lib/dry/initializer/mixin/root.rb:7:in `initialize'
	from (irb):11:in `new'
	from (irb):11

Expected behavior

I expected the alias behavior to work and not render an error.

Your environment

  • Affects my production application: NO
  • Ruby version: 2.4.6
  • OS: Ubuntu

Something about @__options__

@flash-gordon I'm planning to change @__options__ hash so that it collect all initialized variables - both "params" and "options". This is more consistent due to:

param :foo, optional: true
option :bar, as: :foo, optional: true

I mean, a variable can be instantiated through different "paths", it can be Schrödinger's variable of sorts.

My question is whether such an extension (adding param-initialized variables to @__options__ will be compatible to rom?

Undefine them all!

Recently I recognized one "dirty" that the initializer has 2 different responsibilities. One is assigning variables, another is their validation.

IMO validation/coercion via type:, and assigning default: value seems ok.

But optional: true provides another layer of validation which seems totally excessive.
It either assigns Dry::Initializer::UNDEFINED or raises in case no value has been assigned.

And I thought, why don't we assign Dry::Initializer::UNDEFINED to any variable by default.

class Foo
  extend Dry::Initializer::Mixin
  param :bar
end

# CURRENT behavior
Foo.new # => Boom!!!

# NEW behavior
Foo.new.instance_variable_get :@bar # => Dry::Initializer::UNDEFINED

Btw, with such a decision, you're sure you can add positional argument param at any time. Now you cannot add required param after the optional one, and this can provide tricky cases when subclassing.

class Foo
  extend Dry::Initializer::Mixin
  param :bar, optional: true
end

class Bar < Foo
  param :baz # BOOM! (should be optional because it is positioned after :bar)
end

For those who still need that validation, we could keep optional: false (to make that boom when no value was provided).

Providing any declared param and option is UNDEFINED by default, then we could "hide" this value with a bit of magics (I know, I know...). Instead of using attr_reader, we could define readers in a way:

def name
  @name == Dry::Initializer::UNDEFINED ? nil : @name
end

We still keep knowledge about whether a variable has been assigned, or not. At the same time we let the reader to behave like nil (which is another idiom for absent value in Ruby) to escape all that hacks processing Dry::Initializer::UNDEFINED when we don't care the difference.

To recap, I'd propose the following changes:

  1. Assign Dry::Initializer::UNDEFINED when no other default value is provided (via default: proc {...})
  2. Make all arguments - both hash and positional - optional (and remove constraint for the order of positional ones)
  3. Reload readers to hide Dry::Initializer::UNDEFINED values of the variables
  4. Raise an exception in case of optional: false (or required: true) when no value was assigned explicitly

@solnic, @flash-gordon, @AMHOL, @marshall-lee wdyt?

Update cops from Rubocop

Before you submit this: WE ONLY ACCEPT BUG REPORTS AND FEATURE REQUESTS

For more information see CONTRIBUTING.md.

Describe the bug
I can't run rubocop local because these errors:

vendor/bundle/ruby/2.6.0/gems/dry-initializer-3.0.3/.rubocop.yml: Metrics/LineLength has the wrong namespace - should be Layout
Error: The `Layout/IndentFirstArrayElement` cop has been renamed to `Layout/FirstArrayElementIndentation`.
(obsolete configuration found in vendor/bundle/ruby/2.6.0/gems/dry-initializer-3.0.3/.rubocop.yml, please update it)
The `Lint/HandleExceptions` cop has been renamed to `Lint/SuppressedException`.
(obsolete configuration found in vendor/bundle/ruby/2.6.0/gems/dry-initializer-3.0.3/.rubocop.yml, please update it)

A clear and concise description of what the bug is.

To Reproduce
Just run bundle exec rubocop and we receive the above error.

Provide detailed steps to reproduce, an executable script would be best.

Expected behavior
Just run rubocop on the project

A clear and concise description of what you expected to happen.

Your environment

  • Affects my production application: NO
  • We are using Ruby 2.6.5, Rails 6.0.3, rubocop 0.90.0 and dry-struct 1.3.0.- OS: Arch Linux

`nil` skip coercion

class MyClass
  include Dry::Initializer.define -> do
    param :a, proc(&:to_i)
  end
end
MyClass.new('0').a => 0
MyClass.new(nil).a => nil

Why nil is not value?

Add reader: :private option ?

Following poodr book by Sandy Metz, I tend to make internal dependencies private by default. To make my life easier as a developer, I add attr_reader for these instance variables bellow a private keyword.

DSL could look like:

class Thingy
  extend Dry::Initializer::Mixin

  option :my_internal_dependency, reader: :private
end

Plain ruby equivalent:

class Thingy
  def initialize(my_internal_dependency:)
    @my_internal_dependency = my_internal_dependency
  end

  private

  attr_reader  :my_internal_dependency
end

Would you like a PR to add this feature?

Aliases

Before making a release I'd like to discuss, whether it is a good practice to support aliases for DSL methods, namely argument <-> parameter and attribute <-> option.

https://github.com/dryrb/dry-initializer/blob/master/lib/dry/initializer.rb

Maybe we should use param and option for the DSL to "emulate" beloved yard:

class User
  extend Dry::Initializer

  param  :name,  type: String
  option :admin, type: Boolean
end

for

class User
  attr_reader :name, :type

  # @!method initialize(name, options)
  # Instantiates a user with name and options
  #
  # @param [String] name
  # @option options [Boolean] admin
  #
  def initialize(name, admin:)
    # ...
  end
end

I don't much like attribute because both parameter/param/argument, and option define their attributes. The argument inherited from function_object aka dry-function seems misleading in a broader context (we define not only the argument of the initializer, but an attribute for the instance).

Whatever, I think we should select two "proper" names and drop aliases, leaving a user to define its own when necessary.

ordering of option declarations should not prohibit forward reference in default procs

Describe the bug

the :default keyword supports an "instance_exec" type of evaluation whereby default: -> { foo + bar }' reference the object in questions' fooand barmethods. however, when eitherfooorbarare, themselves, dry-initialized attributes, the default value will resolve tonil` due to being referenced prior to 'dry initialization'

To Reproduce

require 'dry-initializer'

class A
  extend Dry::Initializer
  option :foo, default: -> { bar }
  option :bar, default: -> { 42 }
end.new

class B
  extend Dry::Initializer
  option :bar, default: -> { 42 }
  option :foo, default: -> { bar }
end.new

pp A.new #=> #<A:0x0000000103009958 @bar=42, @foo=nil>
pp B.new #=> #<B:0x000000010302aea0 @bar=42, @foo=42>

Expected behavior

@bar == @foo == 42

My environment

n/a

Non-lazy defaults are harder to use with specs

Disclaimer: sorry for clickbaity title

I'm using dry-initializer as a DSL to add basic DI to my classes:

class Foo 
  extend Dry::Initializer
  option :repo, default: -> { UserRepo.new }
  ...
end

It's been pretty convenient so far, as we're not ready to switch to more sophisticated solutions like dry-system / dry-container + dry-auto_inject.

However, there's an issue for us: the default value evaluates whenever we instantiate the new object. That leads to the classic banana monkey jungle problem:

I have a complex domain service with a bunch of dependencies. The service needs those dependencies to perform certain actions, which we may or may not want to run. But it doesn't matter — you still have to initialize all the dependencies.

The best example is specs — I always have to carry around a bunch of boilerplate context just to make the tests execute. It doesn't matter if we never use the dependencies — the library demands them instantly.

My proposal: add an option to define lazily initialized options and params.

I've got two implementations in my mind:

  1. A new module which makes all options / params lazy
class Foo 
  extend Dry::Initializer::Lazy
  option :repo, default: -> { UserRepo.new }
  ...
end
  1. A new keyword argument, which enables lazy loading for each option
class Foo 
  extend Dry::Initializer
  option :repo, default: -> { UserRepo.new }, lazy: true
  ...
end

In the end, all the lazy initialization should work like memoization. Probably should work like this:

def repo
  @repo ||= defaults[:repo].call(...)
end

Overriding options not working in inherited class

I'm going to explain it with an example:

class Product
  extend ::Dry::Initializer

  option :name
end

class Book < Product
  option :data
  option :name, default: proc { data[:name] }
end

When I used these classes in the console this happend:

irb(main):001:0> Product.new(name: "haha")
#<Product:0x00007fc3418ab400 @__options__={:name=>"haha"}, @name="haha">
irb(main):002:0> Book.new(data: {name: "haha"})
NoMethodError: undefined method `[]' for nil:NilClass
	from /Users/pieter/Repos/main-app/app/lib/book.rb:3:in `block in <class:Book>'
	from (eval):4:in `instance_exec'
	from (eval):4:in `block in __initialize__'
	from (eval):4:in `fetch'
	from (eval):4:in `__initialize__'
	from /Users/pieter/.rbenv/versions/2.3.5/lib/ruby/gems/2.3.0/gems/dry-initializer-1.4.1/lib/dry/initializer/instance_dsl.rb:8:in `initialize'
	from (irb):8:in `new'
	from (irb):8
	from /Users/pieter/.rbenv/versions/2.3.5/lib/ruby/gems/2.3.0/gems/railties-5.0.2/lib/rails/commands/console.rb:65:in `start'
	from /Users/pieter/.rbenv/versions/2.3.5/lib/ruby/gems/2.3.0/gems/railties-5.0.2/lib/rails/commands/console_helper.rb:9:in `start'
	from /Users/pieter/.rbenv/versions/2.3.5/lib/ruby/gems/2.3.0/gems/railties-5.0.2/lib/rails/commands/commands_tasks.rb:78:in `console'
	from /Users/pieter/.rbenv/versions/2.3.5/lib/ruby/gems/2.3.0/gems/railties-5.0.2/lib/rails/commands/commands_tasks.rb:49:in `run_command!'
	from /Users/pieter/.rbenv/versions/2.3.5/lib/ruby/gems/2.3.0/gems/railties-5.0.2/lib/rails/commands.rb:18:in `<top (required)>'
	from bin/rails:4:in `require'
	from bin/rails:4:in `<main>'

Reading your docs I thought I could override options in subclasses:
http://dry-rb.org/gems/dry-initializer/inheritance/
And I can refer to other arguments in defaults:
http://dry-rb.org/gems/dry-initializer/optionals-and-defaults/

So what am I doing wrong? Is their something I missed?

Slow getters

After updating dry-initializer in ROM specs became significantly slower (two times-ish). I found out that after introducing == UNDEFINED check accessing readers became ~2.5x more expensive:

Bundler.require(:benchmarks)

require "dry-initializer"

class DryObject
  extend Dry::Initializer::Mixin

  param  :foo, default: proc { "FOO" }
  option :bar, default: proc { "BAR" }
end

puts "Benchmark for reading attributes"

dry_obj = DryObject.new
struct_obj = Struct.new(:foo, :bar).new("FOO", "BAR")

Benchmark.ips do |x|
  x.config time: 15, warmup: 10

  x.report("dry object") do
    dry_obj.foo
    dry_obj.bar
  end

  x.report("struct object") do
    struct_obj.foo
    struct_obj.bar
  end

  x.compare!
end
Benchmark for reading attributes
Warming up --------------------------------------
          dry object   174.899k i/100ms
       struct object   247.535k i/100ms
Calculating -------------------------------------
          dry object      3.396M (± 6.3%) i/s -     50.896M in  15.050659s
       struct object      8.177M (± 6.6%) i/s -    122.282M in  15.023846s

Comparison:
       struct object:  8177160.1 i/s
          dry object:  3395932.8 i/s - 2.41x  slower

Using attr_readers instead shows that == UNDEFINED checks kill the performance:

with attr_reader :#{target}
=====
Benchmark for reading attributes
Warming up --------------------------------------
          dry object   269.201k i/100ms
       struct object   258.059k i/100ms
Calculating -------------------------------------
          dry object      9.289M (± 6.9%) i/s -    138.639M in  15.000684s
       struct object      8.250M (± 6.1%) i/s -    123.352M in  15.012100s

Comparison:
          dry object:  9288521.2 i/s
       struct object:  8249793.6 i/s - same-ish: difference falls within error

This slows down ROM quite a bit because most of its classes uses #== and #eql?methods defined by dry-equalizer which in order call accessors.

Because of such a trade-off we need to make these checks optional or remove them completely. Of course "optional" implies an instance variable have to be either not initialized or set to nil.

P.S. It looks like dry-initializer should have benchmarks for getting values ;)

/cc @solnic

Rewrite module extension

Thanks to @sergey-chechaev for this issue.

Because extending a klass with the Dry::Initializer defines #initialize directly, later redefinition of the #initialize causes warning like this:

warning: method redefined; discarding old initialize
rvm/gems/ruby-2.3.0/gems/dry-initializer-1.4.0/lib/dry/initializer/dsl.rb:35: warning: previous definition of initialize was here

Instead of direct addition, we need to isolate that methods inside another module.

Ruby 3 support

Describe the bug

Hey guys,
We're using DRY-ecosystem and extremely enjoying its flexibility.
During migration to ruby 3, was found that dry-initializer produces the below warning which blocks the migration

dry-initializer-3.1.1/lib/dry/initializer/mixin/root.rb:11: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
(eval):2: warning: The called method `__dry_initializer_initialize__' is defined here

To Reproduce

Provide detailed steps to reproduce, an executable script would be best.

RUBYOPT='-W:deprecated' DEPRECATION_BEHAVIOR="record" bundle exec YourTestCMD

Expected behavior

A clear and concise description of what you expected to happen.

My environment

  • Affects my production application: YES
  • Ruby version: 2.7
  • OS: linux

[Feature Request] Option to skip type checking in certain cases

We have a number of tests that will pass in object_double([class]) when we are running specs for our Rails application, but these all fail on the Types.Instance([class]) checks we are using. As it is difficult to add an option for a test double, since these classes are not even loaded in production environments, there is not an easy way to skip these type checks when needed.

It would be great if we could pass in an extra option to ignore the type checking in certain scenarios. Something like option :arg, type: Types.Instance(Foo), ignore_type: Rails.env.test? would be ideal.

Getters provide inconsistent results in debugging

I'm seeing an issue where something like this has happened:

if comment.present?
  byebug
end

This then creates a state where this is the case:

(byebug) comment
nil
(byebug) comment.present?
false
(byebug) @comment
"foo"
(byebug) send(:comment)
"foo"

Placing byebug outside the block has the same result.

Default value only if not nil

Hello. Initializer options have presence checking by default, and return option 'option' is required when the option is not present, but with default value this checking does not work, even when the default value is nil.
Is it possible to use presence checking along with a default value?

dry-initializer relies on Symbol#to_proc#arity being -1

Taking the README example:

class User
  extend Dry::Initializer
  param :name, proc(&:to_s)
end

This code only works if Symbol#to_proc#arity is -1, which is currently the case on MRI.

But semantically, Symbol#to_proc#arity should be -2, as at least one argument needs to be passed (otherwise, ArgumentError: no receiver given is raised).

It would be great if dry-initializer would not rely on this, but I guess it is difficult.
Maybe one hacky way is noticing the Proc#inspect has (&:...) in such a case:

> proc(&:to_s)
=> #<Proc:0x000055f574149550(&:to_s)>

But that is probably even more fragile.
More ideas welcome :)

I think proc(&:to_s) looks a bit odd (notably, proc is just an identity function in this case, and only serve to transform the block to a positional argument).
If changing the API is an option then this could be made to work easily:

class User
  extend Dry::Initializer
  param :name, :to_s
end

This could maybe also work, but it would encourage users to pass blocks which are non-lambda Procs, which might be undesired, so I think the API just above is better.

param :name, &:to_s

Alternatively the arity check could be changed to only pass self if arity == 2, requiring to use a 2-arguments lambda to take the extra self argument.

See oracle/truffleruby#1462 for more context.

Tokenize hash params

I'm not sure if it's possible yet but It would be awesome if we could pass params e.g from controller to the initializer constructor:

class Foo
  extend Dry::Initializer
  option :a
  option :b
end

Foo.new({ 'a' => '1', 'b' => '2' })

In Dry::Struct we can just do transform_keys(&:to_sym), not sure about Dry::Initializer though.

Move param/option definitions to DSL

@nepalez, just wondering what your thoughts are on moving the param/option definitions to a DSL, this would prevent expanding the interface of classes making use of dry-initializer, proposed interface:

class User
  include Dry::Initializer.define do
    # Params of the initializer along with corresponding readers
    param  :name, type: String
    param  :role, default: proc { 'customer' }
    # Options of the initializer along with corresponding readers
    option :admin, default: proc { false }
  end
end

This way the only modification to the interface would be the #initialize method

Remove support for types except for dry-types

Using ruby classes and modules as types is actually an antipattern from times of Good Old Virtus.

In the next v0.4.0 I'm planning to:

  • remove support for PORO classes/modules in type:
  • move control of types (only dry-types) to a plugin like dry-initializer-rails

[Feature] adding `options` class method

Hey guys,

Great Job on this one, a quick suggestion, do you think this pattern is a good idea:

class User
  extend Dry::Initializer::Mixin

  options :admin, :phone, :email, :address

  # the rest ...
end

If yes, let me know and I'll submit a PR ;)

Cheers,

Passing an unloaded ActiveRecord relation as an option causes it to be loaded

I think this is due to these lines:

"#{@val} = #{@item}.type.call(#{@val}) unless #{@val} == #{@null}"
else
"#{@val} = #{@item}.type.call(#{@val}, self) unless #{@val} == #{@null}"

At least in my version of Rails (4.0 as I'm in progress of upgrading older app), calling #== on certain scopes (such as ActiveRecord::AssociationRelation) will cause it to evaluate and compare the results. I am intending to pass in a base scope to operate over so evaluating it here can load potentially unbounded number of items.

Since @null is a known value in all cases and the behaviour of its #== method is known, could we perhaps swap the operands such that #{@null} == #{@val} is used instead?

Here's a test case I whipped up that demonstrates the issue I'm experiencing:

begin
  require "bundler/inline"
rescue LoadError => e
  $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
  raise e
end

gemfile(true) do
  source "https://rubygems.org"

  gem 'pry'

  gem 'rails'
  gem 'sqlite3'
  gem 'dry-initializer'
end

require "active_record"
require "action_controller/railtie"

ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: "dry-test.db")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :authors, force: true do |t|
    t.string :name
  end

  create_table :posts, force: true do |t|
    t.references :author
    t.string :title
    t.text :body
    t.timestamps
  end
end

class Author < ActiveRecord::Base
  has_many :posts
end

class Post < ActiveRecord::Base
  belongs_to :author
end

author = Author.create! name: 'Bo Jeanes'
author.posts.create!(title: 'Post 1', body: ':(')
author.posts.create!(title: 'Post 2', body: '...')

class Thinger
  extend Dry::Initializer

  option :scope, ->(x) { x }, default: -> do
    Post.all
  end

  def call
    scope.where('1 = 0').count
  end
end

class TestApp < Rails::Application
  config.logger = Logger.new($stdout)
  Rails.logger = config.logger
end

scope = author.posts
raise 'scope already loaded' if scope.loaded?
Thinger.new(scope: scope).call
raise 'scope loaded by initializer' if scope.loaded?

Option not properly working when combine with dry-container

module Container
  extend Dry::Container::Mixin

  register(:deps) {}
end

module Foo
  Import = Dry::AutoInject(Container)
end

class SomeService
  extend Dry::Initializer
  include Foo::Import['deps']

  option :foo
end

Then try to instantiate the service with:

SomeService.new(foo: 1)
KeyError: SomeService: option 'foo' is required
from (eval):4:in `block in __dry_initializer_initialize__'

I'm using dry-initializer (2.3.0)

Feature - method signatures

Feature description

Would be nice if dry-initializer would set the instance method initializer signature.

source: https://bugs.ruby-lang.org/issues/17596.

class Testing
  def initialize(a, b = 'default_b', c:, d: 'default_d')
  end
end
>> Testing.instance_method(:initialize).parameters
=>  [[:req, :a], [:opt, :b], [:keyreq, :c], [:key, :d]]

would be nice to be able to do the same with dry initializer

To Reproduce

class Testing
  extend Dry::Initializer
  param :a
  param :b,  default: proc { 'default_b' }
  option :c
  option :d, default: proc { 'default_d' }, optional: true
end
 Testing.instance_method(:initialize).parameters
=> [[:rest, :args]]

Expected behavior

>> Testing.instance_method(:initialize).parameters
=>   [[:req, :a], [:opt, :b], [:keyreq, :c], [:key, :d]]

[Security] Workflow ci.yml is using vulnerable action actions/checkout

The workflow ci.yml is referencing action actions/checkout using references v1. However this reference is missing the commit a6747255bd19d7a757dbdda8c654a9f84db19839 which may contain fix to the some vulnerability.
The vulnerability fix that is missing by actions version could be related to:
(1) CVE fix
(2) upgrade of vulnerable dependency
(3) fix to secret leak and others.
Please consider to update the reference to the action.

Overriding of dry-container dependencies creates unexpected behaviour

Describe the bug

When including a module into a class that is extended with dry-initializer after including another module which imports a dependency using dry-container, unexpected behaviour is observed

To Reproduce

module Container
  extend Dry::Container::Mixin

  register(:deps) { 1 }
end

module Foo
  Import = Dry::AutoInject(Container)
end

module Bar
  def self.included(base)
    base.extend Dry::Initializer
    base.option :foo
  end
end

module Foobar
  def self.included(base)
    base.include Foo::Import['deps']
  end
end

class SomeService
  include Foobar
  include Bar
end

Behaviour observed

service = SomeService.new(foo: 1)
service.foo => 1
service.deps => nil # expected 1

I am currently running versions:

*dry-initializer (3.1.1)
*dry-auto_inject (0.9.0)
*dry-container (0.11.0)

Arity check for proc to include self break dry-type arrays

Upgrading to 2.3.0 while using arrays as input types to an initializer causes issues. This appears to be related to the way self is called when the arity is not 0. I think the intent was to allow a proc to contain a second argument and capture self.

Here's the discussion on the forum: https://discourse.dry-rb.org/t/interaction-between-di-2-3-and-dt/380
Here's an implementation that breaks: https://github.com/kbacha/types_and_initializer_bug

You can see the arity check here:

if arity.abs == 1
"#{@val} = #{@item}.type.call(#{@val}) unless #{@val} == #{@null}"
else
"#{@val} = #{@item}.type.call(#{@val}, self) unless #{@val} == #{@null}"
end

Which ends up passing self to the method param in dry type arrays:
https://github.com/dry-rb/dry-types/blob/506b7588a7b776cd59e2e27538256472e4c8d3df/lib/dry/types/array/member.rb#L19-L21

Any way to make initializer raise on unknown options?

If you define regular ruby class with keyword arguments, it will raise if you provide it unknown options:

class Whatever
  def initialize(a:, b:)
  end
end
irb(main):006:0> Whatever.new(a: 1, b: 2, c: 3)
Traceback (most recent call last):
        4: from /usr/bin/irb:11:in `<main>'
        3: from (irb):6
        2: from (irb):6:in `new'
        1: from (irb):2:in `initialize'
ArgumentError (unknown keyword: c)

Dry-initializer does not.

Is there any way to make dry-initializer raise on unknown options?

Why is downcase applied on target parameter / option?

target = target.to_s.to_sym.downcase

I try to use this Gem before calling an external API, where the attributes is expected in a hash in CamelCase format.
The .to_h on the class, also converts the hash-keys to strings and not symbols.

Expected

{:administrativeRoles=>["USER"], :answerPlaces=>[{:type=>"SOFT"}]}

Result

{"administrativeroles"=>["USER"], "answerplaces"=>[{"type"=>"SOFT"}]}

Code

      option :administrativeRoles, [], default: proc { ['USER'] }
      option :answerPlaces, [] do
        option :type, proc(&:to_s)
      end

README code not working

require 'dry-initializer'

class User
  extend Dry::Initializer::Mixin

  param  :name,  type: String
  param  :role,  default: proc { 'customer' }
  option :admin, default: proc { false }
end
/Users/joe/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/dry-initializer-0.4.0/lib/dry/initializer/plugins/type_constraint.rb:9:in `call': String used as constraint for argument 'name' is not a dry-type. (Dry::Initializer::Errors::TypeConstraintError)
    from /Users/joe/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/dry-initializer-0.4.0/lib/dry/initializer/plugins/base.rb:18:in `call'
    from /Users/joe/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/dry-initializer-0.4.0/lib/dry/initializer/builder.rb:53:in `block in define'
    from /Users/joe/.rbenv/versions/2.3.1/lib/ruby/2.3.0/set.rb:306:in `each_key'
    from /Users/joe/.rbenv/versions/2.3.1/lib/ruby/2.3.0/set.rb:306:in `each'
    from /Users/joe/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/dry-initializer-0.4.0/lib/dry/initializer/builder.rb:53:in `map'
    from /Users/joe/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/dry-initializer-0.4.0/lib/dry/initializer/builder.rb:53:in `define'
    from /Users/joe/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/dry-initializer-0.4.0/lib/dry/initializer/mixin.rb:16:in `param'
    from test.rb:6:in `<class:User>'
    from test.rb:3:in `<main>'

Deprecation warning with Ruby 2.7: Using the last argument as keyword parameters is deprecated

Describe the bug

(eval):2: warning: The called method `dry_initializer_initialize' is defined here
dry-initializer-3.0.3/lib/dry/initializer/mixin/root.rb:7: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call

To Reproduce

Using dry-initializer with Ruby 2.7 results in the deprecation warning mentioned above.

Expected behavior

No warning should be raised.

Your environment

  • Affects my production application: NO -> still using Ruby 2.6, but I would like to upgrade to Ruby 2.7, which I still could, but I prefer not.
  • Ruby version: 2.7
  • OS: Linux

Ruby 2.7 warning

Ruby 2.7 warning is shown when using dry-validation with option when options are added without **

  • Affects my production application: YES
  • Ruby version: ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]
  • Reproducing script:
require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'dry-validation', require: false
end

require 'dry-validation'

class CustomForm < Dry::Validation::Contract
  option :foo
  params {}
end

options = { foo: 'foo' }
CustomForm.new(**options) # no warning
CustomForm.new(options) # shows warning
# /.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/dry-initializer-3.0.4/lib/dry/initializer/mixin/root.rb:7: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
# (eval):2: warning: The called method `__dry_initializer_initialize__' is defined here

Upgrading to ruby 3 causes the initializer to unproperly handle auto-injected args

Describe the bug

While upgrading an app from Ruby 2.6.6 to 3.0.0, our repos crashed with:

KeyError: Api::Organizations::Repositories::Organization: option 'container' is required
from (eval):4:in `block in __dry_initializer_initialize__'

Here's a stack trace from a test:

1) Api::Organizations::Repositories::Organization#create creates a new organization
     Failure/Error: let(:repository) { described_class.new }

     KeyError:
       Api::Organizations::Repositories::Organization: option 'container' is required
     # (eval):4:in `block in __dry_initializer_initialize__'
     # (eval):4:in `fetch'
     # (eval):4:in `__dry_initializer_initialize__'
     # /bundle/vendor/ruby/3.0.0/gems/dry-initializer-3.0.4/lib/dry/initializer/mixin/root.rb:7:in `initialize'
     # /bundle/vendor/ruby/3.0.0/gems/rom-repository-5.2.2/lib/rom/repository.rb:111:in `initialize'
     # /bundle/vendor/ruby/3.0.0/gems/rom-repository-5.2.2/lib/rom/repository/root.rb:62:in `initialize'
     # /bundle/vendor/ruby/3.0.0/gems/dry-auto_inject-0.7.0/lib/dry/auto_inject/strategies/args.rb:59:in `initialize'
     # /bundle/vendor/ruby/3.0.0/gems/rom-repository-5.2.2/lib/rom/repository/class_interface.rb:62:in `new'
     # /bundle/vendor/ruby/3.0.0/gems/rom-repository-5.2.2/lib/rom/repository/class_interface.rb:62:in `new'
     # /bundle/vendor/ruby/3.0.0/gems/dry-auto_inject-0.7.0/lib/dry/auto_inject/strategies/args.rb:20:in `block (2 levels) in define_new'
     # ./spec/api/organizations/repositories/organization_spec.rb:7:in `block (2 levels) in <top (required)>'
     # ./spec/api/organizations/repositories/organization_spec.rb:15:in `block (4 levels) in <top (required)>'
     # ./spec/api/organizations/repositories/organization_spec.rb:15:in `block (3 levels) in <top (required)>'

That repo inherits a base repo in which the container is auto-injected:

include Import.args['persistence.schemas.public']

To Reproduce

require 'rom'
require 'dry/container'
require 'dry/auto_inject'
require 'sqlite3'

rom = ROM.container(:sql, 'sqlite::memory')
container = Dry::Container.new
container.register(:rom, rom)

Import = Dry::AutoInject(container)

class Repository < ROM::Repository::Root
  include Import.args[:rom]
end

Repository.new

Expected behavior

The container should be properly injected & no error should be raised when doing Repository.new.

My environment

  • Affects my production application: NO
  • Ruby version: 3.0.0
  • OS: Pop!_OS 20.10

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.