Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1b01d11
commit b4731d6
Showing
7 changed files
with
181 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
This is a thought experiment for how Raptor apps might be designed. The module | ||
hierarchy is: | ||
|
||
Routes | ||
Injectables | ||
.current_user | ||
.post_params | ||
Interactors | ||
CreatePost | ||
PostSaved | ||
ValidationFailure | ||
Models | ||
User | ||
AnonymousUser | ||
Post | ||
Records | ||
User | ||
Post | ||
|
||
Responsibility breakdown: | ||
- Routes are the same Raptor routes as ever. Then can route out to different | ||
actions/redirects based on the interactor's response. | ||
- Injectables provide objects for Raptor's implicit DI. For example, the | ||
`post_params` injectable does what it sounds like, so that | ||
Interactors::CreatePost can take a `post_params` argument without worrying | ||
about where it came from. These are class methods directly on the | ||
Injectables module. | ||
- Interactors contain the application's business logic. They return (or | ||
raise) response models that are defined directly in the interactor's | ||
class (or maybe from a shared location if appropriate). | ||
- Models wrap database records to provide simple mutations and data | ||
wrappers. For example, Post#publish updates the published flag and saves. | ||
These don't necessarily map to records one-to-one. For example, the | ||
AnonymousUser model exists to avoid nil current_user, and doesn't map to | ||
anything in the database. | ||
- Records are straight-up database records. No methods; just field | ||
definitions. | ||
|
||
This hierarchy doesn't correspond to the file layout. The files are grouped by | ||
topic. For example, posts.rb contains both Interactors::CreatePost and the | ||
post_params injectable. The modules in the system will be reopened many times | ||
as code is loaded. This gives us lasagna files instead of ravioli files. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
module Models | ||
class User | ||
extend Raptor::Model | ||
delegate [:email, :posts] => :@record | ||
|
||
def anonymous?; false; end | ||
end | ||
|
||
class AnonymousUser | ||
def anonymous?; true; end | ||
end | ||
|
||
class Post | ||
extend Raptor::Model | ||
delegate [:title, :body] => :@record | ||
|
||
def publish | ||
@record.update_attributes(:published => true) | ||
@record.save! | ||
end | ||
|
||
def save_as_draft | ||
@record.update_attributes(:published => false) | ||
@record.save! | ||
end | ||
end | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
require "raptor/shorty" | ||
|
||
module Injectables | ||
def self.post_params(params) | ||
params.fetch(:post) | ||
end | ||
end | ||
|
||
module Interactors | ||
class CreatePost | ||
class PostSaved < Struct.new(:current_user, :post); end | ||
class ValidationFailure < RuntimeError | ||
takes :current_user, :post | ||
end | ||
|
||
def self.create(current_user, post_params) | ||
post = Models::Post.new(post_params) | ||
raise ValidationFailure.new(current_user, post) unless post.valid? | ||
|
||
if current_user.admin? | ||
post.publish | ||
else | ||
post.save_as_draft | ||
end | ||
|
||
PostSaved.new(current_user, post) | ||
end | ||
end | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
require_relative "posts" | ||
|
||
module Models; class Post; end; end | ||
|
||
describe Interactors::CreatePost do | ||
Post = Models::Post | ||
CreatePost = Interactors::CreatePost | ||
PostSaved = Interactors::CreatePost::PostSaved | ||
ValidationFailure = Interactors::CreatePost::ValidationFailure | ||
|
||
let(:post_params) { stub(:post_params) } | ||
|
||
context "when the post is valid" do | ||
let(:post) { stub(:post, :valid? => true) } | ||
before { Post.stub(:new).with(post_params) { post } } | ||
|
||
it "publishes the post if the user is an admin" do | ||
user = stub(:user, :admin? => true) | ||
post.should_receive(:publish) | ||
CreatePost.create(user, post_params) | ||
end | ||
|
||
it "saves the post as a draft if the user isn't an admin" do | ||
user = stub(:user, :admin? => false) | ||
post.should_receive(:save_as_draft) | ||
CreatePost.create(user, post_params) | ||
end | ||
|
||
it "returns a post saved response" do | ||
user = stub(:user, :admin? => false) | ||
post.stub(:save_as_draft) | ||
response = CreatePost.create(user, post_params) | ||
response.should be_a PostSaved | ||
end | ||
end | ||
|
||
it "raises a validation failure when the post is invalid" do | ||
user = stub(:user) | ||
post = stub(:post, :valid? => false) | ||
Post.stub(:new).with(post_params) { post } | ||
expect do | ||
CreatePost.create(user, post_params) | ||
end.to raise_error(ValidationFailure) | ||
end | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module Records | ||
class User < SomeORM::Record | ||
value :email | ||
list :posts => "Post" | ||
end | ||
|
||
class Post < SomeORM::Record | ||
value :title | ||
value :body | ||
reference :author => "User" | ||
end | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
Routes = Raptor.routes do | ||
path "posts" do | ||
# We could also scope all route targets inside Interactors, allowing this | ||
# to say :to => "CreatePost.create". | ||
create :to => "Interactors::CreatePost.create", | ||
:ValidationFailure => render(:new) | ||
end | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
module Injectables | ||
def current_user(session) | ||
begin | ||
id = session.fetch(:user_id) | ||
rescue KeyError | ||
AnonymousUser.new | ||
else | ||
Models::User.find(id) | ||
end | ||
end | ||
end | ||
|