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

Meiosis in Rails Apps

Meiosis in Rails Apps

Rails is optimized for getting you up and running quickly. However, while the first steps are so fast and fun (we all built a blog in 15 minutes!), the rapid speed at which they grow can raise some challenges.
At some point, every Rails developer needs to transform a “monster app” into something more manageable.

During this talk, you’ll join me as I share the story of one such app and the meiosis process of splitting it into several apps, services and gems.
You’ll learn about the pitfalls we faced, the bugs that plagued us, as well as get a robust set of pragmatic tips that will explain what to do when facing a daunting task such as this one.

Talk given at Rails Israel 2014

Yonatan Bergman

November 02, 2014
Tweet

More Decks by Yonatan Bergman

Other Decks in Programming

Transcript

  1. require 'ebayr'
 require 'rebay'
 require_relative ‘services/super_secret_api' ! ! module EbayServices

    class Shopping def initialize @shopping = Rebay::Shopping.new end end end ! !
  2. $ curl -H “TOKEN: FOO” https://example.com { ‘status’: ‘ok’ }

    ! $ curl https://example.com?token=FOO { ‘status’: ‘ok’ }
  3. $ curl https://example.com/api/v1/status { ‘status’: ‘ok’ } ! $ curl

    -H ‘FOO-VERSION: 1’ 
 https://example.com/api/status { ‘status’: ‘ok’ }
  4. $ curl https://example.com/api/v1/status { ‘status’: ‘ok’ } ! $ curl

    -H ‘FOO-VERSION: 1’ 
 https://example.com/api/status { ‘status’: ‘ok’ } ! $ curl
 -H ‘Accept: application/vnd.foo.v1+json’
 https://example.com/api/status { ‘status’: ‘ok’ }
  5. class API::ItemSerializer < ActiveModel::Serializer attributes :uid, :title, :description, :category has_one

    :response, 
 serializer: API::ResponseSerializer 
 def include_category?
 scope.api_version > 1
 end 
 end
  6. class API::ItemSerializer < ActiveModel::Serializer attributes :uid, :title, :description, :category has_one

    :response, 
 serializer: API::ResponseSerializer 
 def include_category?
 scope.api_version > 1
 end 
 end
  7. class API::ItemSerializer < ActiveModel::Serializer attributes :uid, :title, :description, :category has_one

    :response, 
 serializer: API::ResponseSerializer 
 def include_category?
 scope.api_version > 1
 end 
 end
  8. class API::ItemSerializer < ActiveModel::Serializer attributes :uid, :title, :description, :category has_one

    :response, 
 serializer: API::ResponseSerializer 
 def include_category?
 scope.api_version > 1
 end 
 end
  9. class API::ItemCreator def create(params)
 Item.create(parsed_params(params))
 end def parsed_params(params)
 ItemParser.new(params).to_hash
 end

    class ItemParser < Hashie::Trash
 property :title, required: true
 property :description
 end end
  10. class API::ItemCreator def create(params)
 Item.create(parsed_params(params))
 end def parsed_params(params)
 ItemParser.new(params).to_hash
 end

    class ItemParser < Hashie::Trash
 property :title, required: true
 property :description
 end end
  11. class API::ItemCreator def create(params)
 Item.create(parsed_params(params))
 end def parsed_params(params)
 ItemParser.new(params).to_hash
 end

    class ItemParser < Hashie::Trash
 property :title, required: true
 property :description
 end end
  12. class ItemParser < Hashie::Trash property :title, required: true
 property :description


    property :category_id, from: ‘category’
 property :price_cents, with: -> (price) {
 (price * 100).to_i
 } end
  13. class ItemParser < Hashie::Trash property :title, required: true
 property :description


    property :category_id, from: ‘category’
 property :price_cents, with: -> (price) {
 (price * 100).to_i
 } end
  14. class ItemParser < Hashie::Trash property :title, required: true
 property :description


    property :category_id, from: ‘category’
 property :price_cents, with: -> (price) {
 (price * 100).to_i
 } end
  15. class ItemParser < Hashie::Trash property :title, required: true
 property :description


    property :category_id, from: ‘category’
 property :price_cents, with: -> (price) {
 (price * 100).to_i
 } end
  16. class ItemParser < Hashie::Trash property :user_attributes, 
 from: ‘seller’,
 with:

    -> (hash) { 
 ItemParser::UserParser.new(hash) 
 } class UserParser < Hashie::Trash
 property :name, required: true
 end end
  17. class ItemParser < Hashie::Trash property :user_attributes, 
 from: ‘seller’,
 with:

    -> (hash) { 
 ItemParser::UserParser.new(hash) 
 } class UserParser < Hashie::Trash
 property :name, required: true
 end end
  18. class ItemParser < Hashie::Trash property :user_attributes, 
 from: ‘seller’,
 with:

    -> (hash) { 
 ItemParser::UserParser.new(hash) 
 } class UserParser < Hashie::Trash
 property :name, required: true
 end end
  19. class ItemParser < Hashie::Trash property :user_attributes, 
 from: ‘seller’,
 with:

    -> (hash) { 
 ItemParser::UserParser.new(hash) 
 } class UserParser < Hashie::Trash
 property :name, required: true
 end end
  20. class ItemParser < Hashie::Trash property :user_attributes, 
 from: ‘seller’,
 with:

    -> (hash) { 
 ItemParser::UserParser.new(hash) 
 } class UserParser < Hashie::Trash
 property :name, required: true
 end end
  21. ERROR ERROR CODE ERROR MESSAGE HTTP CODE record not found

    1003 The object you are looking for could not be found 404 unauthorized 1004 You are not authorized to do the following action 403 invalid param 1005 You are missing a required parameter 400 invalid value 1006 Why are you reading this slide? 400
  22. module API::ErrorSupport rescue_from Exception, with: :respond_with_error def respond_with_error(e)
 error =

    translate_exception(e)
 render json: error, 
 status: error.status_code
 end end
  23. module API::ErrorSupport rescue_from Exception, with: :respond_with_error def respond_with_error(e)
 error =

    translate_exception(e)
 render json: error, 
 status: error.status_code
 end end
  24. module API::ErrorSupport rescue_from Exception, with: :respond_with_error def respond_with_error(e)
 error =

    translate_exception(e)
 render json: error, 
 status: error.status_code
 end end
  25. POST /api/estimations { 'estimation': { ... },
 'callback': ‘https://example.com/callback' }

    ! RESPONSE { 'uid': 'Y2z2Q3fOTENx4w',
 'status': ‘added’, 'estimation': { ... }
  26. RESPONSE 500 // Wait 1s
 RESPONSE 500 // Wait 2s


    RESPONSE 500 // Wait 3s
 RESPONSE 500 // Wait 5s
 RESPONSE 500 // Wait 8s
 RESPONSE 200
  27. class LegacyModel < ActiveRecord::Base ! YAML_FILE = ‘config/legacy_db.yml’ self.abstract_class =

    true ! establish_connection( YAML::load_file(YAML_FILE)[Rails.env] ) ! end
  28. class LegacyItem < LegacyModel ! self.table_name = ‘items’ ! has_one

    :estimation, class_name: ‘LegacyEstimation’ foreign_key: ‘item_id’ ! end
  29. class LegacyMigration ! def run @output = MigrationOutput.new(@file) scope =

    LegacyItem.from_id(@starting_id) scope.find_each do |legacy_item| migrator = ItemMigrator.new(legacy_item) migrator.run @output.add(migrator.output) end end ! end