Building Web Service
     Clients with
    ActiveModel
          Paul Dix
      http://pauldix.net
          @pauldix
IM Paul Dix




  photo credit: Sebastian Delmont
I am a Ruby love
    machine!
Web Services, duh!
ActiveModel
Web Services Abstract
    Complexity
Scale
Scale with Complexity

PhOtOnQuAnTiQuE (http://www.flickr.com/photos/67968452@N00/1876685709/)
Scale with Team Size
alexkess (http://www.flickr.com/photos/34838158@N00/3370167184/)
Monolithic




  American Backroom (http://www.flickr.com/photos/41922098@N03/4247207167/)
Split into Logical Parts
How ActiveResource
killed my best friend
Brian Hathcock (http://www.flickr.com/photos/22961976@N00/3040920714/)
Understandable Code
Client libraries goal:
    readable and
 maintainable code
Parallel Connections
Make Stubbing Easy for
    Development
define:”Web Services”
External
Services
Internal
Services
ActiveModel is for use
with rails forms helpers
and other things. Things
that are only used inside
user requests.




                     Calls Inside User
                         Requests
focus(:internal_services)
Abstracting Complexity
Work with large code
       base
Scale
Not Traffic
NoSQL is getting there
Code Complexity
Team Size
Examples
Processor
Operating System
MapReduce
Web Services?
Email Service
User Service
Haystack
Indexing
How to separate
 functionality
Partition on Iteration
        Speed
Partition on Logical
      Fucntion
Partition on Read/Write
       Frequencies
Partition on Join
   Frequency
Goals
Denormalize and
   Replicate
Separation of Concerns
Abstraction
ActiveResource and
ActiveRecord killed
       these
Both make connections
   inside the model
class Collect < Base
end

class ShippingAddress < Base
end

class BillingAddress < Base
end

class LineItem < Base
end

class ShippingLine < Base
end

class NoteAttribute < Base
end
lloydi (http://www.flickr.com/photos/58944004@N00/2362260604/)
class Collect < Base
end

class ShippingAddress < Base
end

class BillingAddress < Base
end

class LineItem < Base
end

class ShippingLine < Base
end

class NoteAttribute < Base
end
Makes requests serially
Requests in Parallel
ActiveModel
helpers for adding
prefixes and suffixes to
methods.... boring




              AttributeMethods
Callbacks
class User
  include ActiveModel::Callbacks

 before_create :do_stuff

  def create
    # ...
  end

  def do_stuff
    # ...
  end
end
Dirty Tracking
class Person
  include ActiveModel::Dirty

 define_attribute_methods [:name]

  def name
    @name
  end

  def name=(val)
    name_will_change!
    @name = val
  end

  def save
    @previously_changed = changes
  end
end
# A newly instantiated object is unchanged:
person = Person.find_by_name('Uncle Bob')
person.changed?       # => false
# Change the name:
person.name = 'Bob'
person.changed?        #   =>   true
person.name_changed?   #   =>   true
person.name_was        #   =>   'Uncle Bob'
person.name_change     #   =>   ['Uncle Bob', 'Bob']
person.name = 'Bill'
person.name_change     # => ['Uncle Bob', 'Bill']
# Save the changes:
person.save
person.changed?        # => false
person.name_changed?   # => false
# Assigning the same value leaves the
# attribute unchanged:
person.name = 'Bill'
person.name_changed? # => false
person.name_change     # => nil
# Which attributes have changed?
person.name = 'Bob'
person.changed      # => ['name']
person.changes      # => {'name' => ['Bill', 'Bob']}
# Resetting an attribute returns it to
# its original state:
person.reset_name!    # => 'Bill'
person.changed?       # => false
person.name_changed? # => false
person.name           # => 'Bill'
Errors
class User
  def errors
    @errors ||= ActiveModel::Errors.new(self)
  end

  def validate!
    errors.add(:name, "can not be nil")
  end
end

user.errors[:name]   #=> ["can not be nil"]
Serialization
class User
  include ActiveModel::Serialization

  def attributes
    {
      :id => @id,
      :name => @name,
      :email => @email
    }
  end
end

user.to_json(:only => [:id, :name])
user.to_xml(:except => [:email])
Validations
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :password,
              :password_confirmation, :role

  validates_acceptance_of :terms_of_service
end

user = User.new
puts user.valid?
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :password,
              :password_confirmation, :role

  validates_confirmation_of :password
end

user = User.new
puts user.valid?
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :password,
              :password_confirmation, :role

  validates_presence_of :name
end

user = User.new
puts user.valid?
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :password,
              :password_confirmation, :role

  validates_exclusion_of :name,
    :in => ["admin"]
end

user = User.new
puts user.valid?
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :password,
              :password_confirmation, :role

  validates_inclusion_of :role,
    :in => ["user", "admin"]
end

user = User.new
puts user.valid?
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :password,
              :password_confirmation, :role

  validates_format_of :email,
:with => /A([^@s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})Z/i
end

user = User.new
puts user.valid?
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :password,
              :password_confirmation, :role

  validates_length_of :password, :minimum => 6
end

user = User.new
puts user.valid?
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :password,
              :password_confirmation, :role

  validates_numericality_of :age
end

user = User.new
puts user.valid?
validates_uniqueness_of?
ehpien (http://www.flickr.com/photos/91499534@N00/343313257/)
Testing
require   'rubygems'
require   'active_model'
require   'test/unit'
require   'user'

class LintTest < ActiveModel::TestCase
  include ActiveModel::Lint::Tests

  def setup
    @model = User.new
  end
end
class User
  include ActiveModel::Conversion
  extend ActiveModel::Naming

  def valid?() true end

  def errors
    @errors ||= ActiveModel::Errors.new(self)
  end

  def persisted?
    true
  end
end
Parallel Connections
Asynchronous
EventMachine
Curb
Typhoeus




Mushkush (http://www.flickr.com/photos/43002463@N00/2877016310/)
Quick Example
PauldixReadingList::ReadingList.for_user(
  "paul",
  :include => [:entry, :rating_total]) do |list|

  reading_list = list
end

HYDRA.run

# now we can access the reading list
reading_list.entries.each do |entry|
  puts entry.id
  puts entry.title
  puts entry.body
  puts "up: #{entry.rating_total.up_count} |
down: #{entry.rating_total.down_count}nn"
end
def self.for_user(user_id, options = {}, &block)
  includes = options[:include] || []

 request =
   Typhoeus::Request.new(get_by_id_uri(user_id))
 request.on_complete do |response|
   list = new(response.body, options)

   list.request_entries if includes.include?(:entry)
   list.request_rating_totals if
     includes.include?(:rating_total)

    block.call(list)
  end

  PauldixReadingList::Config.hydra.queue(request)
end
def self.get_ids(ids, &block)
  request = Typhoeus::Request.new(get_ids_uri(ids))

 request.on_complete do |response|
   json = Yajl::Parser.parse(response.body)

   entries = ids.map do |id|
     new(json[id].merge("id" => id))
   end

    block.call(entries)
  end

  PauldixEntries::Config.hydra.queue(request)
end
def self.get_ids(ids, &block)
  request = Typhoeus::Request.new(get_ids_uri(ids))

 request.on_complete do |response|
   json = Yajl::Parser.parse(response.body)

   ratings = ids.map do |id|
     new(json[id])
   end

    block.call(ratings)
  end

  PauldixRatings::Config.hydra.queue(request)
end
Gunnsi (http://www.flickr.com/photos/38735097@N00/440198755/)
Threaded
require 'net/http'
include Java
import 'java.util.concurrent.Executors'

class Request
  include java.util.concurrent.Callable
  def initialize(url)
    @url = url
  end

  def call
    Net::HTTP.get(URI.parse(@url))
  end
end
thread_pool = Executors.new_fixed_thread_pool(50)

futures = []
100.times do |i|
  request =
Request.new("http://localhost:3000/entries/#{i}")
  futures << thread_pool.submit(request)
end

results = futures.map {|f| f.get}
# do something with results

thread_pool.shutdown
Keep Alive
Awesome art credit: Joe West
Stubbing
Review
Readable Client Code
lloydi (http://www.flickr.com/photos/58944004@N00/2362260604/)
Models Build Requests
Parallel Connections
Validate Early
Easy Stubs
Abstract Complexity
Scale
Scale with Complexity

PhOtOnQuAnTiQuE (http://www.flickr.com/photos/67968452@N00/1876685709/)
Scale with Team Size
alexkess (http://www.flickr.com/photos/34838158@N00/3370167184/)
Monolithic




  American Backroom (http://www.flickr.com/photos/41922098@N03/4247207167/)
Thanks!
Questions?

Building Web Service Clients with ActiveModel