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.




    ...
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

cl...
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

cl...
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
    # ...
Dirty Tracking
class Person
  include ActiveModel::Dirty

 define_attribute_methods [:name]

  def name
    @name
  end

  def name=(val)...
# 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.na...
# 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.n...
# Which attributes have changed?
person.name = 'Bob'
person.changed      # => ['name']
person.changes      # => {'name' =>...
# Resetting an attribute returns it to
# its original state:
person.reset_name!    # => 'Bill'
person.changed?       # => ...
Errors
class User
  def errors
    @errors ||= ActiveModel::Errors.new(self)
  end

  def validate!
    errors.add(:name, "can no...
Serialization
class User
  include ActiveModel::Serialization

  def attributes
    {
      :id => @id,
      :name => @name,
      :ema...
Validations
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :passwor...
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :passwor...
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :passwor...
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :passwor...
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :passwor...
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :passwor...
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :passwor...
class User
  include ActiveModel::Validations
  attr_reader :name, :email, :age,
              :terms_of_service, :passwor...
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::TestCa...
class User
  include ActiveModel::Conversion
  extend ActiveModel::Naming

  def valid?() true end

  def errors
    @erro...
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 = lis...
def self.for_user(user_id, options = {}, &block)
  includes = options[:include] || []

 request =
   Typhoeus::Request.new...
def self.get_ids(ids, &block)
  request = Typhoeus::Request.new(get_ids_uri(ids))

 request.on_complete do |response|
   j...
def self.get_ids(ids, &block)
  request = Typhoeus::Request.new(get_ids_uri(ids))

 request.on_complete do |response|
   j...
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.Call...
thread_pool = Executors.new_fixed_thread_pool(50)

futures = []
100.times do |i|
  request =
Request.new("http://localhost...
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
Building Web Service Clients with ActiveModel
Upcoming SlideShare
Loading in …5
×

Building Web Service Clients with ActiveModel

5,759 views

Published on

Published in: Technology

Building Web Service Clients with ActiveModel

  1. 1. Building Web Service Clients with ActiveModel Paul Dix http://pauldix.net @pauldix
  2. 2. IM Paul Dix photo credit: Sebastian Delmont
  3. 3. I am a Ruby love machine!
  4. 4. Web Services, duh!
  5. 5. ActiveModel
  6. 6. Web Services Abstract Complexity
  7. 7. Scale
  8. 8. Scale with Complexity PhOtOnQuAnTiQuE (http://www.flickr.com/photos/67968452@N00/1876685709/)
  9. 9. Scale with Team Size alexkess (http://www.flickr.com/photos/34838158@N00/3370167184/)
  10. 10. Monolithic American Backroom (http://www.flickr.com/photos/41922098@N03/4247207167/)
  11. 11. Split into Logical Parts
  12. 12. How ActiveResource killed my best friend
  13. 13. Brian Hathcock (http://www.flickr.com/photos/22961976@N00/3040920714/)
  14. 14. Understandable Code
  15. 15. Client libraries goal: readable and maintainable code
  16. 16. Parallel Connections
  17. 17. Make Stubbing Easy for Development
  18. 18. define:”Web Services”
  19. 19. External Services
  20. 20. Internal Services
  21. 21. ActiveModel is for use with rails forms helpers and other things. Things that are only used inside user requests. Calls Inside User Requests
  22. 22. focus(:internal_services)
  23. 23. Abstracting Complexity
  24. 24. Work with large code base
  25. 25. Scale
  26. 26. Not Traffic
  27. 27. NoSQL is getting there
  28. 28. Code Complexity
  29. 29. Team Size
  30. 30. Examples
  31. 31. Processor
  32. 32. Operating System
  33. 33. MapReduce
  34. 34. Web Services?
  35. 35. Email Service
  36. 36. User Service
  37. 37. Haystack
  38. 38. Indexing
  39. 39. How to separate functionality
  40. 40. Partition on Iteration Speed
  41. 41. Partition on Logical Fucntion
  42. 42. Partition on Read/Write Frequencies
  43. 43. Partition on Join Frequency
  44. 44. Goals
  45. 45. Denormalize and Replicate
  46. 46. Separation of Concerns
  47. 47. Abstraction
  48. 48. ActiveResource and ActiveRecord killed these
  49. 49. Both make connections inside the model
  50. 50. class Collect < Base end class ShippingAddress < Base end class BillingAddress < Base end class LineItem < Base end class ShippingLine < Base end class NoteAttribute < Base end
  51. 51. lloydi (http://www.flickr.com/photos/58944004@N00/2362260604/)
  52. 52. class Collect < Base end class ShippingAddress < Base end class BillingAddress < Base end class LineItem < Base end class ShippingLine < Base end class NoteAttribute < Base end
  53. 53. Makes requests serially
  54. 54. Requests in Parallel
  55. 55. ActiveModel
  56. 56. helpers for adding prefixes and suffixes to methods.... boring AttributeMethods
  57. 57. Callbacks
  58. 58. class User include ActiveModel::Callbacks before_create :do_stuff def create # ... end def do_stuff # ... end end
  59. 59. Dirty Tracking
  60. 60. 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
  61. 61. # A newly instantiated object is unchanged: person = Person.find_by_name('Uncle Bob') person.changed? # => false
  62. 62. # 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']
  63. 63. # Save the changes: person.save person.changed? # => false person.name_changed? # => false
  64. 64. # Assigning the same value leaves the # attribute unchanged: person.name = 'Bill' person.name_changed? # => false person.name_change # => nil
  65. 65. # Which attributes have changed? person.name = 'Bob' person.changed # => ['name'] person.changes # => {'name' => ['Bill', 'Bob']}
  66. 66. # Resetting an attribute returns it to # its original state: person.reset_name! # => 'Bill' person.changed? # => false person.name_changed? # => false person.name # => 'Bill'
  67. 67. Errors
  68. 68. 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"]
  69. 69. Serialization
  70. 70. 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])
  71. 71. Validations
  72. 72. 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?
  73. 73. 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?
  74. 74. 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?
  75. 75. 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?
  76. 76. 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?
  77. 77. 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?
  78. 78. 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?
  79. 79. 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?
  80. 80. validates_uniqueness_of?
  81. 81. ehpien (http://www.flickr.com/photos/91499534@N00/343313257/)
  82. 82. Testing
  83. 83. 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
  84. 84. 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
  85. 85. Parallel Connections
  86. 86. Asynchronous
  87. 87. EventMachine
  88. 88. Curb
  89. 89. Typhoeus Mushkush (http://www.flickr.com/photos/43002463@N00/2877016310/)
  90. 90. Quick Example
  91. 91. 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
  92. 92. 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
  93. 93. 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
  94. 94. 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
  95. 95. Gunnsi (http://www.flickr.com/photos/38735097@N00/440198755/)
  96. 96. Threaded
  97. 97. 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
  98. 98. 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
  99. 99. Keep Alive
  100. 100. Awesome art credit: Joe West
  101. 101. Stubbing
  102. 102. Review
  103. 103. Readable Client Code
  104. 104. lloydi (http://www.flickr.com/photos/58944004@N00/2362260604/)
  105. 105. Models Build Requests
  106. 106. Parallel Connections
  107. 107. Validate Early
  108. 108. Easy Stubs
  109. 109. Abstract Complexity
  110. 110. Scale
  111. 111. Scale with Complexity PhOtOnQuAnTiQuE (http://www.flickr.com/photos/67968452@N00/1876685709/)
  112. 112. Scale with Team Size alexkess (http://www.flickr.com/photos/34838158@N00/3370167184/)
  113. 113. Monolithic American Backroom (http://www.flickr.com/photos/41922098@N03/4247207167/)
  114. 114. Thanks!
  115. 115. Questions?

×