Slide 1

Slide 1 text

HELLO!

Slide 2

Slide 2 text

I’m Piotr Solnica :)

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Ruby developer github.com/solnic @_solnic_

Slide 6

Slide 6 text

powow.no gitorious.org evofitness.no

Slide 7

Slide 7 text

Convenience vs Simplicity Piotr Solnica / RedDotRubyConf / Singapore 2014

Slide 8

Slide 8 text

Convenience

Slide 9

Slide 9 text

www.oxforddictionaries.com/definition/english/convenience “The state of being able to proceed with something without difficulty”

Slide 10

Slide 10 text

Look at all the things I’M NOT doing!

Slide 11

Slide 11 text

Simplicity

Slide 12

Slide 12 text

www.oxforddictionaries.com/definition/english/simplicity “The quality or condition of being easy to understand or do”

Slide 13

Slide 13 text

Separation of Concerns

Slide 14

Slide 14 text

# simple! class User < ActiveRecord::Base # all the things we’re not doing end ! # still very simple! user = User.find_by(name: "Jade") user.mutate_it_somehow user.save # again simple! User.create(params[:user])

Slide 15

Slide 15 text

It’s all so simple, right? :)

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

It is all CONVENIENT (but not simple)

Slide 18

Slide 18 text

User.create(params[:user])

Slide 19

Slide 19 text

• Input data coercion • Setting default values • Input data validation • Interaction with the database

Slide 20

Slide 20 text

• Business logic hidden in life-cycle hooks • Handling nested-data structures (via accepts_nested_attributes_for) • Data normalization through custom attribute writers • …

Slide 21

Slide 21 text

Separation of Concerns

Slide 22

Slide 22 text

tuple = params_processor.call(params[:user]) errors = validator.call(tuple) repository.insert(tuple) if errors.empty? input data coercion data validation persistence

Slide 23

Slide 23 text

def create(params) tuple = params_processor.call(params[:user]) errors = validator.call(tuple) repository.insert(tuple) if errors.empty? end

Slide 24

Slide 24 text

Data & Behavior

Slide 25

Slide 25 text

# CONVENIENCE ! class User < ActiveRecord::Base ! def full_name "#{first_name} #{last_name}" end ! end

Slide 26

Slide 26 text

# SIMPLICITY ! ! data = { first_name: "Jade", last_name: "Doe", email: "[email protected]" } class Presenter def initialize(data) @data = data end def full_name "#{@data[:first_name]} #{@data[:last_name]}" end end

Slide 27

Slide 27 text

Presenter.new(data).full_name => "Jade Doe"

Slide 28

Slide 28 text

Convenience

Slide 29

Slide 29 text

Simplicity

Slide 30

Slide 30 text

Focus on what’s essential

Slide 31

Slide 31 text

Try to be minimalistic

Slide 32

Slide 32 text

Immutability

Slide 33

Slide 33 text

presenter = Presenter.new(data) data[:first_name].upcase! presenter.full_name # JADE Doe

Slide 34

Slide 34 text

Oh You Mutable State!

Slide 35

Slide 35 text

require "ice_nine" IceNine.deep_freeze(data) presenter = Presenter.new(data) data[:first_name].upcase! # BOOM! RuntimeError: can't modify frozen String

Slide 36

Slide 36 text

Adamantium github.com/dkubb/adamantium

Slide 37

Slide 37 text

class Money include Adamantium attr_reader :value, :currency def initialize(data) @value, @currency = data.values_at(:value, :currency) end end money = Money.new(value: 123.12, currency: "pln") money.currency.upcase! # BOOM! RuntimeError: can't modify frozen String

Slide 38

Slide 38 text

Relational Model

Slide 39

Slide 39 text

“Out of the Tar Pit” Ben Moseley, Peter Marks “The relational model has — despite its origins — nothing intrinsically to do with databases. Rather it is an elegant approach to structuring data, a means for manipulating such data, and a mechanism for maintaining integrity and consistency of state”

Slide 40

Slide 40 text

Structure

Slide 41

Slide 41 text

require "axiom" users = Axiom::Relation.new([ [:user_id, Integer], [:name, String] ]) ! tasks = Axiom::Relation.new([ [:user_id, Integer], [:title, String] ])

Slide 42

Slide 42 text

Manipulation

Slide 43

Slide 43 text

