reference_book 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+