Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optional plays nice with Hashie::Mash. Should it play nice with ActionController::Parameters and OpenStruct too? #6

Open
olistik opened this issue Aug 11, 2015 · 1 comment

Comments

@olistik
Copy link

olistik commented Aug 11, 2015

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?

@sclinede
Copy link
Contributor

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 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants