Slide 1

Slide 1 text

For smaller, simpler, and more readily understandable Rails web apps Exploiting The RESource Idiom! Tuesday, July 3, 12

Slide 2

Slide 2 text

@mattyoho Tuesday, July 3, 12

Slide 3

Slide 3 text

@mattyoho @EdgeCase Tuesday, July 3, 12

Slide 4

Slide 4 text

@mattyoho @NewContext Tuesday, July 3, 12

Slide 5

Slide 5 text

@mattyoho @NewContext @HungryAcademy Tuesday, July 3, 12

Slide 6

Slide 6 text

Conventio figura Thinking is hard Tuesday, July 3, 12

Slide 7

Slide 7 text

Conventio figura Rails requires less Thinking is hard Tuesday, July 3, 12

Slide 8

Slide 8 text

http://www.etsy.com/shop/weelittlestitches Patterns! Tuesday, July 3, 12

Slide 9

Slide 9 text

http://www.etsy.com/shop/weelittlestitches Patterns! REST! Tuesday, July 3, 12

Slide 10

Slide 10 text

Representational State Transfer on Rails! Tuesday, July 3, 12

Slide 11

Slide 11 text

Resources “Nouns” of the system (At least from an external perspective... ...return to that later) Tuesday, July 3, 12

Slide 12

Slide 12 text

HTTP Provides the shared set of verbs, and their semantics, for interacting with the system’s nouns, i.e. resources Tuesday, July 3, 12

Slide 13

Slide 13 text

Features Such as... •Routes •respond_to/with •Default view rendering •Rails Tuesday, July 3, 12

Slide 14

Slide 14 text

Memory Footprint Tuesday, July 3, 12

Slide 15

Slide 15 text

Memory Footprint Simpler... Tuesday, July 3, 12

Slide 16

Slide 16 text

Memory Footprint Simpler... Smaller... Tuesday, July 3, 12

Slide 17

Slide 17 text

Memory Footprint Simpler... Easier to understand Smaller... Tuesday, July 3, 12

Slide 18

Slide 18 text

Caveat! Tuesday, July 3, 12

Slide 19

Slide 19 text

Reader beware! These ideas should be considered my opinion! They are options, context dependent, Caveat Emptor! Tuesday, July 3, 12

Slide 20

Slide 20 text

How about an example?! Tuesday, July 3, 12

Slide 21

Slide 21 text

Feeds! Tuesday, July 3, 12

Slide 22

Slide 22 text

User Item Feed 1 1 1 0..* Diagram! Tuesday, July 3, 12

Slide 23

Slide 23 text

Feedlr::Application.routes.draw do root to: 'feeds#index' resources :feeds, only: [:index, :show] resources :items, except: :index end Routes! Tuesday, July 3, 12

Slide 24

Slide 24 text

New Feature Refeed Tuesday, July 3, 12

Slide 25

Slide 25 text

Refeed Let’s add some methods! Tuesday, July 3, 12

Slide 26

Slide 26 text

Feedlr::Application.routes.draw do root to: 'feeds#index' resources :feeds, only: [:index, :show] resources :items, except: :index do member do post :refeed end end end Routes! Tuesday, July 3, 12

Slide 27

Slide 27 text

Routes! Feedlr::Application.routes.draw do root to: 'feeds#index' resources :feeds, only: [:index, :show] resources :items, except: :index do member do post :refeed end end end Tuesday, July 3, 12

Slide 28

Slide 28 text

class ItemsController < ApplicationController before_filter :require_login respond_to :html, :json def new @item = Item.new respond_with(@item) end def create; end def destroy; end # etc. end CRUD Tuesday, July 3, 12

Slide 29

Slide 29 text

class ItemsController < ApplicationController before_filter :require_login respond_to :html, :json def new @item = Item.new respond_with(@item) end def create; end def destroy; end def refeed # impl. end end CRUD? Tuesday, July 3, 12

Slide 30

Slide 30 text

CRUD? class ItemsController < ApplicationController before_filter :require_login respond_to :html, :json def new @item = Item.new respond_with(@item) end def create; end def destroy; end def refeed # impl. end end Tuesday, July 3, 12

Slide 31

Slide 31 text

class ItemsController < ApplicationController before_filter :require_login def refeed feed = current_user.feed item = feed.items.find(params[:id]) attrs = item.attributes.merge(refed_item: item) feed.items.create(attrs) redirect_to(feed) end end MEMBER Tuesday, July 3, 12

Slide 32

Slide 32 text

Feedlr::Application.routes.draw do root to: 'feeds#index' resources :feeds, only: [:index, :show] resources :items, except: :index do member do post :refeed delete :remove_refeed end end end Routes! Tuesday, July 3, 12

