dry-rb / dry-initializer Goto Github PK
View Code? Open in Web Editor NEWDSL for building class initializer with params and options.
Home Page: https://dry-rb.org/gems/dry-initializer
License: MIT License
DSL for building class initializer with params and options.
Home Page: https://dry-rb.org/gems/dry-initializer
License: MIT License
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)
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:
class Foo
extend Dry::Initializer::Lazy
option :repo, default: -> { UserRepo.new }
...
end
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
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.
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
I noticed that options defined with defaults are not included in @__options__
hash. Is this expected?
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
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)
Unfortunately, this broke public behavior that rom relies on, hence this issue was reported.
Please yank 2.6.0 and release it again as 3.0.0.
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
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>'
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.
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:
dry-initializer/lib/dry/initializer/builders/attribute.rb
Lines 69 to 73 in 7a605f2
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
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?
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.
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:
type:
dry-initializer-rails
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
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]]
>> Testing.instance_method(:initialize).parameters
=> [[:req, :a], [:opt, :b], [:keyreq, :c], [:key, :d]]
Hi,
thank you for this useful gem! I have one question about the design choice of how to generate the constructor of the object.
dry-initializer/lib/dry/initializer/builders/signature.rb
Lines 8 to 10 in 3167b5a
Why is the '*' included in the parameter list? Wouldn't that just match all positional arguments regardless of whether they are defined or not?
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.
Ruby 2.7 warning is shown when using dry-validation
with option
when options are added without **
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
With refinements in my code I keep getting
Uncaught exception: wrong number of arguments (given 1, expected 0)
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.
class MyClass
include Dry::Initializer.define -> do
param :a, proc(&:to_i)
param :b, proc(&:to_i)
param :c, proc(&:to_i)
param :d, proc(&:to_i)
end
end
DRY?
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:
Dry::Initializer::UNDEFINED
when no other default value is provided (via default: proc {...}
)Dry::Initializer::UNDEFINED
values of the variablesoptional: false
(or required: true
) when no value was assigned explicitly@solnic, @flash-gordon, @AMHOL, @marshall-lee wdyt?
the :default keyword supports an "instance_exec" type of evaluation whereby default: -> { foo + bar }' reference the object in questions'
fooand
barmethods. however, when either
fooor
barare, themselves, dry-initialized attributes, the default value will resolve to
nil` due to being referenced prior to 'dry initialization'
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>
n/a
https://rubygems.org/search?utf8=%E2%9C%93&query=dry-initializer shows nothing..
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?
@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
?
I think this is due to these lines:
dry-initializer/lib/dry/initializer/builders/attribute.rb
Lines 70 to 72 in 78b0349
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?
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.
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
Provide detailed steps to reproduce, an executable script would be best.
RUBYOPT='-W:deprecated' DEPRECATION_BEHAVIOR="record" bundle exec YourTestCMD
A clear and concise description of what you expected to happen.
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_reader
s 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
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.
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
dry-struct
1.3.0.- OS: Arch LinuxI'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?
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?
@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
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
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?
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']
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
The container should be properly injected & no error should be raised when doing Repository.new
.
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,
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.