-
Notifications
You must be signed in to change notification settings - Fork 1
Home
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.
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
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';
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)
}
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.
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)
}
}
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)
}
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
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) ]