Slide 33

Slide 33 text

Routes! Feedlr::Application.routes.draw do root to: 'feeds#index' resources :feeds, only: [:index, :show] resources :items, except: :index do member do post :refeed delete :remove_refeed end end end Tuesday, July 3, 12

Slide 34

Slide 34 text

class ItemsController < ApplicationController before_filter :require_login def refeed feed = current_user.feed item = feed.items.find(params[:id]) attrs = item.attributes.merge(refed_item: item) feed.items.create(attrs) redirect_to(feed) end end MEMBER Tuesday, July 3, 12

Slide 35

Slide 35 text

ARGH! class ItemsController < ApplicationController before_filter :require_login def refeed feed = current_user.feed item = feed.items.find(params[:id]) attrs = item.attributes.merge(refed_item: item) feed.items.create(attrs) redirect_to(feed) end def remove_refeed end end Tuesday, July 3, 12

Slide 36

Slide 36 text

Trouble When programming, we should prefer to generalize from the specific Tuesday, July 3, 12

Slide 37

Slide 37 text

Edge Cases! Are bad! Tuesday, July 3, 12

Slide 38

Slide 38 text

Edge Cases! (That’s why we changed our name.) Are bad! Tuesday, July 3, 12

Slide 39

Slide 39 text

Feedlr::Application.routes.draw do root to: 'feeds#index' resources :feeds, only: [:index, :show] resources :items, except: :index do member do post :refeed delete :remove_refeed end end end Routes! Tuesday, July 3, 12

Slide 40

Slide 40 text

Feedlr::Application.routes.draw do root to: 'feeds#index' resources :feeds, only: [:index, :show] resources :items, except: :index do resources :refeeds, only: [:create, :destroy] end end Nested! Tuesday, July 3, 12

Slide 41

Slide 41 text

ARGH! class ItemsController < ApplicationController before_filter :require_login def refeed feed = current_user.feed item = feed.items.find(params[:id]) attrs = item.attributes.merge(refed_item: item) feed.items.create(attrs) redirect_to(feed) end def remove_refeed end end Tuesday, July 3, 12

Slide 42

Slide 42 text

class RefeedsController < ApplicationController before_filter :require_login def create feed = current_user.feed item = feed.items.find(params[:item_id]) attrs = item.attributes.merge(refed_item: item) feed.items.create(attrs) redirect_to(feed) end def destroy end end ARGH? Tuesday, July 3, 12

Slide 43

Slide 43 text

ARGH? class RefeedsController < ApplicationController before_filter :require_login def create feed = current_user.feed item = feed.items.find(params[:item_id]) attrs = item.attributes.merge(refed_item: item) feed.items.create(attrs) redirect_to(feed) end def destroy end end Tuesday, July 3, 12

Slide 44

Slide 44 text

class Refeed def initialize(feed,item) @feed = feed @item = item end def create atrs = @item.attributes.merge(refed_item: item) @feed.items.create(atrs) end def destroy; end end app/lib Tuesday, July 3, 12

Slide 45

Slide 45 text

class RefeedsController < ApplicationController before_filter :require_login def create feed = current_user.feed item = feed.items.find(params[:item_id]) attrs = item.attributes.merge(refed_item: item) feed.items.create(attrs) redirect_to(feed) end def destroy end end ARGH? Tuesday, July 3, 12

Slide 46

Slide 46 text

class RefeedsController < ApplicationController before_filter :require_login def create feed = current_user.feed item = feed.items.find(params[:item_id]) refeed = Refeed.new(feed, item) refeed.create redirect_to(feed) end def destroy end end MEH! Tuesday, July 3, 12

Slide 47

Slide 47 text

If you’re introducing non-”RESTful” methods into a controller, you’re probably missing a top- level concept that should be a first-class resource. Tuesday, July 3, 12

Slide 48

Slide 48 text

Resources DO NOT have to be backed by database tables! Shocking, I know. Tuesday, July 3, 12

Slide 49

Slide 49 text

Across town, trouble brews! Tuesday, July 3, 12

Slide 50

Slide 50 text

Danger looms in Rails City... Tuesday, July 3, 12

Slide 51

Slide 51 text

Danger looms in Rails City... Our apps strain under the weight of our code! Tuesday, July 3, 12

Slide 52

Slide 52 text

