LoreTutorial
From Cubawiki
Defining database models
A model is a class providing a database type consisting of one or more tables.
Let's use the following tables for our example:
| public.vehicle Abstract base class for all vehicles | ||
| vehicle_id | integer | primary key |
| name | character varying (100) | |
| owner_id | integer | references public.owner.owner_id |
| manufacturer_id | integer | references public.manufacturer.manufacturer_id |
| public.car Specialisation of vehicle | ||
| car_id | integer | primary key |
| vehicle_id | integer | references public.vehicle |
| public.owner | ||
| owner_id | integer | primary key |
| name | character varying (100) | |
| public.manufacturer | ||
| manufacturer_id | integer | primary key |
| name | character varying (100) | |
| public.driver | ||
| driver_id | integer | primary key |
| name | character varying (100) | |
| public.vehicle_driver Maps n drivers to n vehicles | ||
| v_id | integer | references public.vehicle.vehicle_id |
| d_id | integer | references public.driver.driver_id |
Our model would look like this:
name_format = /a-ZA-Z0-9_-/ class Manufacturer < Lore::Model table :manufacturer primary_key :manufacturer_id, manufacturer_id_seq validates :name, :maxlength => 100, :format => name_format end class Vehicle < Lore::Model table :vehicle, :public # optional. When no schema is given, :public is used primary_key :vehicle_id, :vehicle_id_seq validates :name, :maxlength => 100, :format => name_format has_a Manufacturer, :manufacturer_id has_many Driver, :through => :vehicle_driver def owned_by(owner_instance) end end class Car < Vehicle # inheritance from Vehicle necessary for custom methods like 'owned_by' only table :car primary_key :car_id is_a Vehicle, :vehicle_id end
class Driver < Lore::Model table :driver, :public primary_key :driver_id has_many Vehicle, :through => :vehicle_driver end
class Vehicle_Driver < Lore::Model
table :vehicle_driver, :public
primary_key :v_id, :d_id
has_many Driver, :d_id
has_many Vehicle, :v_id
end
Learn more about Lore::Model
Creating and modifying records
Creating a new record is quite easy. Just pass attributes to klass method create of a model:
m = Manufacturer.create( :name => 'BMW' )
o = Owner.create( :name => 'Mathilda Bang' )
c = Car.create( :name => 'BMW318i',
:owner_id => o.owner_id,
:manufacturer_id => m.manufacturer_id )
# Let's validate that:
assert_equal m, c.manufacturer_entity
assert_equal o, c.owner_entity
In this case, there are two has_a relations involved, so we also can use model instances as well:
Car.create( :name => 'BMW318i',
:owner => o, # <--- here
:manufacturer => m ) # <--- and here
Selecting records from the database
After reading this section, be sure to read about Lore's prepared statements , which increase performance and security when loading instances from DB.
Ok, now that our database is filled with values, we want to get our information back.
Selecting one model instance by its primary key values is provided by model klass method .load(pkey_values):
car_obj = Car.load(:car_id => 4)
Attribute values of a model instance are accessed by automatically defined instance methods having the same name as the attribte:
puts car_obj.car_id puts car_obj.name
Related entities defined with has_many and has_a in the Model klass can be retreived by calling automatically defined model instance methods with suffix "_entities" for 1:n and "_entity" for 1:1 relations:
Driver.load(:driver_id => 4).vehicle_entities.each { |vehicle|
puts vehicle.manufacturer_entity.name
To select more than one instance, let's use Lore's language directly for starters. This always works the same: Call a model's select method.
As you already know from SQL, a DB entity is returned if the given query statements evaluate to true for it. So the most simple select is: Passing true.
all_cars = Car.select { |c|
c.where(true)
}
This returns all cars. But thanks to query shortcuts you also could state: all_cars = Cars.all.entities. Ok, that's a boring example, i know. Normally we only want entities matching more specific criteria.
The block variable c is a clause parser instance. It's most frequently used methods are:
- #where( <clause> )
- #limit( <integer|range> )
- #order_by( <:attribute_name>, <:asc|:desc> )
- #group_by( <:attribute_name> )
Not that frequent, but handy sometimes:
- #join(<Other_Model>).on(<clause>)
- #join(<Other_Model>).using(<:common_attribute>)
Let's see what we can do with it:
my_cars = Car.select { |c|
c.where c.name.like('%BMW%')
c.order_by :name, :asc
c.limit(10..20)
}
Not bad. With query shortcuts, this could look like this:
my_cars = Car.find(10..20).with(Car.name.like '%BMW%').ordered_by(:name, :asc).entities
There also are rather exotic things you can do with Lore's query language. Let's assume we want to join Car with a model klass that regularly has no relationship to it. Like ... umm ... pie. I like pie.
my_pie_car = Car.select { |c|
c.join(Pie).on(Car.car_id == Pie.pie_id) { |pc|
pc.where(pc.name.like '�rry%')
}
}
To add some complications: What if Car and Pie both have an attribute name?
c.name just returns 'name', like in WHERE name LIKE '�rry%'. We want WHERE public.car.name LIKE '�rry%'. So we have to tell Lore explicitly which one to use:
my_pie_car = Car.select { |c|
c.join(Pie).on(Car.car_id == Pie.pie_id) { |pc|
pc.where(Car.name.like '�rry%') # <-- see?
}
}
How many cars are there at all? Of course there's Cars.all.entities.length, but this is a bad thing to do, as we know. We want something like
SELECT count(car_id) FROM public.car
So let's use value_of. This returns a helper object providing methods like sum, max, count and whichever aggregate function Postgres provides. In our case we need:
$(irb) Car.value_of.count(:car_id) $(irb) -> 5
Be sure to read about Lore's prepared statements , which increase performance and security when loading instances from DB.
Learn more about Lore::Clause
