Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Exploiting The Resource Idiom

Exploiting The Resource Idiom

Scottish Ruby Conference 2012

Matt Yoho

July 03, 2012
Tweet

More Decks by Matt Yoho

Other Decks in Programming

Transcript

  1. For smaller, simpler, and more readily understandable Rails web apps

    Exploiting The RESource Idiom! Tuesday, July 3, 12
  2. Resources “Nouns” of the system (At least from an external

    perspective... ...return to that later) Tuesday, July 3, 12
  3. HTTP Provides the shared set of verbs, and their semantics,

    for interacting with the system’s nouns, i.e. resources Tuesday, July 3, 12
  4. Reader beware! These ideas should be considered my opinion! They

    are options, context dependent, Caveat Emptor! Tuesday, July 3, 12
  5. Feedlr::Application.routes.draw do root to: 'feeds#index' resources :feeds, only: [:index, :show]

    resources :items, except: :index end Routes! Tuesday, July 3, 12
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. Resources DO NOT have to be backed by database tables!

    Shocking, I know. Tuesday, July 3, 12
  26. Danger looms in Rails City... Our apps strain under the

    weight of our code! Tuesday, July 3, 12
  27. 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
  28. 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
  29. The largely static data model of the application, captures user

    and programmer knowledge about what the system is. Data Tuesday, July 3, 12
  30. 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
  31. 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
  32. (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
  33. 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
  34. class ComicBookReaderController def update # Context? user = current_user user.extend

    ComicBookReading user.read() end end Tuesday, July 3, 12
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. Unless! We cast Refeed as a resource abstraction with CRUD

    semantics! domain Tuesday, July 3, 12
  45. Bookkeeping In this case, we draw a line between HTTP

    book- keeping and problem domain Tuesday, July 3, 12