SmartKv
Best practice of writing options or configurations by strictly allowing and requiring keys.
It doesn't have to be options or configurations. You can use it for strict request body or other use cases too.
Background
Have you ever used ruby options like this:
# this example is for rails
d = DateTime.now
e = d.change(hour: 1, minute: 5)
and then move on with your life.. until you realize that the code doesn't behave as you expected it to behave.
But why? Everything looks fine, right? Yes, it does look fine.. but it's not fine.
So, what's the problem?
The problem was the option key :minute was not recognized.
Eh? :confused:
Why didn't it tell me if it was not recognized?
I wish that too.
But Hash has a default value of nil if key is not found (same thing applies to OpenStruct).
So, it will fail gracefully even if an option is actually required, unless the developer uses #fetch instead of #[].
Even if the developer uses #fetch for required keys, it doesn't actually check if there are foreign keys input.
Also, most developers won't bother checking each options' key made by the users of the library or method.
If only the source of the DateTime#change method starts like this:
# this class can be defined on its own file
class ChangeOptions < SmartKv
optional :nsec, :usec, :year, :month, :day, :hour, :min, :sec, :offset, :start
end
class DateTime
...
def change()
= ChangeOptions.new()
...
end
end
So, when you do this d.change(hour: 1, minute: 5), it will yell:
NotImplementedError: unrecognized key(s): `:minute' in ChangeOptions
Well, this is better. But, how do you know all the right options?
Type: ChangeOptions.optional_keys and ChangeOptions.required_keys.
More Usage Example
class Config < SmartKv
required :some_key, :second_key
optional :an_option
end
Config.new({some_key: "val"})
This will complain that you're not using the :second key.
If you add another key that is not recognized, it will complain too.
If there is a key that you don't always use but want it to be recognized, mark it as optional.
Inheritable
class ChildConfig < Config
required :first_key
end
ChildConfig.new({first_key: "val", second_key: "val 2"})
This will also complain that you're not using the :some_key.
Directly callable
Whatever given as input is callable directly.
c = Config.new({some_key: "val", second_key: "val 2"})
c[:some_key]
c2 = Config.new(OpenStruct.new({some_key: "val", second_key: "val 2"}))
c2.second_key
Override callable object
You can change the callable object to any class that accepts hash as input of its new class method.
class Convertable < SmartKv
required :abcd
callable_as OpenStruct
end
c = Convertable.new({abcd: 123})
c.abcd #=> 123
Not using it for options or configs?
You can choose not to use it for options or configs. Maybe for strict request body keys?
class PostBody < SmartKv
required :app_key, :secret_key
end
.
.
request.set_form_data(PostBody.new({app_key: "abc", secret_key: "def"}))
Installation
Add this line to your application's Gemfile:
gem 'smart_kv'
And then execute:
$ bundle
Coming Soon
- [X] Convertable from hash (as input) to OpenStruct (the resulting object) or another object and vice versa
- [X] Suggests corrections for unrecognized keys using DidYouMean
- [ ] Support nested/deep key value object as input
- [ ] Make some nested keys from the same parent key required and some others optional
- [ ] Accept config file (e.g.
json,yaml, etc.) or file path as input
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/styd/smart_kv. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the SmartKv project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.