interfacer 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/.yardopts +7 -0
- data/README.md +104 -0
- data/lib/interfacer.rb +59 -0
- metadata +61 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8a833ad6c374e5255b3390d46b0f8bac7f14d040f88a255182efc159170d1b6f
|
4
|
+
data.tar.gz: 3d40f9c176ce3ef82c164780d09c6efdf77212ca8cb1d461e06166011af43fc2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9c20b5f30a63ced573767ec4a4cb9d7b2b772aace4757a610564ea041465ffb72b4658fb50f9d53fa970cd95f88a97f78a3a3139f76e391ba8b931035d912240
|
7
|
+
data.tar.gz: b2564c14983ca1732bc40f25487243b747470b4781106e3dd654e4f7b8a3fc3b0784f756fdffb6980b9746bc47180e9b52510002bb492266007a3e7b00705c54
|
data/.yardopts
ADDED
data/README.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# About
|
2
|
+
|
3
|
+
[![Gem version][GV img]][Gem version]
|
4
|
+
[![Build status][BS img]][Build status]
|
5
|
+
[![Coverage status][CS img]][Coverage status]
|
6
|
+
[![CodeClimate status][CC img]][CodeClimate status]
|
7
|
+
[![YARD documentation][YD img]][YARD documentation]
|
8
|
+
|
9
|
+
How much abstraction is too much?
|
10
|
+
|
11
|
+
On one hand <abbr title="Inversion of control">IoC</abbr> is probably a too heavy canon for a dynamic language like Ruby, instantiating external dependencies in `#initalize` doesn't sound like a good idea either.
|
12
|
+
|
13
|
+
If I have to replace a dependency, I want to know what interface it has to provide.
|
14
|
+
|
15
|
+
That is what methods is my project actually using, so not the same as `class MyCollection implements ListInterface`.
|
16
|
+
|
17
|
+
# Example
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
require 'import'
|
21
|
+
|
22
|
+
Interfacer = import('interfacer').Interfacer
|
23
|
+
|
24
|
+
class Post
|
25
|
+
extend Interfacer
|
26
|
+
|
27
|
+
attribute(:time_class, '.now', '#to_s') { Time }
|
28
|
+
|
29
|
+
def publish!
|
30
|
+
"~ Post #{self.inspect} has been published at #{self.time_class.now} (using #{self.time_class})."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
puts "~ With the default time_class."
|
35
|
+
post = Post.new
|
36
|
+
puts post.publish!
|
37
|
+
|
38
|
+
puts "\n~ With overriden time_class."
|
39
|
+
require 'date'
|
40
|
+
|
41
|
+
post = Post.new
|
42
|
+
post.time_class = DateTime
|
43
|
+
puts post.publish!
|
44
|
+
```
|
45
|
+
|
46
|
+
To me, this is what I consider to be the golden middle way. There's nearly no extra code, no factory method etc, but I can replace the time class any time I want.
|
47
|
+
|
48
|
+
Why?
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
class TimeMock
|
52
|
+
class << self
|
53
|
+
alias_method :now, :new
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
'Monday evening'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe Post do
|
62
|
+
before(:each) do
|
63
|
+
subject.time_class = TimeMock
|
64
|
+
end
|
65
|
+
|
66
|
+
it "prints out when a post was published" do
|
67
|
+
expect(post.publish!).to match(/has been published at Monday evening/)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
You can say that you could just stub `Time.now` and you're right, but I'm not a huge fan of that approach. I like clear dependencies, actual objects and (on a slightly different subject, but still vaguely related) I think tests should test public APIs and not order in which things are executed (when used mocks), because everything breaks when you do internal refactoring.
|
73
|
+
|
74
|
+
But whatever, let's have an another example.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
settings = import('settings')
|
78
|
+
|
79
|
+
class Post
|
80
|
+
attribute :json_encoder, '.generate' { settings.json_encoder }
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
Now when you decide to switch to say `oj`, all you have to do is this:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
# settings.rb
|
88
|
+
|
89
|
+
require 'oj'
|
90
|
+
|
91
|
+
export json_encoder: Oj
|
92
|
+
```
|
93
|
+
|
94
|
+
[Gem version]: https://rubygems.org/gems/interfacer
|
95
|
+
[Build status]: https://travis-ci.org/botanicus/interfacer
|
96
|
+
[Coverage status]: https://coveralls.io/github/botanicus/interfacer
|
97
|
+
[CodeClimate status]: https://codeclimate.com/github/botanicus/interfacer/maintainability
|
98
|
+
[YARD documentation]: http://www.rubydoc.info/github/botanicus/interfacer/master
|
99
|
+
|
100
|
+
[GV img]: https://badge.fury.io/rb/interfacer.svg
|
101
|
+
[BS img]: https://travis-ci.org/botanicus/interfacer.svg?branch=master
|
102
|
+
[CS img]: https://img.shields.io/coveralls/botanicus/interfacer.svg
|
103
|
+
[CC img]: https://api.codeclimate.com/v1/badges/a99a88d28ad37a79dbf6/maintainability
|
104
|
+
[YD img]: http://img.shields.io/badge/yard-docs-blue.svg
|
data/lib/interfacer.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# @api private
|
2
|
+
class InterfaceSpec
|
3
|
+
def initialize(required_interface_methods)
|
4
|
+
@required_interface_methods = required_interface_methods
|
5
|
+
end
|
6
|
+
|
7
|
+
def missing_methods(tested_class)
|
8
|
+
@required_interface_methods.reject do |method_name|
|
9
|
+
if method_name[0] == '.'
|
10
|
+
tested_class.respond_to?(method_name[1..-1])
|
11
|
+
elsif method_name[0] == '#'
|
12
|
+
tested_class.instance_methods.include?(method_name[1..-1].to_sym)
|
13
|
+
else
|
14
|
+
raise ArgumentError.new("Incorrect method name. Method name must start with either . or #, such as .new or #to_s.")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def fullfills_interface?(tested_class)
|
20
|
+
self.missing_methods(tested_class).empty?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @api public
|
25
|
+
class InterfaceRequirementsNotMetError < StandardError
|
26
|
+
def initialize(attr_name, missing_methods)
|
27
|
+
super("Attribute #{attr_name} expects #{missing_methods.inspect} to be defined.")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @api public
|
32
|
+
module Interfacer
|
33
|
+
def interface_specs_for_attrs
|
34
|
+
@interface_specs_for_attrs ||= Hash.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def attribute(attr_name, *required_methods, &block)
|
38
|
+
interface_specs_for_attrs[attr_name] = InterfaceSpec.new(required_methods)
|
39
|
+
|
40
|
+
define_method(attr_name) do
|
41
|
+
value = instance_variable_get(:"@#{attr_name}")
|
42
|
+
return value if value
|
43
|
+
instance_variable_set(:"@#{attr_name}", block.call)
|
44
|
+
end
|
45
|
+
|
46
|
+
define_method(:"#{attr_name}=") do |value|
|
47
|
+
spec = self.class.interface_specs_for_attrs[attr_name]
|
48
|
+
|
49
|
+
unless spec.fullfills_interface?(value)
|
50
|
+
raise InterfaceRequirementsNotMetError.new(attr_name, spec.missing_methods(value))
|
51
|
+
end
|
52
|
+
|
53
|
+
instance_variable_set(:"@#{attr_name}", value)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
export Interfacer: Interfacer,
|
59
|
+
InterfaceRequirementsNotMetError: InterfaceRequirementsNotMetError
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: interfacer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James C Russell
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-06-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: commonjs_modules
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.0'
|
27
|
+
description: "."
|
28
|
+
email: james@101ideas.cz
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- ".yardopts"
|
34
|
+
- README.md
|
35
|
+
- lib/interfacer.rb
|
36
|
+
homepage: http://github.com/botanicus/interfacer
|
37
|
+
licenses:
|
38
|
+
- MIT
|
39
|
+
metadata:
|
40
|
+
yard.run: yri
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
requirements: []
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 2.7.6
|
58
|
+
signing_key:
|
59
|
+
specification_version: 4
|
60
|
+
summary: ''
|
61
|
+
test_files: []
|