-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a5d7484
Showing
18 changed files
with
544 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
*.gem | ||
*.rbc | ||
.bundle | ||
.config | ||
.yardoc | ||
Gemfile.lock | ||
InstalledFiles | ||
_yardoc | ||
coverage | ||
doc/ | ||
lib/bundler/man | ||
pkg | ||
rdoc | ||
spec/reports | ||
test/tmp | ||
test/version_tmp | ||
tmp | ||
*.bundle | ||
*.so | ||
*.o | ||
*.a | ||
mkmf.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
--color | ||
--format=progress |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ruby-2.3.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
language: ruby | ||
rvm: | ||
- 2.2.2 | ||
- 2.3.0 | ||
gemfile: | ||
- Gemfile |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
## BetterSettings 1.0.0 (2017-12-29) ## | ||
|
||
* Initial Release. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
source 'https://rubygems.org' | ||
|
||
gemspec |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
Copyright (c) 2017 Máximo Mussini | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining | ||
a copy of this software and associated documentation files (the | ||
"Software"), to deal in the Software without restriction, including | ||
without limitation the rights to use, copy, modify, merge, publish, | ||
distribute, sublicense, and/or sell copies of the Software, and to | ||
permit persons to whom the Software is furnished to do so, subject to | ||
the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be | ||
included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
BetterSettings [![Gem Version](https://img.shields.io/gem/v/better_settings.svg?colorB=e9573f)](https://rubygems.org/gems/better_settings) [![Build Status](https://travis-ci.org/ElMassimo/better_settings.svg)](https://travis-ci.org/ElMassimo/better_settings) [![Coverage Status](https://coveralls.io/repos/github/ElMassimo/better_settings/badge.svg?branch=master)](https://coveralls.io/github/ElMassimo/better_settings?branch=master) [![Inline docs](http://inch-ci.org/github/ElMassimo/better_settings.svg)](http://inch-ci.org/github/ElMassimo/better_settings) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ElMassimo/better_settings/blob/master/LICENSE.txt) | ||
======================================= | ||
|
||
A robust settings library that can read YML files and provide an immutable object allowing to access settings through method calls. Can be used in __any Ruby app__, __not just Rails__. | ||
|
||
### Installation | ||
|
||
Add this line to your application's Gemfile: | ||
|
||
```ruby | ||
gem 'better_settings' | ||
``` | ||
|
||
And then execute: | ||
|
||
$ bundle | ||
|
||
Or install it yourself as: | ||
|
||
$ gem install better_settings | ||
|
||
### Usage | ||
|
||
#### 1. Define a class | ||
|
||
Instead of defining a Settings constant for you, that task is left to you. Simply create a class in your application | ||
that looks like: | ||
|
||
```ruby | ||
# app/models/settings.rb | ||
class Settings < BetterSettings | ||
source Rails.root.join('config', 'application.yml'), namespace: Rails.env | ||
end | ||
``` | ||
|
||
#### 2. Create your settings | ||
|
||
Notice above we specified an absolute path to our settings file called `application.yml`. This is just a typical YAML file. | ||
Also notice above that we specified a namespace for our environment. A namespace is just an optional string that corresponds to a key in the YAML file. | ||
|
||
Using a namespace allows us to change our configuration depending on our environment: | ||
|
||
```yaml | ||
# config/application.yml | ||
defaults: &defaults | ||
port: 80 | ||
mailer: | ||
root: www.example.com | ||
dynamic: <%= "Did you know you can use ERB inside the YML file? Env is #{ Rails.env }." %> | ||
|
||
development: | ||
<<: *defaults | ||
port: 3000 | ||
|
||
test: | ||
<<: *defaults | ||
|
||
production: | ||
<<: *defaults | ||
``` | ||
|
||
#### 3. Access your settings | ||
|
||
>> Rails.env | ||
=> "development" | ||
>> Settings.mailer | ||
=> "#<Settings ... >" | ||
>> Settings.mailer.root | ||
=> "www.example.com | ||
>> Settings.port | ||
=> 3000 | ||
>> Settings.dynamic | ||
=> "Did you know you can use ERB inside the YML file? Env is development." | ||
You can use these settings anywhere, for example in a model: | ||
|
||
class Post < ActiveRecord::Base | ||
self.per_page = Settings.pagination.posts_per_page | ||
end | ||
|
||
### Advanced Setup ⚙ | ||
Name it `Settings`, name it `Config`, name it whatever you want. Add as many or as few as you like, read from as many files as necessary (nested keys will be merged). | ||
|
||
We usually read a few optional files for the `development` and `test` environment, which allows each developer to override some settings in their own local environment (we git ignore `development.yml` and `test.yml`). | ||
|
||
```ruby | ||
# app/models/settings.rb | ||
class Settings < BetterSettings | ||
source Rails.root.join('config', 'application.yml'), namespace: Rails.env | ||
source Rails.root.join('config', 'development.yml'), namespace: Rails.env, optional: true if Rails.env.development? | ||
source Rails.root.join('config', 'test.yml'), namespace: Rails.env, optional: true if Rails.env.test? | ||
end | ||
``` | ||
Our `application.yml` looks like this: | ||
```yaml | ||
# application.yml | ||
defaults: &defaults | ||
auto_logout: false | ||
secret_key_base: 'fake_secret_key_base' | ||
|
||
server_defaults: &server_defaults | ||
<<: *defaults | ||
auto_logout: true | ||
secret_key: <%= ENV['SECRET_KEY'] %> | ||
|
||
development: | ||
<<: *defaults | ||
host: 'localhost' | ||
|
||
test: | ||
<<: *defaults | ||
host: '127.0.0.1' | ||
|
||
staging: | ||
<<: *server_defaults | ||
host: 'staging.example.com' | ||
|
||
production: | ||
<<: *server_defaults | ||
host: 'example.com' | ||
``` | ||
A developer might want to override some settings by defining a `development.yml` such as: | ||
```yaml | ||
development: | ||
auto_logout: true | ||
```` | ||
The main advantage is that those changes won't be tracked by source control :smiley: | ||
|
||
## Opinionated Design | ||
After using [settingslogic](https://github.com/settingslogic/settingslogic) for a long time, we learned some lessons, which are distilled in the following decisions: | ||
- __Immutability:__ Once created settings can't be modified. | ||
- __No Optional Setings:__ Any optional setting can be modeled in a safer way, this library doesn't allow them. | ||
- __Not Tied to a Source File:__ Useful to create multiple environment-specific files. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
require 'bundler/gem_tasks' | ||
require 'rspec/core/rake_task' | ||
|
||
RSpec::Core::RakeTask.new | ||
|
||
task default: :spec |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
require File.expand_path('../lib/better_settings/version', __FILE__) | ||
|
||
Gem::Specification.new do |s| | ||
s.name = 'better_settings' | ||
s.version = BetterSettings::VERSION | ||
s.authors = ['Máximo Mussini'] | ||
s.email = ['maximomussini@gmail.com'] | ||
s.summary = 'Settings for Rails applications: simple, immutable, better.' | ||
s.description = 'Settings solution for Rails applications that can read YAML files (ERB-enabled) and allows to access using method calls.' | ||
s.homepage = 'https://github.com/ElMassimo/better_settings' | ||
s.license = 'MIT' | ||
s.extra_rdoc_files = ['README.md'] | ||
s.files = Dir.glob('{lib}/**/*.rb') + %w(README.md) | ||
s.test_files = Dir.glob('{spec}/**/*.rb') | ||
s.require_path = 'lib' | ||
|
||
s.required_ruby_version = '~> 2.2' | ||
|
||
s.add_development_dependency 'coveralls' | ||
s.add_development_dependency 'pry-byebug' | ||
s.add_development_dependency 'rake' | ||
s.add_development_dependency 'rspec-given', '~> 3.0' | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'yaml' | ||
require 'erb' | ||
require 'open-uri' | ||
require 'forwardable' | ||
|
||
# Public: Rewrite of BetterSettings to enforce fail-fast and immutability, and | ||
# avoid extending a core class like Hash which can be problematic. | ||
class BetterSettings | ||
extend Forwardable | ||
|
||
VALID_SETTING_NAME = /^\w+$/ | ||
RESERVED_METHODS = %w[ | ||
settings | ||
root_settings | ||
] | ||
|
||
attr_reader :settings | ||
def_delegators :settings, :to_h, :to_hash | ||
|
||
# Public: Initializes a new settings object from a Hash or compatible object. | ||
def initialize(hash, parent:) | ||
@settings = hash.to_h.freeze | ||
@parent = parent | ||
|
||
# Create a getter method for each setting. | ||
@settings.each { |key, value| create_accessor(key, value) } | ||
end | ||
|
||
# Internal: Returns a new Better Settings instance that combines the settings. | ||
def merge(other_settings) | ||
self.class.new(deep_merge(@settings, other_settings.to_h), parent: @parent) | ||
end | ||
|
||
# Internal: Display explicit errors for typos and missing settings. | ||
# rubocop:disable Style/MethodMissing | ||
def method_missing(name, *) | ||
raise MissingSetting, "Missing setting '#{ name }' in #{ @parent }" | ||
end | ||
|
||
private | ||
|
||
# Internal: Wrap nested hashes as settings to allow accessing keys as methods. | ||
def auto_wrap(key, value) | ||
case value | ||
when Hash then self.class.new(value, parent: "'#{ key }' section in #{ @parent }") | ||
when Array then value.map { |item| auto_wrap(key, item) }.freeze | ||
else value.freeze | ||
end | ||
end | ||
|
||
# Internal: Defines a getter for the specified setting. | ||
def create_accessor(key, value) | ||
raise InvalidSettingKey if !key.is_a?(String) || key !~ VALID_SETTING_NAME || RESERVED_METHODS.include?(key) | ||
instance_variable_set("@#{ key }", auto_wrap(key, value)) | ||
singleton_class.send(:attr_reader, key) | ||
end | ||
|
||
# Internal: Recursively merges two hashes (in case ActiveSupport is not available). | ||
def deep_merge(this_hash, other_hash) | ||
this_hash.merge(other_hash) do |key, this_val, other_val| | ||
if this_val.is_a?(Hash) && other_val.is_a?(Hash) | ||
deep_merge(this_val, other_val) | ||
else | ||
other_val | ||
end | ||
end | ||
end | ||
|
||
class MissingSetting < StandardError; end | ||
class InvalidSettingKey < StandardError; end | ||
|
||
class << self | ||
extend Forwardable | ||
def_delegators :root_settings, :to_h, :to_hash, :method_missing | ||
|
||
# Public: Loads a file as settings (merges it with any previously loaded settings). | ||
def source(file_name, namespace: false, optional: false) | ||
return if !File.exist?(file_name) && optional | ||
|
||
# Load the specified yaml file and instantiate a Settings object. | ||
settings = new(yaml_to_hash(file_name), parent: file_name) | ||
|
||
# Take one of the settings keys if one is specified. | ||
settings = settings.public_send(namespace) if namespace | ||
|
||
# Merge settings if a source had previously been specified. | ||
@root_settings = @root_settings ? @root_settings.merge(settings) : settings | ||
|
||
# Allow to call any settings methods directly on the class. | ||
singleton_class.extend(Forwardable) | ||
singleton_class.def_delegators :root_settings, *@root_settings.settings.keys | ||
end | ||
|
||
private | ||
|
||
# Internal: Methods called at the class level are delegated to this instance. | ||
def root_settings | ||
raise ArgumentError, '`source` must be specified for the settings' unless defined?(@root_settings) | ||
@root_settings | ||
end | ||
|
||
# Internal: Parses a yml file that can optionally use ERB templating. | ||
def yaml_to_hash(file_name) | ||
return {} if (content = open(file_name).read).empty? | ||
YAML.load(ERB.new(content).result).to_hash | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# frozen_string_literal: true | ||
|
||
class BetterSettings | ||
VERSION = '1.0.0' | ||
end |
Oops, something went wrong.