reference_book 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 380f06491602cd309c6565ce8ada2c5c63b688c7
4
+ data.tar.gz: 7a02e9c9e6b22611b12d4a0493c75a24204dcca0
5
+ SHA512:
6
+ metadata.gz: 3cf69339ecfb6974f4eec6f1ce10da3385e956ebe1b205e88909ce89f4c642ed929f605877091073280ea04deba2e926fd5d1ef0aef171be397901d99c2f1276
7
+ data.tar.gz: 3709e458d2063e5baeebbacc41c73e55e4973781da300dafdf8e70a02f41c74fea017fc304c298ac406dc9fc88cbbdca7058e311b748654b9962e1536925cc59
data/.gitignore ADDED
@@ -0,0 +1,27 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+
24
+ # OSX
25
+ .Spotlight-V100
26
+ .DS_Store
27
+ .Trashes
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in reference_book.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Tommaso Pavese
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # ReferenceBook
2
+
3
+ A multi-context configuration library and DSL.
4
+ **ReferenceBook** provides an easy interface to define, validate and query **multi-context** configuration data.
5
+
6
+ What does **multi-context** mean?
7
+ Any setting that should be static and that exist in different alternative versions.
8
+
9
+
10
+
11
+
12
+ ## Usage
13
+
14
+ TODO: Write usage instructions here
15
+
16
+ ## Examples
17
+
18
+ Lots are available [in the repo](https://github.com/tompave/reference_book/tree/master/examples).
19
+
20
+
21
+
22
+
23
+ ## Features/Problems
24
+
25
+ * requires Ruby >= 2.0.0
26
+ * simple configuration DSL, with validations
27
+ * configuration data exists as frozen objects, not namespaced constants
28
+ * library and books metaphor
29
+ * easy and flexible query interface
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+ ## Rubies
38
+
39
+ **ReferenceBook** requires a Ruby patch level `>= 2.0`.
40
+ It doesn't use any _2-only_ syntax features, but it does rely on some core classes' methods that only appeared with MRI 2.0.0.
41
+
42
+ **MRI**: supported, tested with 2.0 and 2.1.
43
+ **JRuby**: tested in 2.0 mode (`JRUBY_OPTS=--2.0 rake test`).
44
+ **Rubinius**: not supported, even in 2.1 mode its standard library classes seem to expose a 1.9 interface.
45
+
46
+
47
+ ## Tests
48
+
49
+ `rake test`
50
+
51
+
52
+
53
+
54
+ ## To Do
55
+
56
+ * add support for different libraries, each with an enforced book structure
57
+ * make it work with Ruby 1.9.3 (involves implementing methods that at the moment are provided by core classes).
58
+
59
+
60
+ ## Installation
61
+
62
+
63
+ Add this line to your application's Gemfile:
64
+
65
+ gem 'reference_book'
66
+
67
+ And then execute:
68
+
69
+ $ bundle
70
+
71
+ Or install it yourself as:
72
+
73
+ $ gem install reference_book
74
+
75
+
76
+ ## Contributing
77
+
78
+ 1. Fork it ( https://github.com/[my-github-username]/reference_book/fork )
79
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
80
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
81
+ 4. Push to the branch (`git push origin my-new-feature`)
82
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'test'
7
+ t.pattern = 'test/**/*_test.rb'
8
+ end
@@ -0,0 +1,59 @@
1
+ # To optionally define and enforce a static book structure.
2
+ # Without this, books are free-form.
3
+ #
4
+ ReferenceBook.define_book_structure :price, :currency, :country_name, :date_format
5
+
6
+
7
+ # Given the structure defined above:
8
+
9
+
10
+ # This is valid
11
+ #
12
+ ReferenceBook.write_book(title: "UnitedStates", library_key: :us) do |book|
13
+ book.price = 12
14
+ book.currency = '$'
15
+ book.country_name = 'US'
16
+ book.date_format = '%-m/%-d/%Y'
17
+ end
18
+
19
+
20
+ # This is valid too. The library_key will be ':united_kingdom'
21
+ #
22
+ ReferenceBook.write_book(title: "UnitedKingdom") do |book|
23
+ book.price = 10
24
+ book.currency = '£'
25
+ book.country_name = 'UK'
26
+ book.date_format = '%-d/%-m/%Y'
27
+ end
28
+
29
+
30
+ # This will raise an error because of the missing keys
31
+ #
32
+ ReferenceBook.write_book(title: "Canada") do |book|
33
+ book.price = 12
34
+ end
35
+
36
+
37
+ # This will raise an error because of the unexpected key
38
+ #
39
+ ReferenceBook.write_book(title: "Italy") do |book|
40
+ book.price = 10
41
+ book.currency = '€'
42
+ book.country_name = 'Italy'
43
+ book.date_format = '%-d/%-m/%Y'
44
+ book.international_prefix = '+39'
45
+ end
46
+
47
+
48
+
49
+ # This will raise an error because 'title' and 'library_key' have
50
+ # a special meaning, and must be defined as method parameters
51
+
52
+ ReferenceBook.write_book(title: "Spain") do |book|
53
+ book.price = 10
54
+ book.currency = ''
55
+ book.country_name = 'Spain'
56
+ book.date_format = '%-d/%-m/%Y'
57
+ book.title = 'Spain'
58
+ book.library_key = :spain
59
+ end
@@ -0,0 +1,129 @@
1
+ # Given this configuration:
2
+
3
+ ReferenceBook.write_book(title: "Elf") do |book|
4
+ book.strength = 0
5
+ book.dexterity = +2
6
+ book.constitution = -2
7
+ book.intelligence = 0
8
+ book.wisdom = 0
9
+ book.charisma = 0
10
+ end
11
+
12
+ ReferenceBook.write_book(title: "Dwarf") do |book|
13
+ book.strength = 0
14
+ book.dexterity = 0
15
+ book.constitution = +2
16
+ book.intelligence = 0
17
+ book.wisdom = 0
18
+ book.charisma = -2
19
+ end
20
+
21
+ ReferenceBook.write_book(title: "Halfling") do |book|
22
+ book.strength = -2
23
+ book.dexterity = +2
24
+ book.constitution = 0
25
+ book.intelligence = 0
26
+ book.wisdom = 0
27
+ book.charisma = 0
28
+ end
29
+
30
+
31
+
32
+
33
+ # The books can be retrieved with:
34
+
35
+ ReferenceBook.library.index
36
+ # => [:elf, :dwarf, :halfling]
37
+
38
+ ReferenceBook.library.elf
39
+ ReferenceBook.library[:elf]
40
+
41
+ ReferenceBook::Library.elf
42
+ ReferenceBook::Library[:elf]
43
+
44
+ # => #<struct ReferenceBook::Book::Elf
45
+ # strength = 0,
46
+ # dexterity = 2,
47
+ # constitution = -2,
48
+ # intelligence = 0,
49
+ # wisdom = 0,
50
+ # charisma = 0,
51
+ # title = "Elf",
52
+ # library_key = :elf>
53
+
54
+
55
+ # And each book can be queried with:
56
+
57
+ ReferenceBook.library.elf.dexterity
58
+ ReferenceBook.library.elf[:dexterity]
59
+ # => 2
60
+
61
+ ReferenceBook.library.dwarf.dexterity
62
+ ReferenceBook.library.dwarf[:dexterity]
63
+ # => 0
64
+
65
+ ReferenceBook.library[:halfling].widsom
66
+ ReferenceBook.library[:halfling][:wisdom]
67
+ # => 0
68
+
69
+
70
+
71
+
72
+ # You can wrap it with your own logic:
73
+
74
+ def modifier_for(race, stat)
75
+ race = race.downcase.to_sym
76
+ stat = stat.downcase.to_sym
77
+ ReferenceBook.library[race][stat]
78
+ end
79
+
80
+ modifier_for('Dwarf', 'Charisma')
81
+ # => -2
82
+
83
+
84
+
85
+ # Books are read-only:
86
+
87
+ ReferenceBook.library.dwarf.strength = 10
88
+ # RuntimeError: can't modify frozen ReferenceBook::Book::Dwarf
89
+
90
+
91
+
92
+
93
+
94
+
95
+ # Also, books are a thin subclass of the base Ruby Struct.
96
+ # They are frozen, but expose the same interface:
97
+
98
+ book = ReferenceBook.library.elf
99
+
100
+ book.to_h
101
+ # => {:strength => 0,
102
+ # :dexterity => 2,
103
+ # :constitution => -2,
104
+ # :intelligence => 0,
105
+ # :wisdom => 0,
106
+ # :charisma => 0,
107
+ # :title => "Elf",
108
+ # :library_key => :elf}
109
+
110
+ book.to_a
111
+ # => [0, 2, -2, 0, 0, 0, "Elf", :elf]
112
+
113
+ book.members
114
+ # => [:strength, :dexterity, :constitution, :intelligence, :wisdom, :charisma, :title, :library_key]
115
+
116
+ book.select { |value| value != 0 }
117
+ # => [2, -2, "Elf", :elf]
118
+
119
+
120
+ book.each_pair { |k, v| puts "#{k}: #{v}" }
121
+ # strength: 0
122
+ # dexterity: 2
123
+ # constitution: -2
124
+ # intelligence: 0
125
+ # wisdom: 0
126
+ # charisma: 0
127
+ # title: Elf
128
+ # library_key: elf
129
+
@@ -0,0 +1,65 @@
1
+ # Given this configuration:
2
+
3
+ ReferenceBook.write_book(title: 'Italy', library_key: :it) do |book|
4
+ book.currency = '€'
5
+ book.time_zone = 'Europe/Rome'
6
+ end
7
+
8
+ ReferenceBook.write_book(title: 'UnitedKindom', library_key: :uk) do |book|
9
+ book.currency = '£'
10
+ book.time_zone = 'Europe/London'
11
+ end
12
+
13
+
14
+
15
+
16
+ # All controllers can have access to the most appropriate book for the current locale:
17
+
18
+
19
+ class ApplicationController < ActionController::Base
20
+ helper_method :ref_book
21
+
22
+ private
23
+
24
+ def ref_book
25
+ @ref_book ||= ReferenceBook.library[country_key_for_locale]
26
+ end
27
+
28
+
29
+ def country_key_for_locale
30
+ case I18n.locale
31
+ #...
32
+ end
33
+ end
34
+ end
35
+
36
+
37
+
38
+
39
+ # Or, to have more fine grained control:
40
+
41
+ module CustomReferenceBookMethods
42
+ extend ActiveSupport::Concern
43
+
44
+ included do |base|
45
+ raise 'ops' unless base.instance_methods.include?(:country_key_for_locale)
46
+ helper_method :ref_book
47
+ end
48
+
49
+ private
50
+
51
+ def ref_book
52
+ @ref_book ||= ReferenceBook.library[country_key_for_locale]
53
+ end
54
+ end
55
+
56
+
57
+
58
+
59
+ class ApplicationController < ActionController::Base
60
+ include CustomReferenceBookMethods
61
+
62
+ def country_key_for_locale
63
+ # ...
64
+ end
65
+ end
@@ -0,0 +1,103 @@
1
+ # Given this configuration:
2
+
3
+ ReferenceBook.write_book(title: 'FreeAccount', library_key: :free) do |book|
4
+ book.max_blog_posts = 5
5
+ book.max_comments = 10
6
+ book.max_votes = 0
7
+ book.with_ads = true
8
+ book.auth_policy = Proc.new { |user, resource| resource.free? && !resource.already_viewed_by?(user) }
9
+ end
10
+
11
+
12
+ ReferenceBook.write_book(title: 'SilverAccount', library_key: :silver) do |book|
13
+ book.max_blog_posts = 50
14
+ book.max_comments = 10_000
15
+ book.max_votes = 1_000
16
+ book.with_ads = false
17
+ book.auth_policy = Proc.new { true }
18
+ end
19
+
20
+
21
+ ReferenceBook.write_book(title: 'GoldAccount', library_key: :gold) do |book|
22
+ book.max_blog_posts = 200
23
+ book.max_comments = 50_000
24
+ book.max_votes = 10_000
25
+ book.with_ads = false
26
+ book.auth_policy = Proc.new { true }
27
+ end
28
+
29
+
30
+
31
+
32
+ # An ActiveRecord model can intercat with it directly:
33
+
34
+ class User < ActiveRecord::Base
35
+
36
+ validates :accounty_type, presence: true,
37
+ inclusion: { in: %w(free silver gold) }
38
+
39
+
40
+ def ref_book
41
+ @ref_book ||= ReferenceBook.library[account_type.to_sym]
42
+ end
43
+
44
+
45
+ def max_blog_posts
46
+ ref_book.max_blog_posts
47
+ end
48
+
49
+
50
+ def can_access?(resource)
51
+ ref_book.auth_policy.call(self, resource)
52
+ end
53
+
54
+
55
+ def ads_list
56
+ if ref_book.with_ads
57
+ Ads::List.new(self)
58
+ else
59
+ Ads::None.new
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+
66
+
67
+ # Or it can be implemented in a module
68
+
69
+
70
+ module CustomReferenceBookMethods
71
+ def ref_book
72
+ @ref_book ||= ReferenceBook.library[account_type.to_sym]
73
+ end
74
+
75
+
76
+ def max_blog_posts
77
+ ref_book.max_blog_posts
78
+ end
79
+
80
+
81
+ def can_access?(resource)
82
+ ref_book.auth_policy.call(self, resource)
83
+ end
84
+
85
+
86
+ def ads_list
87
+ if ref_book.with_ads
88
+ Ads::List.new(self)
89
+ else
90
+ Ads::None.new
91
+ end
92
+ end
93
+ end
94
+
95
+
96
+
97
+
98
+ class User < ActiveRecord::Base
99
+ include CustomReferenceBookMethods
100
+
101
+ validates :accounty_type, presence: true,
102
+ inclusion: { in: %w(free silver gold) }
103
+ end
@@ -0,0 +1,2 @@
1
+ class ReferenceBook::Book < Struct
2
+ end
@@ -0,0 +1,13 @@
1
+ class ReferenceBook::Error < StandardError
2
+ end
3
+
4
+
5
+ class ReferenceBook::BookDefinitionError < ReferenceBook::Error
6
+ end
7
+
8
+ class ReferenceBook::LockedBookSpecError < ReferenceBook::Error
9
+ end
10
+
11
+
12
+ # class ReferenceBook::BookLookupError < ReferenceBook::Error
13
+ # end
@@ -0,0 +1,34 @@
1
+ module ReferenceBook::Inflector
2
+ class << self
3
+
4
+ def constantize(raw)
5
+ if raw && raw.length > 0
6
+ title = raw.to_s.gsub(/[^a-zA-Z]/, '')
7
+ title[0] = title[0].upcase
8
+ title
9
+ end
10
+ end
11
+
12
+
13
+
14
+
15
+ def make_key(raw)
16
+ return nil unless raw
17
+ if raw.is_a? Symbol
18
+ raw
19
+ else
20
+ camel_to_snake(raw.to_s).gsub(/[^a-zA-Z0-9]/, '_').downcase.to_sym
21
+ end
22
+ end
23
+
24
+
25
+
26
+ private
27
+
28
+ CAMEL_REGEX = /([a-z])([A-Z])/
29
+ def camel_to_snake(raw)
30
+ raw.gsub(CAMEL_REGEX, '\1_\2').downcase
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,64 @@
1
+ module ReferenceBook::Library
2
+ class << self
3
+
4
+ def store(book)
5
+ verify_library_key!(book.library_key)
6
+
7
+ shelf[book.library_key] = book
8
+ index << book.library_key
9
+ define_accessor_for(book)
10
+ book
11
+ end
12
+
13
+
14
+ def shelf
15
+ @shelf ||= {}
16
+ end
17
+
18
+
19
+ def index
20
+ @index ||= []
21
+ end
22
+
23
+
24
+ def [](key)
25
+ shelf[key]
26
+ end
27
+
28
+
29
+ def empty!
30
+ index.each { |key| remove_accessor_for(key) }
31
+ @index = []
32
+ @shelf = {}
33
+ nil
34
+ end
35
+
36
+
37
+ def verify_library_key!(key)
38
+ if is_key_in_use?(key)
39
+ raise ReferenceBook::BookDefinitionError, "the library key '#{key}' is already in use."
40
+ end
41
+ end
42
+
43
+
44
+
45
+ private
46
+
47
+ def define_accessor_for(book)
48
+ self.define_singleton_method book.library_key do
49
+ self.shelf[book.library_key]
50
+ end
51
+ end
52
+
53
+
54
+ def remove_accessor_for(key)
55
+ self.singleton_class.send :remove_method, key
56
+ end
57
+
58
+
59
+ def is_key_in_use?(key)
60
+ index.include?(key)
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,28 @@
1
+ require 'ostruct'
2
+
3
+ class ReferenceBook::Setup::Collector < OpenStruct
4
+ def title=(*args)
5
+ protected_attribute_warning('title')
6
+ end
7
+
8
+ def library_key=(*args)
9
+ protected_attribute_warning('library_key')
10
+ end
11
+
12
+
13
+ def []=(key, value)
14
+ if key == :title || key == :library_key
15
+ protected_attribute_warning(key)
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+
22
+
23
+ private
24
+
25
+ def protected_attribute_warning(key)
26
+ raise ReferenceBook::BookDefinitionError, "You can't set a Book's #{key} in its definition block"
27
+ end
28
+ end
@@ -0,0 +1,51 @@
1
+ class ReferenceBook::Setup::LockedBookSpec
2
+
3
+
4
+ def initialize(keys_array)
5
+ if keys_array.any?
6
+ @book_keys = symbolize_and_sort(keys_array)
7
+ else
8
+ raise ReferenceBook::LockedBookSpecError, "A LockedBookSpec must have at least one key"
9
+ end
10
+ end
11
+
12
+
13
+
14
+ def verify_keys!(other_keys)
15
+ if @book_keys == other_keys.sort
16
+ true
17
+ else
18
+ missing = @book_keys - other_keys
19
+ unexpected = other_keys - @book_keys
20
+
21
+ message = error_message_with(missing, unexpected)
22
+ raise ReferenceBook::BookDefinitionError, message
23
+ end
24
+ end
25
+
26
+
27
+
28
+
29
+
30
+ private
31
+
32
+ def symbolize_and_sort(keys)
33
+ keys.map(&:to_sym).sort
34
+ end
35
+
36
+
37
+
38
+ def error_message_with(missing, unexpected)
39
+ msg = "Couldn't create Book"
40
+ if missing.any?
41
+ msg << ", missing keys: [#{missing.join(', ')}]"
42
+ end
43
+
44
+ if unexpected.any?
45
+ msg << ", unexpected keys: [#{unexpected.join(', ')}]"
46
+ end
47
+
48
+ msg
49
+ end
50
+ end
51
+