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

Convenience vs Simplicity

Convenience vs Simplicity

Avoiding complexity is one of the greatest goals in programming. The tools we use, libraries and frameworks, must be helping us in achieving that goal. There must be a reasonable balance between convenience and simplicity as growing complexity is the price we pay for that convenience.

This talk is about seeking simplicity when dealing with data and behavior showing alternative approaches to object relational mapping and persistence concerns.

Piotr Solnica

June 27, 2014
Tweet

More Decks by Piotr Solnica

Other Decks in Programming

Transcript

  1. # 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])
  2. • Input data coercion • Setting default values • Input

    data validation • Interaction with the database
  3. • Business logic hidden in life-cycle hooks • Handling nested-data

    structures (via accepts_nested_attributes_for) • Data normalization through custom attribute writers • …
  4. # 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
  5. 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
  6. “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”
  7. require "axiom" users = Axiom::Relation.new([ [:user_id, Integer], [:name, String] ])

    ! tasks = Axiom::Relation.new([ [:user_id, Integer], [:title, String] ])
  8. 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"}]
  9. “Data Independence - a clear separation is enforced between the

    logical data and its physical representation” “Out of the Tar Pit” Ben Moseley, Peter Marks
  10. 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
  11. 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
  12. # 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 })
  13. # 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
  14. 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
  15. 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)
  16. # 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
  17. # 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")
  18. 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