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

Lesscode

 Lesscode

RubyConfChina 2015 talk

Avatar for Xuejie "Rafael" Xiao

Xuejie "Rafael" Xiao

October 10, 2015
Tweet

More Decks by Xuejie "Rafael" Xiao

Other Decks in Programming

Transcript

  1. Simple vs. Easy • Simple • One fold/task • No

    interleaving • Defined by us • Easy • Near our capabilities • Defined by problems From Simplicity Matters @ RailsConf 2012 by Rich Hickey, https://www.youtube.com/watch?v=rI8tNMsozo0 Simple!
  2. Complexity • Essential Complexity • Determined by the problem •

    Accidental Complexity • Exists because our tools are not perfect
  3. Accidental Complexity • Simple • Untwisted • Complex • Twisted

    From Simplicity Matters @ RailsConf 2012 by Rich Hickey, https://www.youtube.com/watch?v=rI8tNMsozo0
  4. Don’t forget you have Lua at your fingertip! 1 local

    ctoken = redis.call('HGET', KEYS[1], '_cas') 2 if (not ctoken) or ctoken == ARGV[2] then 3 local ntoken 4 if not ctoken then 5 ntoken = 1 6 else 7 ntoken = tonumber(ctoken) + 1 8 end 9 redis.call('HMSET', KEYS[1], '_sdata', ARGV[1], 10 '_cas', ntoken, '_ndata', ARGV[3]) 11 return ntoken 12 else 13 error('cas_error') 14 end
  5. 1 class Event < Ohm::Model 2 attribute :name 3 reference

    :venue, :Venue 4 set :participants, :Person 5 counter :votes 6 7 index :name 8 end 9 10 class Venue < Ohm::Model 11 attribute :name 12 collection :events, :Event 13 end 14 15 class Person < Ohm::Model 16 attribute :name 17 end
  6. 1 event = Event.create :name => "Ohm Worldwide Conference 2031"

    2 event.id 3 # => 1 4 5 # Find an event by id 6 event == Event[1] 7 # => true 8 9 # Update an event 10 event.update :name => "Ohm Worldwide Conference 2032" 11 # => #<Event:0x007fb4c35e2458 @attributes={:name=>"Ohm Worldwide Conference"}, @_memo={}, @id="1"> 12 13 # Trying to find a non existent event 14 Event[2] 15 # => nil 16 17 # Finding all the events 18 Event.all.to_a 19 # => [<Event:1 name='Ohm Worldwide Conference 2031'>]
  7. 1 event.attendees.add(Person.create(name: "Albert")) 2 3 # And now... 4 event.attendees.each

    do |person| 5 # ...do what you want with this person. 6 end
  8. Complex Behavior • expect(…).to returns a Matcher object • You

    don’t know Matcher unless you read RSpec source code
  9. Tests cannot be simpler 1 setup do 2 {:a =>

    23, :b => 43} 3 end 4 5 test "should receive the result of the setup block as a parameter" do |params| 6 assert params == {:a => 23, :b => 43} 7 end 8 9 test "should evaluate the setup block before each test" do | params| 10 params[:a] = nil 11 end 12 13 test "should preserve the original values from the setup" do | params| 14 assert 23 == params[:a] 15 end
  10. 0 3000 6000 9000 12000 Cuba 3.4.0 Sinatra 1.4.6 ActionPack

    4.2.4 Cuba 3.4.0 has only 314 lines of code
  11. Gems normally used together with Cuba Framework | LOC ---------------|---------------

    mote | 33 shield | 98 scrivener | 97 ohm | 647 protest | 118 ost | 59 malone | 93 nobi | 127 clap | 28 gs | 43 dep | 213 Notice this is never about LOC, it’s about one library fulfills one purpose only
  12. The whole stack is ~1556 lines of code • Read

    the source code! • Simple library, clear boundary • Easy to extend
  13. You might say: Rails feels easy to me! • Complicated

    constructs can be: • Familiar • Ready to use • But they are still complex, meaning they are: • Interleaved • Brings accidental complexity, which will bite you
  14. Route 1 Houndapp::Application.routes.draw do 2 # ... 3 4 resources

    :repos, only: [:index] do 5 with_options(defaults: { format: :json }) do 6 resource :activation, only: [:create] 7 resource :deactivation, only: [:create] 8 resource :subscription, only: [:create, :destroy] 9 end 10 end 11 12 # ... 13 end
  15. Controller 1 class ActivationsController < ApplicationController 2 class FailedToActivate <

    StandardError; end 3 class CannotActivatePaidRepo < StandardError; end 4 5 before_action :check_repo_plan 6 7 def create 8 if activator.activate 9 analytics.track_repo_activated(repo) 10 render json: repo, status: :created 11 else 12 analytics.track_repo_activation_failed(repo) 13 render json: { errors: activator.errors }, status: 502 14 end 15 end 16 17 private
  16. Controller (cont.) 1 def check_repo_plan 2 if repo.plan_price > 0

    3 raise CannotActivatePaidRepo 4 end 5 end 6 7 def activator 8 @activator ||= RepoActivator.new(repo: repo, github_token: github_token) 9 end 10 11 def repo 12 @repo ||= current_user.repos.find(params[:repo_id]) 13 end 14 15 def github_token 16 current_user.token 17 end 18 end
  17. Related action in ApplicationController 1 class ApplicationController < ActionController::Base 2

    protect_from_forgery 3 4 before_action :force_https 5 before_action :capture_campaign_params 6 before_action :authenticate 7 8 helper_method :current_user, :signed_in? 9 10 private 11 # ... 12 13 def analytics 14 @analytics ||= Analytics.new(current_user, session[:campaign_params]) 15 end 16 17 # ... 18 end
  18. Service Object 1 class RepoActivator 2 attr_reader :errors 3 4

    def initialize(github_token:, repo:) 5 @github_token = github_token 6 @repo = repo 7 @errors = [] 8 end 9 10 def activate 11 activated = activate_repo 12 13 if activated 14 enqueue_org_invitation 15 end 16 17 activated 18 end 19 # ... 20 end
  19. View or Serializer 1 class RepoSerializer < ActiveModel::Serializer 2 attributes(

    3 :active, 4 :full_github_name, 5 :full_plan_name, 6 :github_id, 7 :id, 8 :in_organization, 9 :price_in_cents, 10 :private, 11 :stripe_subscription_id, 12 ) 13 # ... 14 end
  20. Luckily, the action we showed is simple • No helpers

    • No so-called presenter or whatever objects • We assume you already know models • before_action is not abused
  21. Routes that perform actions 1 on "application/:id/contact" do |id| 2

    application = Application[id] 3 on application && company.posts.include?(application.post) do 4 on post, param("message") do |params| 5 mail = Contact.new(params) 6 if mail.valid? 7 message = JSON.dump(application_id: id, 8 subject: params["subject"], body: params["body"]) 9 Ost[:contacted_applicant].push(message) 10 res.redirect "/post/#{application.post.id}/applications" 11 else 12 session[:error] = "All fields are required" 13 render("company/post/contact", 14 title: "Contact developer", 15 application: application, message: mail) 16 end 17 end 18 end 19 end
  22. Filters that validates requests 1 class Contact < Scrivener 2

    attr_accessor :subject, :body 3 4 def validate 5 assert_present :subject 6 assert_present :body 7 end 8 end
  23. Views that queries and assembles data 1 <section id="contact-applicant"> 2

    <h2>Contact developer</h2> 3 4 <form action="/application/{{ application.id }}/contact" 5 method="POST"> 6 <!-- ... --> 7 8 <input type="text" name="message[subject]" 9 value="{{ message.subject }}" placeholder="Subject"> 10 11 <textarea name="message[body]" 12 placeholder="This mail will be sent to the developer"> 13 {{ message.body }} 14 </textarea> 15 16 <!-- ... --> 17 </form> 18 </section>
  24. We do use helpers, but it’s more general 1 module

    DeveloperHelpers 2 # ... 3 4 def mote_vars(content) 5 super.merge(current_developer: current_developer) 6 end 7 8 def notfound(msg) 9 res.status = 404 10 res.write(msg) 11 halt(res.finish) 12 end 13 14 # ... 15 end
  25. People love to customize Rails • ActiveRecord vs. Sequel •

    Sprockets vs. Webpack/browserify • Disable Turbolinks • Concerns considered harmful • Rails API
  26. Don’t worry about bootstrapping speed! From “Simple Made Easy” at

    QCon London 2012 by Rich Hickey, http://www.infoq.com/presentations/Simple-Made-Easy-QCon-London-2012