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

Ruby and Rails: The Bad Parts

Ruby and Rails: The Bad Parts

Volodymyr Melnyk

September 19, 2015
Tweet

More Decks by Volodymyr Melnyk

Other Decks in Programming

Transcript

  1. 1 minute of self- advertisement • Software craftsman with ~7

    experience (~5y experience in Ruby). • Atheist • Libertarian and anarcho- capitalist. • Scientist and skeptic. • A man with business / economics mind.
  2. Ruby’s Good Parts • It has high abilities in meta-

    programming • It’s good for Domain-Specific Languages • It allows to write prototypes very fast • It has a huge ecosystem with tons of gems and development tools.
  3. Ruby isn’t Static- typed Ruby has no typed signatures. This

    means that you need to do all type checks manually. x = 5 y = “string" lambda = ->{ x + y } # you will not get an # error until execution # next code line: lambda.call
  4. Ruby has an unclear concept of Module In Ruby a

    module is a class which can’t have instances and can be included into another class or another class can be extended with it.
  5. module InstanceMethods; end module ClassMethods; end class C include InstanceMethods

    extend ClassMethods end C.ancestors #=> [C, InstanceMethods, Object, Kernel, BasicObject] C.singleton_class.ancestors #=> [ # #<Class:C>, ClassMethods, #<Class:Object>, # <Class:BasicObject>, Class, Module, Object, Kernel, # BasicObject # ]
  6. Proc & Lambda In Ruby we have a Proc class

    and two types of its instances (it’s strange), but actually we need only one of them (as for me, I prefer lambdas).
  7. p1 = Proc.new { |a, b| a + b }

    p2 = Proc.new { |a, b| return a + b } l1 = ->(a, b) { a + b } l2 = ->(a, b) { return a + b } p1.class # Proc l1.class # Proc p1.lambda? # false l1.lambda? # true def mtd(pobj, a, b = nil) args = b ? [a, b] : [a] puts "method code 1" puts pobj.call *args puts "method code 2" end
  8. mtd p1, 1
 # method code 1
 # TypeError: nil

    can't be coerced into Fixnum mtd p1, 1, 3
 # method code 1
 # 4
 # method code 2 mtd p2, 1
 # method code 1
 # TypeError: nil can't be coerced into Fixnum mtd p2, 1, 3
 # method code 1
 # LocalJumpError: unexpected return
  9. mtd l1, 1
 # method code 1
 # ArgumentError: wrong

    number of arguments (1 for 2) mtd l1, 1, 3
 # method code 1
 # 4
 # method code 2 mtd l2, 1
 # method code 1
 # ArgumentError: wrong number of arguments (1 for 2) mtd l2, 1, 3
 # method code 1
 # 4
 # method code 2
  10. Ruby Standard Library is a ghetto Ruby Standard Library has

    a huge number of low-quality and useless libraries.
  11. Abbrev require 'abbrev' %w{ car cone }.abbrev #=> {
 “car”

    => "car",
 “ca" => "car",
 “cone" => "cone",
 “con” => "cone",
 “co” => "cone"
 }
  12. Delegator class SimpleDelegator < Delegator
 def initialize obj
 super
 @delegate_sd_obj

    = obj
 end def __getobj__
 @delegate_sd_obj
 end def __setobj__ obj
 @delegate_sd_obj = obj
 end
 end
  13. Alternate syntax Ruby has a lot of alternate syntaxes for

    different things. Because of that Ruby code can look very differently.
  14. {…} vs do … end
 ->() {…}, -> {}, lambda

    {|| }, lambda {} def mtd(a, b), def mtd a, b 
 0..10, 0…10
 %w{}, %w[], %w(), %w//, %w``, %w"", %w||
 %w[], %W[], %i[], %i[]
 //, %r//, %s[]
 '', ""

  15. && versus and n = 1 && n + 1

    # NoMethodError: undefined # method `+' for nil n = 1 #=> 1 n = 2 && n + 1 #=> 2 n = 2 && n + 1 #=> 3 n = 2 && n + 1 #=> 4 n = 2 && n + 1 #=> 5 n #=> 5 n = 1 and n + 1 #=> 2 n = 1 #=> 1 n = 2 and n + 1 #=> 3 n = 2 and n + 1 #=> 3 n = 2 and n + 1 #=> 3 n = 2 and n + 1 #=> 3 n #=> 2
  16. || versus or n = 1
 n = nil ||

    n + 2 #=> 3 n = 1
 n = nil or n + 2
 # NoMethodError: undefined
 # method `+' for nil
  17. Boolean Algebra Fail If && and || are boolean operators,

    why their in/out arguments aren’t booleans? true && "string" #=> "string" 1 || nil #=> 1
  18. nil I call it my billion-dollar mistake. – Tony Hoare,

    inventor of ALGOL nil is • more code • checks • special cases • error handling
 • more specs / tests
 • more errors
  19. Polymorphic associations At first glance polymorphic associations look pretty well,

    but their usage result in a lot of problems when business logic is growing.
  20. # Article
 has_many :comments, as: :commentable # Photo
 has_many :comments,

    as: :commentable # Comment
 belongs_to :commentable, polymorphic: true Comment.joins(:commentable) #Exception
  21. Good schema • Article has one ArticleCommentable • ArticleCommentable belongs

    to Commentable • Commentable has many Comments • Comment belongs to Commentable
  22. Leak of abstraction ActiveRecord forces us to use implementation details

    in business logic (associations, queries, joins, etc.) while what we need is to separate them from each other.
  23. Solution: DDD Use Aggregate pattern to define associations and dependencies.

    Use Aggregate’s Root Entity to request associated entities.
  24. Single Table Inheritance STI is the way to make troubles

    instead of relations (tables) and the superclass makes no sense in most cases.
  25. Subclasse’s logic differences from its superclass, so you can’t use

    superclass to get a superset of all records in many cases. class Article < Publication
 has_many :comments
 
 # You can’t do this:
 Publication.order(published_at: :desc)
 .includes(:comments)
 .where(comments: {approved: true})
  26. More problems resulted by STI • We can’t use DB-layer

    validation and constraints (uniqueness and NOT NULL, for example). • We need an additional “type” column for metadata, but AR models don’t protect it as a metadata-column. • Totally misused cases of STI, like states, statuses, roles (something changeable). class ProcessedOrder < Order
 end
 
 class Admin < User
 end
  27. Good scheme • Article shouldn’t be inherited from Publication, but

    Article (Aggregate) is a composition over Publication and other entities. • ProcessedOrder shouldn’t be inherited from Order, but Order (Aggregate) is a composition over OrderItems, Delivery, Payment, etc. • Admin shouldn’t be inherited from User, but Admin (Aggregate) is a composition over User and other related entities.
  28. ActiveRecord is a Golden Hammer ActiveRecord as a pattern became

    a golden hammer. It is good for some limited purposes, but we should not use it in every case.
  29. ActiveRecord is good for mapping a tuple to an object,

    but bad for everything else. The most bestest alts: • Vanilla SQL and PORO • DataMapper
  30. ActiveRecord’s poor architecture What AR models are responsible for? •

    Querying • Persistency • Associations • Validation • Business logic • Triggering Isn’t that too much?
  31. Unit tests are a dream Rails developers don’t write unit-

    tests for models, because model has a lot of responsibilities. 
 
 Here mock. There stub. There one more mock. Mock, mock, mock, … Stub. validate :title, presence: true has_many :comments, dependent def add_comment(attrs)
 comments.create attrs
 end before :create, :assign_slug
  32. Pseudo (surrogate) keys ActiveRecord and Rails force you to use

    pseudo-keys instead of natural keys. In most cases you have no ID or anything of the kind. Your records should be identified with a natural key even if it could be composite.