Optional plays nice with Hashie::Mash. Should it play nice with ActionController::Parameters and OpenStruct too?
I was experimenting the use of Monads::Optional with chains of calls that imply hash access, such as when dealing with Rails parameters:
params[:profile][:resume].tempfile.path
Initially I thought that wrapping an OpenStruct around a Hash would've been a good fit for Monads::Optional but then I discovered that it doesn't work.
Here's a list of tests that I've run:
require 'monads'
require 'hashie'
require 'ostruct'
require 'action_pack'
require 'rack/test'
require 'action_controller'
params = {
foo: {
bar: 42
}
}
rails_params = ActionController::Parameters.new(params)
ostruct_params = OpenStruct.new(params)
hashie_params = Hashie::Mash.new(params)
hashie_rails_params = Hashie::Mash.new(rails_params)
ostruct_rails_params = OpenStruct.new(rails_params)
Monads::Optional.new(ostruct_params).foo
# => #<struct Monads::Optional value={:bar=>42}>
Monads::Optional.new(hashie_params).foo
# => #<struct Monads::Optional value=#<Hashie::Mash bar=42>>
Monads::Optional.new(ostruct_rails_params).foo
# => #<struct Monads::Optional value={"bar"=>42}>
Monads::Optional.new(hashie_rails_params).foo
# => #<struct Monads::Optional value=#<Hashie::Mash bar=42>>
Monads::Optional.new(ostruct_params).foo.bar
# NoMethodError: undefined method `bar' for {:bar=>42}:Hash
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `public_send'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `block in method_missing'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `block in within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `block in ensure_monadic_result'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `and_then'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:4:in `within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:10:in `method_missing'
# from (irb):23
# from /Users/olistik/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
Monads::Optional.new(hashie_params).foo.bar
# => #<struct Monads::Optional value=42>
Monads::Optional.new(ostruct_rails_params).foo.bar
# NoMethodError: undefined method `bar' for {"bar"=>42}:ActionController::Parameters
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `public_send'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `block in method_missing'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `block in within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `block in ensure_monadic_result'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `and_then'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:4:in `within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:10:in `method_missing'
# from (irb):25
# from /Users/olistik/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
Monads::Optional.new(hashie_rails_params).foo.bar
# => #<struct Monads::Optional value=42>
Monads::Optional.new(ostruct_params).foo.bar.value
# NoMethodError: undefined method `bar' for {:bar=>42}:Hash
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `public_send'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `block in method_missing'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `block in within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `block in ensure_monadic_result'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `and_then'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:4:in `within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:10:in `method_missing'
# from (irb):27
# from /Users/olistik/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
Monads::Optional.new(hashie_params).foo.bar.value
# => 42
Monads::Optional.new(ostruct_rails_params).foo.bar.value
# NoMethodError: undefined method `bar' for {"bar"=>42}:ActionController::Parameters
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `public_send'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `block in method_missing'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `block in within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `block in ensure_monadic_result'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `and_then'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:4:in `within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:10:in `method_missing'
# from (irb):29
# from /Users/olistik/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
Monads::Optional.new(hashie_rails_params).foo.bar.value
# => 42
It looks like that the only viable solution is to wrap params into a Hashie::Mash and then pass it to Monads::Optional.
Would you like/expect to have an implementation that supports OpenStruct and ActionController::Parameters?
Hi @olistik can you explain to me why are you using such examples? in my thoughts the valid examples should be:
require 'monads'
require 'ostruct'
require 'action_pack'
require 'rack/test'
require 'action_controller'
params = {
foo: {
bar: 42
}
}
rails_params = ActionController::Parameters.new(params)
ostruct_params = OpenStruct.new(params)
ostruct_rails_params = OpenStruct.new(rails_params)
Monads::Optional.new(ostruct_params).foo.bar
# NoMethodError: undefined method `bar' for {:bar=>42}:Hash
# ... but
Monads::Optional.new(ostruct_params).foo[:bar]
# => #<struct Monads::Optional value=42>
Monads::Optional.new(ostruct_rails_params).foo.bar
# NoMethodError: undefined method `bar' for {"bar"=>42}:ActionController::Parameters
# ... but
Monads::Optional.new(ostruct_rails_params).foo[:bar]
Monads::Optional.new(ostruct_params).foo.bar.value
# NoMethodError: undefined method `bar' for {:bar=>42}:Hash
# ... but
Monads::Optional.new(ostruct_params).foo[:bar].value
# => 42
Monads::Optional.new(ostruct_rails_params).foo.bar.value
# NoMethodError: undefined method `bar' for {"bar"=>42}:ActionController::Parameters
# ... but
Monads::Optional.new(ostruct_rails_params).foo[:bar].value
# => 42
Or I misunderstand something ?