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 +7 -0
- data/.gitignore +27 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +82 -0
- data/Rakefile +8 -0
- data/examples/config.rb +59 -0
- data/examples/interface_and_use.rb +129 -0
- data/examples/rails_controllers_and_views.rb +65 -0
- data/examples/rails_models.rb +103 -0
- data/lib/reference_book/book.rb +2 -0
- data/lib/reference_book/errors.rb +13 -0
- data/lib/reference_book/inflector.rb +34 -0
- data/lib/reference_book/library.rb +64 -0
- data/lib/reference_book/setup/collector.rb +28 -0
- data/lib/reference_book/setup/locked_book_spec.rb +51 -0
- data/lib/reference_book/setup/writer.rb +70 -0
- data/lib/reference_book/setup.rb +2 -0
- data/lib/reference_book/version.rb +3 -0
- data/lib/reference_book.rb +52 -0
- data/reference_book.gemspec +26 -0
- data/test/book_test.rb +118 -0
- data/test/inflector_test.rb +81 -0
- data/test/library_test.rb +166 -0
- data/test/reference_book_test.rb +197 -0
- data/test/setup/collector_test.rb +96 -0
- data/test/setup/locked_book_spec_test.rb +117 -0
- data/test/setup/writer_test.rb +202 -0
- data/test/test_helper.rb +4 -0
- metadata +121 -0
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
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
data/examples/config.rb
ADDED
@@ -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,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
|
+
|