def new @feed_item = FeedItem.new respond_to do |format| format.html format.json { render json: @feed_item } end end def edit @feed_item = current_user.feed.feed_items.find(params[:id]) end def create kind = params[:feed_item].delete(:kind) @feed_item = current_user.feed.feed_item_of(kind).new(params[:feed_item]) respond_to do |format| if @feed_item.save format.html { redirect_to current_user.feed, notice: 'Feed item was successfully created.' } format.json { render json: @feed_item, status: :created, location: @feed_item } else format.html { render action: "new" } format.json { render json: @feed_item.errors, status: :unprocessable_entity } end end end def update @feed_item = current_user.feed.feed_items.find(params[:id]) respond_to do |format| if @feed_item.update_attributes(params[:feed_item]) format.html { redirect_to current_user.feed, notice: 'Feed item was successfully updated.' } format.json { head :no_content } else format.html { render action: "edit" } format.json { render json: @feed_item.errors, status: :unprocessable_entity } end end end def destroy @feed_item = current_user.feed.feed_items.find(params[:id]) @feed_item.destroy respond_to do |format| format.html { redirect_to root_url } format.json { head :no_content } end end def destroy @feed_item = current_user.feed.feed_items.find(params[:id]) @feed_item.destroy respond_to do |format| format.html { redirect_to root_url } format.json { head :no_content } end end def destroy @feed_item = current_user.feed.feed_items.find(params[:id]) @feed_item.destroy respond_to do |format| format.html { redirect_to root_url } format.json { head :no_content } end end Tuesday, July 3, 12

Slide 53

Slide 53 text

Fat models Skinny Controllers Tuesday, July 3, 12

Slide 54

Slide 54 text

But the beast broke free! Tuesday, July 3, 12

Slide 55

Slide 55 text

def new @feed_item = FeedItem.new respond_to do |format| format.html format.json { render json: @feed_item } end end def edit @feed_item = current_user.feed.feed_items.find(params[:id]) end def create kind = params[:feed_item].delete(:kind) @feed_item = current_user.feed.feed_item_of(kind).new(params[:feed_item]) respond_to do |format| if @feed_item.save format.html { redirect_to current_user.feed, notice: 'Feed item was successfully created.' } format.json { render json: @feed_item, status: :created, location: @feed_item } else format.html { render action: "new" } format.json { render json: @feed_item.errors, status: :unprocessable_entity } end end end def update @feed_item = current_user.feed.feed_items.find(params[:id]) respond_to do |format| if @feed_item.update_attributes(params[:feed_item]) format.html { redirect_to current_user.feed, notice: 'Feed item was successfully updated.' } format.json { head :no_content } else format.html { render action: "edit" } format.json { render json: @feed_item.errors, status: :unprocessable_entity } end end end def destroy @feed_item = current_user.feed.feed_items.find(params[:id]) @feed_item.destroy respond_to do |format| format.html { redirect_to root_url } format.json { head :no_content } end end def destroy @feed_item = current_user.feed.feed_items.find(params[:id]) @feed_item.destroy respond_to do |format| format.html { redirect_to root_url } format.json { head :no_content } end end def destroy @feed_item = current_user.feed.feed_items.find(params[:id]) @feed_item.destroy respond_to do |format| format.html { redirect_to root_url } format.json { head :no_content } end end Tuesday, July 3, 12

Slide 56

Slide 56 text

HAHAHA! Methods! Tuesday, July 3, 12

Slide 57

Slide 57 text

Go back to the well... Tuesday, July 3, 12

Slide 58

Slide 58 text

Navigate Model View Controller Model View Controller Tuesday, July 3, 12

Slide 59

Slide 59 text

MVC Trygve Reenskaug DCI Tuesday, July 3, 12

Slide 60

Slide 60 text

The largely static data model of the application, captures user and programmer knowledge about what the system is. Data Tuesday, July 3, 12

Slide 61

Slide 61 text

A mapping from one or more data objects to roles in the system. Implements one or more system use cases. CONTEXT Tuesday, July 3, 12

Slide 62

Slide 62 text

Captures behavior, or what the system does. Corresponds to the user’s mental model of action. Stateless, and relies on a data object in a context. Interaction Tuesday, July 3, 12

Slide 63

Slide 63 text

(Roles) Captures behavior, or what the system does. Corresponds to the user’s mental model of action. Stateless, and relies on a data object in a context. Tuesday, July 3, 12

Slide 64

Slide 64 text

Navigate Model View Controller Role View Controller Data Context Tuesday, July 3, 12

Slide 65

Slide 65 text

class User < AR::Base # Data end module ComicBookReading # Role def read ... end end # Context? user = User.new user.extend ComicBookReading user.read() Tuesday, July 3, 12

Slide 66

Slide 66 text

class ComicBookReaderController def update # Context? user = current_user user.extend ComicBookReading user.read() end end Tuesday, July 3, 12

