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
Feature: Mash::UnderscoreKeys Extensions #566
base: master
Are you sure you want to change the base?
Changes from 4 commits
dcf0f18
bcb90ff
6630705
fdbb118
0edb260
63f1a44
8fc7700
cf9ac84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -783,6 +783,30 @@ end | |
|
||
However, on Rubies less than 2.0, this means that every key you send to the Mash will generate a symbol. Since symbols are not garbage-collected on older versions of Ruby, this can cause a slow memory leak when using a symbolized Mash with data generated from user input. | ||
|
||
### UnderscoreKeys | ||
This extension can be mixed into a Mash to change the default behavior of converting keys to be underscore. After mixing this extension into a Mash, the Mash will convert all string keys to underscore. It can be useful to use with external source hashes, which maybe contain hyphens or CamelCase. | ||
|
||
```ruby | ||
class UnderscoreMash < ::Hashie::Mash | ||
include Hashie::Extensions::Mash::UnderscoreKeys | ||
end | ||
|
||
mash = UnderscoreMash.new | ||
mash.updatedAt = 'Today' #=> 'Today' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: Can we lowercase |
||
mash.updatedAt #=> 'Today' | ||
mash[:updated_at] #=> 'Today' | ||
mash['updated_at'] #=> 'Today' | ||
mash.updated_at #=> 'Today' | ||
mash.to_hash #=> {"updated_at"=>true} | ||
``` | ||
|
||
The other benefit is hashes that have hyphens can be accessed with methods | ||
```ruby | ||
mash = UnderscoreMash.new('created-at': 'tomorrow') #=> {"created_at"=>"tomorrow"} | ||
|
||
mash.created_at #=> 'tomorrow' | ||
``` | ||
|
||
### DefineAccessors | ||
|
||
This extension can be mixed into a Mash so it makes it behave like `OpenStruct`. It reduces the overhead of `method_missing?` magic by lazily defining field accessors when they're requested. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
module Hashie | ||
module Extensions | ||
module Mash | ||
# Overrides the indifferent access of a Mash to keep keys in | ||
# underscore format. | ||
# | ||
# @example | ||
# class UnderscoreMash < ::Hashie::Mash | ||
# include Hashie::Extensions::Mash::UnderscoreKeys | ||
# end | ||
# | ||
# mash = UnderscoreMash.new(symbolKey: { dataFrom: { java: true, javaScript: true } }) | ||
# mash.symbol_key.data_from.java #=> true | ||
# mash.symbolKey.dataFrom.java_script #=> true | ||
module UnderscoreKeys | ||
def self.included(base) | ||
raise ArgumentError, "#{base} must descent from Hashie::Mash" unless base <= Hashie::Mash | ||
end | ||
|
||
private | ||
|
||
def _underscore(string) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems quite a bit different from, say, https://github.com/rails/rails/blob/04972d9b9ef60796dc8f0917817b5392d61fcf09/activesupport/lib/active_support/inflector/methods.rb#L96, what are the differences? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess I went pretty old school and grabbed the version from 3.1 haha I can update it to reflect the new version, I think there are likely performance and edge case improvements. The one difference to this method that I added was converting spaces to underscores as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure we want a different "underscore" behavior than ActiveSupport? Why? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I was thinking this could be slightly different than Rails. The intent here is making method names compatible with the Ruby's method naming conversions. But I think it could also go a step further, Maybe this should be called But I'm also okay just keeping it exactly the same as rails. Either way works for me, just feel the former is more convenient There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think about implementing a generic extension that lets me transform keys whichever way I want ( |
||
string.gsub(/::/, '/') | ||
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') | ||
.gsub(/([a-z\d])([A-Z])/, '\1_\2') | ||
.tr('- ', '_') | ||
.downcase | ||
end | ||
|
||
# Ensures all keys are underscore formatting. | ||
# | ||
# @param [Object, String, Symbol] key the key to access. | ||
# @return [Object] the value assigned to the key. | ||
def convert_key(key) | ||
_underscore(key.to_s) if key.is_a?(String) || key.is_a?(Symbol) | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
require 'spec_helper' | ||
|
||
RSpec.describe Hashie::Extensions::Mash::UnderscoreKeys, :aggregate_failures do | ||
let(:underscore_mash) do | ||
Class.new(Hashie::Mash) do | ||
include Hashie::Extensions::Mash::UnderscoreKeys | ||
end | ||
end | ||
|
||
it 'allows access to keys via original name' do | ||
original = { | ||
dataFrom: { java: true, javaScript: true }, | ||
DataSource: { GitHub: true }, | ||
'created-at': 'today' | ||
} | ||
|
||
mash = underscore_mash.new(original) | ||
|
||
expect(mash.dataFrom.java).to be(true) | ||
expect(mash[:dataFrom][:java]).to be(true) | ||
expect(mash['dataFrom']['java']).to be(true) | ||
|
||
expect(mash.dataFrom.javaScript).to be(true) | ||
expect(mash[:dataFrom][:javaScript]).to be(true) | ||
expect(mash['dataFrom']['javaScript']).to be(true) | ||
|
||
expect(mash.DataSource.GitHub).to be(true) | ||
expect(mash[:DataSource][:GitHub]).to be(true) | ||
expect(mash['DataSource']['GitHub']).to be(true) | ||
|
||
# can't currently call a method with a hyphen | ||
# expect(mash.call(:'created-at')).to eq('today') | ||
expect(mash[:'created-at']).to eq('today') | ||
expect(mash['created-at']).to eq('today') | ||
end | ||
|
||
it 'allows access to underscore key names' do | ||
original = { | ||
dataFrom: { java: true, javaScript: true }, | ||
DataSource: { GitHub: true }, | ||
'created-at': 'today' | ||
} | ||
|
||
mash = underscore_mash.new(original) | ||
|
||
expect(mash.data_from.java).to be(true) | ||
expect(mash[:data_from][:java]).to be(true) | ||
expect(mash['data_from']['java']).to be(true) | ||
|
||
expect(mash.data_from.java_script).to be(true) | ||
expect(mash[:data_from][:java_script]).to be(true) | ||
expect(mash['data_from']['java_script']).to be(true) | ||
|
||
expect(mash.data_source.git_hub).to be(true) | ||
expect(mash[:data_source][:git_hub]).to be(true) | ||
expect(mash['data_source']['git_hub']).to be(true) | ||
|
||
expect(mash.created_at).to eq('today') | ||
expect(mash[:'created-at']).to eq('today') | ||
expect(mash['created-at']).to eq('today') | ||
end | ||
|
||
it 'allows mixing and matching of underscore and camelCase' do | ||
original = { | ||
dataFrom: { java: true, javaScript: true }, | ||
DataSource: { GitHub: true }, | ||
'created-at': 'today' | ||
} | ||
|
||
mash = underscore_mash.new(original) | ||
|
||
expect(mash.dataFrom.java_script).to be(true) | ||
expect(mash[:data_from][:javaScript]).to be(true) | ||
expect(mash['dataFrom']['java_script']).to be(true) | ||
|
||
expect(mash.DataSource.git_hub).to be(true) | ||
expect(mash[:data_source][:GitHub]).to be(true) | ||
expect(mash['DataSource']['git_hub']).to be(true) | ||
end | ||
|
||
it 'converts spaces to underscore' do | ||
original = { 'hashie mashie': 'mashie hashie' } | ||
|
||
mash = underscore_mash.new(original) | ||
|
||
expect(mash.hashie_mashie).to eq('mashie hashie') | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just missing a period.