Skip to content
fuchsto edited this page Sep 13, 2010 · 10 revisions

Lore Cheatsheet

Connecting to a database


require('rubygems')
require('lore')

Lore.add_login_data('dbname' => [ 'dbuser', 'dbpass' ])
Lore::Context.enter :dbname

Examples are from Lore’s spec fixtures in lore/spec/fixtures.

Defining models


require('rubygems')
require('lore/model')

class Owner < Lore::Model
  table :owner, :public              # table and schema name
  primary_key :id, :owner_id_seq     # primary key name and its sequence, if present
end

class Vehicle < Lore::Model
  table :vehicle, :public
  primary_key :id, :vehicle_id_seq 

  expects :maxspeed                  # required attribute

  # Generates methods Vehicle#owner(), Vehicle#owner=o and Vehicle#set_owner(o)
  has_a Owner, :owner_id

  # Validate attribute values before storing them
  validates :name, :format => /[a-zA-Z0-9_]+/, :length => (3..30)

  # Validation also accepts blocks
  validates :maxspeed  { |value|  value.to_i < 500 && value.to_i > 5 }

  # Filter field values before storing them in DB
  add_input_filter(:name) { |value| 
      value.gsub(' ','_')
  }
  # Filter field values after loading them from DB
  add_output_filter(:name) { |value|
      value.gsub('_',' ')
  }
end

class Motorized_Vehicle < Vehicle
   table :motorized, :public
   primary_key :id, :motorized_id_seq

   # Automatically eager-joins Motor, much like is_a, but 
   # Does not delete its Motor instance recursively or change 
   # its values. From the outside, it looks like inheritance, but it 
   # behaves like has_a
   aggregates :Motor, :motor_id
end

class Car < Motorized_Vehicle
  table :car, :public
  primary_key :id, :car_id_seq

  # Define this model as derived from Vehicle using foreign key "vehicle_id". 
  # Can be used more than once, implementing multiple table inheritance. 
  is_a Motorized_Vehicle, :motorized_id 
end


Loading Model instances


v = Car.load(:id => 123)
# same as
v = Car.get(123)
# same as 
v = Car.select { |v| v.where(Car.id == 123) }.first

SQL performed looks like this:


select * 
  from vehicle 
  join motorized on (motorized.vehicle_id = vehicle.id) 
  join car on (car.motorized_id = motorized.id) 
where car.id = '123'; 

Select options (order by, limit, offset, …)


Car.select { |c|
  c.where(c.num_seats >= 4)
  # return 10 results, starting with 5th result (offset)
  c.limit(10, 5)
  # Ordering. :asc is ascdending, :desc is descending: 
  c.order_by(:num_seats, :asc)
}

Selecting skalar values (max, sum, avg …)


Car.select_value('avg(num_doors)') { |c|
  c.where(c.description.like('%Limousine%'))
}.to_i

Using a query shortcut results in the same:


Car.value_of.avg(:num_doors).where(Car.description.like("%Limousine%")).to_i

Note that queries are regular objects. It won’t be passed to DB until a so-called kicker is used.


filter = Car.description.like("%Limousine%")
# Still no query performed
query  = Car.find(10).with(filter)

cars = query.entities  # Now it's passed to DB

Kicker methods are:

  • result (returns array of model instances)
  • entities (same as #result)
  • to_a (same as #result)
  • entity (first of entities)
  • to_i (selects single value)
  • to_s (selects single value)
  • values (returns array of single values)

There are many convenience methods for selects:


Car.find(10, 5).with(...)  # LIMIT 10, OFFSET 5
Car.find(:all).with(...)   # SELECT * 
Car.all_with(...)          # same as find(:all)
# JOIN car_settings USING (car_id)
Car.find(4).join(Car_Settings).using(:car_id).with(...)

and many more.

Select with custom joins


Car.select { |c|
  c.join(Car_Settings).on(Car.id == Car_Settings.car_id) { |cs|
     cs.where(cs.color.like("%red"))
     cs.order_by(:color, :asc)
  }
}

Updating Model instances


car = Car.get(123)

car.num_doors = 4
# same as
car[:num_doors] = 4
car.save
# same as
car.set_num_doors!(4)

Updating also works like a select block:


Car.update { |c|
  c.where((c.num_doors >= 4 & (c.num_seats >= 4) | 
          (c.name.like('%Limousine')))
  c.set(:description => 'Limousine')
}

Updating using a column reference works intuitively using known syntax.
Incrementing a field value looks like this:


Car.update { |c|
  c.where(c.name.like('%Limousine'))
  c.set(:num_doors => c.num_doors + 1)
}

Deleting Model instances


car = Car.get(3)
car.delete

In this case, this would be better of course:


Car.delete { |car| car.where(:car_id => 3) }

Deleting all entities (careful with this one):


Car.delete_all

Polymorphism

Defining polymorphic models

To use polymorphism, just add a line in a base model:


class Vehicle < Lore::Model
    # name of auxiliary field to store concrete model information in
    is_polymorphic :concrete_model  
end

Concrete models do not have to be adjusted, just derive them as usual:


class Car < Vehicle
    is_a Vehicle :vehicle_id
end
class Motorbike < Vehicle
     is_a Vehicle, :vehicle_id
end


There are two strategies for polymorphic queries: Lazy and eager.
In eager polymorphic queries, all possible concrete model tables are queried using a full outer joint. Attribute values are then distributed to concrete instances.
An eager polymorphic query returns concrete instances of the abstract model klass used to execute the query.


    Vehicle.select_polymorphic { |v|
        v.where(v.maxspeed.between(120,230))
    }.to_a
# --> [ Car(:maxspeed => 182), Motorbike(:maxspeed => 229), Car(:maxspeed => 201) ]

Lazy polymorphism is used after having queried abstract instances of a polymorphic model.
To do so, first query the abstract model as usual, without polymorphism:


    vehicles = Vehicle.select { |v|
        v.where(v.maxspeed.between(120,230))
    }.to_a
# --> [ Vehicle(:vehicle_id => 12), Vehicle(:vehicle_id => 39), Vehicle(:vehicle_id=> 81) ]

If the instances concrete representation is needed later, they can be resolved using Model#concrete_instance:


    vehicles = vehicles.map { |v| v.concrete_instance }
    p vehicles
    # --> [ Car(:maxspeed => 182), Motorbike(:maxspeed => 229), Car(:maxspeed => 201) ]