Slide 67

Slide 67 text

Coupling! Mixins == Inheritance Tuesday, July 3, 12

Slide 68

Slide 68 text

Navigate Model View Controller Model View Controller Tuesday, July 3, 12

Slide 69

Slide 69 text

Request Model View Controller Controller View Model Tuesday, July 3, 12

Slide 70

Slide 70 text

The Dark Model Saga! Tuesday, July 3, 12

Slide 71

Slide 71 text

class RefeedsController < ApplicationController before_filter :require_login def create feed = current_user.feed item = feed.items.find(params[:item_id]) refeed = Refeed.new(feed, item) refeed.create redirect_to(feed) end def destroy end end MEH! Tuesday, July 3, 12

Slide 72

Slide 72 text

module ResourceModel def self.inherited(inheritor) inheritor.class_eval do include ActiveModel::Naming include ActiveModel::Validations include ActiveModel::Translation end end attr_reader :attributes, :errors def initialize(attributes = {}) @attributes = attributes @errors = ActiveModel::Errors.new(self) end end app/Mixins Tuesday, July 3, 12

Slide 73

Slide 73 text

module ResourceModel def to_key; nil end def to_param; nil end def to_partial_path; ”" end def valid? errors.empty? end def persisted?; false end def read_attribute_for_validation(key) @attributes[key] end end app/Mixins Tuesday, July 3, 12

Slide 74

Slide 74 text

class Refeed def initialize(feed,item) @feed = feed @item = item end def create atrs = @item.attributes.merge(refed_item: item) @feed.items.create(atrs) end def destroy; end end app/lib Tuesday, July 3, 12

Slide 75

Slide 75 text

class Refeed include ResourceModel def self.find_by_feed_and_item_id(feed, item_id) ... end def save feed = attributes[:feed] item = feed.items.find(attributes[:item_id]) atrs = item.attributes.merge(refed_item: item) feed.items.create(atrs) end end app/models Tuesday, July 3, 12

Slide 76

Slide 76 text

app/models class Refeed include ResourceModel def self.find_by_feed_and_item_id(feed, item_id) ... end def save feed = attributes[:feed] item = feed.items.find(attributes[:item_id]) atrs = item.attributes.merge(refed_item: item) feed.items.create(atrs) end end Tuesday, July 3, 12

Slide 77

Slide 77 text

class RefeedsController < ApplicationController before_filter :require_login def create feed = current_user.feed item = feed.items.find(params[:item_id]) refeed = Refeed.new(feed, item) refeed.create redirect_to(feed) end def destroy end end Meh! Tuesday, July 3, 12

Slide 78

Slide 78 text

class RefeedsController < ApplicationController before_filter :require_login def create feed = current_user.feed item = feed.find(params[:item_id]) refeed = Refeed.new(feed: feed, item: item) if refeed.save redirect_to refeed_path(refeed) end end def destroy end end Okay! Tuesday, July 3, 12

Slide 79

Slide 79 text

class RefeedsController < ApplicationController def create ... end def destroy feed = current_user.feed item_id = params[:item_id] refeed = Refeed.find_by_feed_and_item_id( feed: feed, item_id: item_id) refeed.destroy redirect_to feed end end Okay! Tuesday, July 3, 12

Slide 80

Slide 80 text

But does it make sense? Tuesday, July 3, 12

Slide 81

Slide 81 text

Interface Don’t have the same surface area as AR::Base objects Tuesday, July 3, 12

Slide 82

Slide 82 text

Anemic names Refeed#save doesn’t seem descriptive Tuesday, July 3, 12

Slide 83

Slide 83 text

Unless! We cast Refeed as a resource abstraction with CRUD semantics! domain Tuesday, July 3, 12

Slide 84

Slide 84 text

Bookkeeping In this case, we draw a line between HTTP book- keeping and problem domain Tuesday, July 3, 12

Slide 85

Slide 85 text

It’s a spectrum! Tuesday, July 3, 12

Slide 86

Slide 86 text

Food for thought, anyway Tuesday, July 3, 12

Slide 87

Slide 87 text

Food for thought, anyway That said... Tuesday, July 3, 12

Slide 88

Slide 88 text

Don’t get in Rails’ way. Tuesday, July 3, 12

Slide 89

Slide 89 text

http://www.etsy.com/shop/weelittlestitches Tuesday, July 3, 12

Slide 90

Slide 90 text

Mental Model! Tuesday, July 3, 12

Slide 91

Slide 91 text

THANKS! See you next time! Questions? @mattyoho @NewContext Tuesday, July 3, 12