design que visa a construção de um sistema baseado em serviços que se comunicam através de um protocolo • Protocolo: HTTP • REST: estilo arquitetural utilizado para criar Web Services.
Não possui accessors • uso do method_missing module Library class Book def self.create(params) response = post('books', books.to_hash) new(response['data']) end def initialize(data) @data = JSON.parse(data) end def id @data['id'] || @data['_id'] end def valid? errors.nil? end def method_missing(name, *args, &block) if has_key?(name.to_s) data[name.to_s] else nil end end ... end end Book (Wrapper)
responsabilidades • Sabe como interpretar o recurso • Sabe como requisitar o recurso module Library class Book def self.create(params) response = post('books', books.to_hash) new(response['data']) end def initialize(data) @data = JSON.parse(data) end def id @data['id'] || @data['_id'] end def valid? errors.nil? end def method_missing(name, *args, &block) if has_key?(name.to_s) data[name.to_s] else nil end end ... end end Book (Wrapper)
• Os erros não são acoplados ao form, uso do flash message class BooksController < ApplicationController def new end def create @book = Library::Book.create(params[:book]) if @book.valid? flash[:success] = 'Successfully created a book.' redirect_to book_path(@book.id) else flash[:error] = @book.errors @book = Library::Book.new(params[:book].to_json) render :new, status: 400 end end end Dashboard Admin (Web)
mostrados globalmente. Se fosse necessário mostrar os erros por campo, aumentaria e muito a complexidade <% flash.each do |type, message| %> <div class="alert alert-<%= type %>"> <p> <strong><%= type.to_s.camelize %>!</strong> <%= message %> </p> </div> <% end %> Dashboard Admin (Web)
ambas as aplicações • Só é usado parte dos recursos do Rails • Piora sensivelmente a legibilidade • Renderização feita na mão e aumento de complexidade para melhorar UX
interagir com o form e o i18n. • Usa o módulo Errors para se comunicar com Rails • Accessors criados dinamicamente module Fuelzee class BaseModel include ActiveModel::Model extend ActiveModel::Naming include ActiveModel::Conversion attr_reader :errors def initialize(data={}) data_errors = data.delete(:errors) if data_errors @errors = ActiveModel::Errors.new(self) add_errors(data_errors) end self.class.module_eval { attr_accessor *data.keys } super(data) end def method_missing(m, *args, &block) nil end private def add_errors(data_errors) if data_errors.is_a? Hash data_errors.each do |attr, error_arr| error_arr.each do |error| errors.add(attr, error) end end else errors.add(:base, data_errors) end end end end
lógica de interpretação do recurso da API • Indica Modelo a ser usado module Library class BookMapper < Mapper def create(params) response = transport.post("books", params) build_response(response) end protected def model_class Book end end end
As mensagens de erro são automaticamente acopladas ao form pelo Rails class BooksController < ApplicationController def new @book = Library::Book.new end def create book_mapper = Library::BookMapper.create(params[:book]) if book_mapper.valid? flash[:success] = 'Successfully created reward.' redirect_to books_path(book_mapper.id) else @book = Library::Book.new(params[:book]) @book.errors = book_mapper.errors render :new, status: 422 end end end