users = users.insert([[1, "Jade"]]) tasks = tasks.insert([[1, "Task One”]]) users_tasks = proc { |user_id| users. join(tasks). rename(name: :user_name). restrict(user_id: user_id). project([:user_name, :title]) } users_tasks.call(1).to_a # [{:user_name=>"Jade", :title=>"Task One"}]

Slide 44

Slide 44 text

users.join(tasks).insert([[1, "Jade", "Jade's Task"]]).to_a # => [{:user_id=>1, :name=>"Jade", :title=>"Jade's Task"}]

Slide 45

Slide 45 text

“Data Independence - a clear separation is enforced between the logical data and its physical representation” “Out of the Tar Pit” Ben Moseley, Peter Marks

Slide 46

Slide 46 text

Relations as 1st class citizens

Slide 47

Slide 47 text

class User < ActiveRecord::Base def self.active where(active: true) end end # returns a relation with *all the data* User.all # returns a relation with *a subset* of all the data User.active

Slide 48

Slide 48 text

Let’s put relations in front

Slide 49

Slide 49 text

class RelationRegistry def initialize @registry = {} # internal base relation, not exposed to the outside world @registry[:users] = Axiom::Relation.new( [[:name, String], [:active, Boolean]] ) end # derived, public relation with active users def active_users @registry[:users].restrict(active: true) end end relations = RelationRegistry.new relations.active_users

Slide 50

Slide 50 text

Composability & Encapsulation

Slide 51

Slide 51 text

# Find all users without tasks # we need to resort to a hand-written partial SQL User. joins( "left outer join tasks on tasks.user_id = users.user_id” ). where(tasks: { id: nil }) # won't work as it produces an inner join User.joins(:tasks).where(:tasks => { :id => nil })

Slide 52

Slide 52 text

Composing queries gives very little confidence

Slide 53

Slide 53 text

Rather than composing queries, let’s compose relations

Slide 54

Slide 54 text

# Find all users without tasks users_with_tasks = users.join(tasks).project([:user_id, :name]) # difference relational operator users_with_no_tasks = users.difference(users_with_tasks) # ...or just users_with_no_tasks = users - users_with_tasks

Slide 55

Slide 55 text

Ruby Object Mapper github.com/rom-rb

Slide 56

Slide 56 text

Relations (aka data) Mapping to objects Simplicity

Slide 57

Slide 57 text

Logical Schema

Slide 58

Slide 58 text

rom = ROM::Environment.setup(:memory) rom.schema do ! relation(:users, internal: true) do attribute :id, Integer, rename: :user_id attribute :name, String key :user_id end relation(:tasks) do attribute :user_id, Integer attribute :title, String key :user_id, :title end ! end that’s a base, internal relation

Slide 59

Slide 59 text

Public “external” relations

Slide 60

Slide 60 text

rom.schema do ! relation(:users_tasks) do |user_id| users. join(tasks). rename(name: :user_name). restrict(user_id: user_id). project([:user_id, :user_name, :title]) end ! end # get all user's tasks rom.users_tasks(1)

Slide 61

Slide 61 text

Mapping “data” to objects

Slide 62

Slide 62 text

# basic attribute mapping ! rom.mapping do relation(:users_tasks) do map :user_id, :user_name, :title end end

Slide 63

Slide 63 text

# mapping embedded values ! rom.mapping do relation(:users_tasks) do map :title wrap :user do map :id, from: :user_id map :name, from: user_name end end end

Slide 64

Slide 64 text

Simplicity!

Slide 65

Slide 65 text

# simple access to all relations defined in the schema rom.users_tasks(1).to_a # simple access to mapped objects rom.load(rom.users_tasks(1)) # simple inserts using *just data* rom.tasks.insert( user_id: 1, user_name: “Jade", title: "Jade's First Task” ) # simple updates using *just data* rom.users_tasks(1). restrict(id: 1). update(title: "Jade's Awesome Task")

Slide 66

Slide 66 text

Embrace simplicity

Slide 67

Slide 67 text

Convenience can come later

Slide 68

Slide 68 text

Thank You! <3

Slide 69

Slide 69 text

Resources • Out of the tar pit paper • Relations as the first class citizen blog post • Ruby Relational Algebra libraries • www.try-alf.org • github.com/dkubb/axiom • Ruby Object Mapper github.com/rom-rb/rom

Slide 70

Slide 70 text

Photo credits • https://www.flickr.com/photos/ntrinkhaus/12748578494 • https://www.flickr.com/photos/ntrinkhaus/12748578494 • https://www.flickr.com/photos/scottlaird/31367273 • https://www.flickr.com/photos/nanpalmero/ 14373506594 • https://www.flickr.com/photos/georgiesharp/ 8547779958/