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
Bug - Mash with value coercion #265
Comments
did some investigation with this example class TweetMash < Hashie::Mash
include Hashie::Extensions::Coercion
coerce_value Hash, Hashie::Mash
end
t = TweetMash.new(:user => {:email => 'foo@bar.com'}) first ....
elsif type.respond_to?(:new)
lambda do |v|
return v if v.is_a? type
type.new(v)
end
else
... line The workaround I've found is to use proc class TweetMash < Hashie::Mash
include Hashie::Extensions::Coercion
coerce_value self, -> (v) do
Hashie::Mash.new(v)
end, strict: true
coerce_value Hash, Hashie::Mash
end the first |
This smells like a bug to me. The |
AnalysisI looked into this some. The main issue with the example is that TweetMash.new(:user => {:email => 'foo@bar.com'}) effectively becomes this: TweetMash.new(TweetMash[:user => TweetMash[:email => 'foo@bar.com']]) Thus, the As noted above, if the example is changed to this: t = TweetMash.new
t.user = {:email => 'foo@bar.com'} then the coercion works as expected. This is because the coercion takes precedence over the Hash-to-Mashifying writer, since the coercion was mixed into the Mash. To me, this is a non-obvious interaction with an equally non-obvious solution. Mash's specification is such that it's expected to convert all lower keys recursively to its class (see these lines, which are called from the The only recourse for changing this that I can see is to make the Coercion mixin override some Mash behavior when it is mixed into a Mash. This sounds very fragile, since it will strictly couple that behavior to the Mash implementation, which would necessitate a change to the override whenever a pertinent piece of the Mash implementation changes. Suggested alternative implementation@antulik, here is a possible solution you could use. require 'hashie'
class UserMash < Hashie::Mash
end
class TweetHash < Hash
include Hashie::Extensions::Coercion
include Hashie::Extensions::MergeInitializer
include Hashie::Extensions::MethodAccess
coerce_value Hash, UserMash
end
t = TweetHash.new(:user => {:email => 'foo@bar.com'})
t.user.class #=> UserMash By using the lighter weight mixins instead of the heavyweight Mash, it's easier to reason about the behavior. I would much rather create the individual pieces of the overall "Tweet[H|M]ash" than rely on the behavior of Mash if I wanted to customize the behavior. There are a lot of moving pieces to Mash, so in cases like this I feel it's not well-suited to the task. Maintenance concernsWhat do we want to do about this? Should we spend the time and energy to make a Mash-specific override for Coercion? Should we write documentation about this? What would the documentation say? What is a useful byproduct of this bug report and analysis? |
First, hats off for debugging this. I think that this should be closed or left open as a bug without a fix. At best it should be a blog post in the WTF category. Mash is such a crapshoot with lots of unexpected side effects - I wouldn't pollute clean extensions with more hacks around Mash. The alternative implementation by using well behaved extensions is much better. |
it returns
TweetMash
but should returnUserMash
However it works correctly when assigning value after
The text was updated successfully, but these errors were encountered: