interfaces 0.0.2.pre → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # Interfaces
2
2
 
3
- TODO: Write a gem description
4
-
5
3
  ## Installation
6
4
 
7
5
  Add this line to your application's Gemfile:
@@ -29,9 +27,9 @@ Let's role play a development scenario. Let's say you are writing an applicatio
29
27
  # some options specifically for sending mail
30
28
  attr_accessor :email_server, :use_ssl?, :port, :use_html?
31
29
 
32
- def email_sent_callback(mailer)
33
- # do something after mail is sent
34
- end
30
+ def email_sent_callback(mailer)
31
+ # do something after mail is sent
32
+ end
35
33
  end
36
34
 
37
35
  And we create a MailerService to send mail:
@@ -52,39 +50,26 @@ And we create a MailerService to send mail:
52
50
  end
53
51
  end
54
52
 
55
- This works, but we've needlessly coupled our MailerService to our User model. The Mailer service does not really need a User, what it needs is configuration. To be slightly more explicit then we could simply rename the first parameter to 'configuration':
53
+ We can call the Mailer service and pass it a user:
56
54
 
57
- class MailerService
58
- attr_accessor :configuration, :to, :subject, :message
55
+ MailerService.new(user, 'bob@example.com', 'test message', 'hello world!').deliver
59
56
 
60
- def initialize(configuration, to, subject, message)
61
- self.configuration = configuration
62
- self.to = to
63
- self.subject = subject
64
- self.message = message
65
- end
57
+ Looking at the line of code above, one might wonder why a user is being passed into the mailer service. One might also wonder what properties of 'user' the mailer service is actually using. Is the mailer only reading properties of my user or is it *changing* the user? What if another developer comes along, noticing that the mailer is receiving a User model, and inadvertantly tightly couples MailerService to the User model? That may not cause immediate problems, but down the road services and models can become more and more tightly coupled. When you find yourself needing to use your service somewhere else you might find the de-coupling refactor to be a daunting task...
66
58
 
67
- def deliver
68
- # ... implement mail sending here
69
- end
70
- end
71
-
72
- This also works fine. We can call the Mailer service and pass it a user:
73
-
74
- MailerService.new(user, 'bob@example.com', 'test message', 'hello world!').deliver
75
-
76
- The problem isn't that it doesn't work, it does. The problem is that the readability is low. Looking at the line of code above, one might wonder why a user is being passed into the mailer service. One might also wonder what properties of 'user' the mailer service is actually using. Is the mailer only reading properties of my user or is it *changing* the user? What if another developer comes along, noticing that the mailer is receiving a User model, and inadvertantly tightly couples MailerService to the User model? That may not cause immediate problems, but down the road services and models can become more and more tightly coupled. When you find yourself needing to use your service somewhere else you might find the de-coupling refactor to be a daunting task...
77
-
78
- So let's re-write this code using Interfaces. First, let's define an interface:
59
+ The Mailer service does not really need a User, what it needs is configuration. So let's re-write this code using Interfaces. First, let's define an interface:
79
60
 
80
61
  class MailerConfiguration < Interface
81
62
  abstract :email_server, :use_ssl?, :port, :use_html?, :email_sent_callback
82
63
  end
83
64
 
84
- Then, when we call our mailer service we cast the 'user' object to be a MailerConfiguration object:
65
+ Then, when we call our mailer service it will cast whatever object is passed in to be a MailerConfiguration object:
85
66
 
86
- MailerService.new(user.as(MailerConfiguration), 'bob@example.com', 'test message', 'hello world!').deliver
67
+ class MailerService
68
+ attr_accessor :config, :to, :subject, :message
87
69
 
70
+ def initialize(config, to, subject, message)
71
+ self.config = config.as(MailerConfiguration)
72
+ ...
88
73
 
89
74
  Now it's clear that the user is being passed in because it contains configuration information. Further, there is enforcement taking place-- if User did not implement one of the four required methods, a clear exception would be fired at runtime. And if MailerService tries to call another method of User that is not defined in the MailerConfiguration interface, an exception will be thrown. Lastly, there's one place to look to determine what methods are needed by MailerConfiguration-- the code is self documenting.
90
75
 
@@ -132,6 +117,11 @@ An interface may contain optional methods. If they are defined by a class then t
132
117
  TestInterface.new.field2
133
118
  => nil
134
119
 
120
+ ## Checking conformance
121
+
122
+ User.conforms_to?(TestInterface)
123
+ => true
124
+
135
125
  ## Typed accessors
136
126
 
137
127
  The typed_attr_accessor and typed_attr_writer helpers make it easy to create attributes that always conform to an interface:
@@ -20,6 +20,14 @@ module Interfaces
20
20
  end
21
21
  end
22
22
 
23
+ # returns true if an object conforms to the given interface (implements the abstract methods)
24
+ def conforms_to?(interface)
25
+ # interface must be an Interface
26
+ raise InterfaceError, "#{interface} is not an Interface" unless interface < Interface
27
+ interface.abstract_methods.all? { |method| self.respond_to?(method) }
28
+ end
29
+
30
+ # casts an object to an instance of a particular Interface
23
31
  def as(interface)
24
32
  # interface must be a class
25
33
  raise InterfaceError, "#{interface} is not a class" unless interface.is_a?(Class)
@@ -1,3 +1,3 @@
1
1
  module Interfaces
2
- VERSION = "0.0.2.pre"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -31,6 +31,10 @@ describe Interfaces::Castable do
31
31
  let(:instance) { ClassConformingToTestInterface.new }
32
32
  let(:casted_instance) { instance.as(TestInterface) }
33
33
 
34
+ it 'instance should conform_to the TestInteface' do
35
+ instance.conforms_to?(TestInterface).should be_true
36
+ end
37
+
34
38
  it 'casted_instance should be an instance of TestInterface' do
35
39
  casted_instance.should be_a(TestInterface)
36
40
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: interfaces
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2.pre
5
- prerelease: 6
4
+ version: 0.2.0
5
+ prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Justin Schumacher
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-11-05 00:00:00.000000000 Z
12
+ date: 2014-05-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -101,9 +101,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
101
101
  required_rubygems_version: !ruby/object:Gem::Requirement
102
102
  none: false
103
103
  requirements:
104
- - - ! '>'
104
+ - - ! '>='
105
105
  - !ruby/object:Gem::Version
106
- version: 1.3.1
106
+ version: '0'
107
107
  requirements: []
108
108
  rubyforge_project:
109
109
  rubygems_version: 1.8.23
@@ -116,4 +116,3 @@ test_files:
116
116
  - spec/interface_spec.rb
117
117
  - spec/spec_helper.rb
118
118
  - spec/typed_accessor_spec.rb
119
- has_rdoc: