Share
Embed
Ruby and Rails: The Bad Parts
Volodymyr Melnyk
September 19, 2015
Transcript
40 slides
-
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. -
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. -
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 -
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. -
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 # ] -
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). -
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 -
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 -
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 -
Ruby Standard Library is a ghetto Ruby Standard Library has
a huge number of low-quality and useless libraries. -
Abbrev require 'abbrev' %w{ car cone }.abbrev #=> { “car”
=> "car", “ca" => "car", “cone" => "cone", “con” => "cone", “co” => "cone" } -
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 -
Alternate syntax Ruby has a lot of alternate syntaxes for
different things. Because of that Ruby code can look very differently. -
{…} 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[] '', "" -
&& 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 -
|| versus or n = 1 n = nil ||
n + 2 #=> 3 n = 1 n = nil or n + 2 # NoMethodError: undefined # method `+' for nil -
Boolean Algebra Fail If && and || are boolean operators,
why their in/out arguments aren’t booleans? true && "string" #=> "string" 1 || nil #=> 1 -
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 -
Polymorphic associations At first glance polymorphic associations look pretty well,
but their usage result in a lot of problems when business logic is growing. -
# Article has_many :comments, as: :commentable # Photo has_many :comments,
as: :commentable # Comment belongs_to :commentable, polymorphic: true Comment.joins(:commentable) #Exception -
Good schema • Article has one ArticleCommentable • ArticleCommentable belongs
to Commentable • Commentable has many Comments • Comment belongs to Commentable -
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. -
Solution: DDD Use Aggregate pattern to define associations and dependencies.
Use Aggregate’s Root Entity to request associated entities. -
Single Table Inheritance STI is the way to make troubles
instead of relations (tables) and the superclass makes no sense in most cases. -
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}) -
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 -
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. -
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. -
ActiveRecord is good for mapping a tuple to an object,
but bad for everything else. The most bestest alts: • Vanilla SQL and PORO • DataMapper -
ActiveRecord’s poor architecture What AR models are responsible for? •
Querying • Persistency • Associations • Validation • Business logic • Triggering Isn’t that too much? -
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 -
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. -
Follow me • Home page http://www.volodymyr-melnyk.com • IT Blog http://www.itrampage.com
• Me on Facebook https://www.facebook.com/egotraumatic