[~/sinatra/sinatra-datamapper-jump-start(master)]$ pwd -P /Users/casiano/local/src/ruby/sinatra/sinatra-datamapper-jump-start
[~/sinatra/sinatra-datamapper-jump-start(master)]$ git remote -v origin git@github.com:crguezl/sinatra-datamapper-jump-start.git (fetch) origin git@github.com:crguezl/sinatra-datamapper-jump-start.git (push)
[~/sinatra/sinatra-datamapper-jump-start(master)]$ cat song.rb require 'dm-core' require 'dm-migrations' class Song include DataMapper::Resource property :id, Serial property :title, String property :lyrics, Text property :length, Integer endThe
Song
model is going to need to be persistent,
so we'll include DataMapper::Resource
.
The convention with model names is to use the singular, not plural version... but that's just the convention, we can do whatever we want.
configure do enable :sessions set :username, 'frank' set :password, 'sinatra' end
DataMapper.finalizeThis method performs the necessary steps to finalize DataMapper for the current repository. It should be called after loading all models and plugins. It ensures foreign key properties and anonymous join models are created. These are otherwise lazily declared, which can lead to unexpected errors. It also performs basic validity checking of the DataMapper models.
get '/songs' do @songs = Song.all slim :songs end get '/songs/new' do halt(401,'Not Authorized') unless session[:admin] @song = Song.new slim :new_song end get '/songs/:id' do @song = Song.get(params[:id]) slim :show_song end get '/songs/:id/edit' do @song = Song.get(params[:id]) slim :edit_song end
If you want to create a new resource with some given attributes and
then save it all in one go, you can use the #create
method:
post '/songs' do song = Song.create(params[:song]) redirect to("/songs/#{song.id}") end put '/songs/:id' do song = Song.get(params[:id]) song.update(params[:song]) redirect to("/songs/#{song.id}") end delete '/songs/:id' do Song.get(params[:id]).destroy redirect to('/songs') end
[~/sinatra/sinatra-datamapper-jump-start(master)]$ pry [1] pry(main)> require 'sinatra' => true [2] pry(main)> require './song' => true
We must specify our database connection.
We need to make sure to do this before you use our models, i.e. before we actually start accessing the database.
# If you want the logs displayed you have to do this before the call to setup DataMapper::Logger.new($stdout, :debug) # An in-memory Sqlite3 connection: DataMapper.setup(:default, 'sqlite::memory:') # A Sqlite3 connection to a persistent database DataMapper.setup(:default, 'sqlite:///path/to/project.db') # A MySQL connection: DataMapper.setup(:default, 'mysql://user:password@hostname/database') # A Postgres connection: DataMapper.setup(:default, 'postgres://user:password@hostname/database') Note: that currently you must setup a :default repository to work with DataMapper (and to be able to use additional differently named repositories). This might change in the future.
In our case:
[4] pry(main)> pry(main)> DataMapper.setup(:default,'sqlite:development.db')
DataMapper sports a concept called a context which encapsulates the
data-store context in which you want operations to occur. For example,
when you setup a connection you are defining a
context known as :default
DataMapper.setup(:default, 'mysql://localhost/dm_core_test')If you supply another context name, you will now have 2 database contexts with their own unique loggers, connection pool, identity map....one default context and one named context.
DataMapper.setup(:external, 'mysql://someother_host/dm_core_test')To use one context rather than another, simply wrap your code block inside a
repository
call. It will return whatever your block of code returns.
DataMapper.repository(:external) { Person.first } # hits up your :external database and retrieves the first PersonThis will use your connection to the
:external
data-store and the first
Person
it finds. Later, when you call .save
on that person, it'll get
saved back to the :external
data-store;
An
object is aware of what
context it came from and should be saved back to.
=> #<DataMapper::Adapters::SqliteAdapter:0x007fad2c0f6a50 @field_naming_convention=DataMapper::NamingConventions::Field::Underscored, @name=:default, @normalized_uri= #<DataObjects::URI:0x007fad2c0f62a8 @fragment="{Dir.pwd}/development.db", @host="", @password=nil, @path=nil, @port=nil, @query= {"scheme"=>"sqlite3", "user"=>nil, "password"=>nil, "host"=>"", "port"=>nil, "query"=>nil, "fragment"=>"{Dir.pwd}/development.db", "adapter"=>"sqlite3", "path"=>nil}, @relative=nil, @scheme="sqlite3", @subscheme=nil, @user=nil>, @options= {"scheme"=>"sqlite3", "user"=>nil, "password"=>nil, "host"=>"", "port"=>nil, "query"=>nil, "fragment"=>"{Dir.pwd}/development.db", "adapter"=>"sqlite3", "path"=>nil}, @resource_naming_convention= DataMapper::NamingConventions::Resource::UnderscoredAndPluralized>
[4] pry(main)> DataMapper.auto_migrate!
CREATE
statements (DROP
ing
the table first, if it exists) to define each storage according to
their properties.
auto_migrate!
has been run, the
database should be in a pristine state.
DataMapper.auto_upgrade!
CREATE
new tables, and add columns to existing tables.
NOT NULL
constraint)
and it doesn't drop any columns.
Song.auto_migrate!
)
[5] pry(main)> song = Song.new => #<Song @id=nil @title=nil @lyrics=nil @length=nil @released_on=nil> [6] pry(main)> song.save => true [7] pry(main)> song => #<Song @id=1 @title=<not loaded> @lyrics=<not loaded> @length=<not loaded> @released_on=<not loaded>> [8] pry(main)> song.title = "My Way" => "My Way" [9] pry(main)> song.lyrics => nil [10] pry(main)> song.lyrics = "And now, the end is near ..." => "And now, the end is near ..." [11] pry(main)> song.length = 435 => 435 [42] pry(main)> song.save => true [43] pry(main)> song => #<Song @id=1 @title="My Way" @lyrics="And now, the end is near ..." @length=435 @released_on=nil>
#create
method.
[28] pry(main)> Song.create(title: "Come fly with me", lyrics: "Come fly with me, let's fly, let's fly away ...", length: 199) => #<Song @id=2 @title="Come fly with me" @lyrics="Come fly with me, let's fly, let's fly away ..." @length=199 @released_on=<not loaded>>
#create
will return the newly created DataMapper::Resource
#saved
? on the returned resource
true
if the resource was successfully persisted, or false
otherwise
#first_or_create
.
s = Song.first_or_create(:title => 'New York, New York')This will first try to find a
Song
instance with the given title
, and if it fails to do so, it will return a newly created Song
with that title
.
If the criteria you want to use to query for the resource differ from the attributes you need for creating a new resource, you can pass the attributes for creating a new resource as the second parameter to #first_or_create
, also in the form of a #Hash
.
s = Song.first_or_create({ :title => 'My Way' }, { :lyrics => '... the end is not near' })
This will search for a Song
named 'My Way
' and if it
can't find one, it will return a new Song
instance with its
name set to 'My Way
' and the lyrics
set to
.. the end is not near
s = Song.first_or_create({ :title => 'My Way' }, { :title => 'My Way Home', :lyrics => '... the end is not near' })This will search for a
Song
named
'My Way'
but if it fails to find one,
it will return a Song
instance with its
title set to 'My Way Home'
and its
lyrics
set to '... the end is not near'
.
Podemos abrir la base de datos con el gestor de base de datos y comprobar que las tablas y los datos están allí:
[~/sinatra/sinatra-datamapper-jump-start(master)]$ sqlite3 development.db SQLite version 3.7.11 2012-03-20 11:35:50 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> .schema CREATE TABLE "songs" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "title" VARCHAR(50), "lyrics" TEXT, "length" INTEGER, "released_on" TIMESTAMP); sqlite> select * from songs; 1|My Way|And now, the end is near ...|435| 2|Come fly with me|Come fly with me, let's fly, let's fly away ...|199| sqlite>
song = Song.get(1) # get the song with primary key of 1. song = Song.get!(1) # Or get! if you want an ObjectNotFoundError on failure song = Song.first(:title => 'Girl') # first matching record with the title 'Girl' song = Song.last(:title => 'Girl') # last matching record with the title 'Girl' songs = Song.all # all songs
[29] pry(main)> Song.count => 2 [30] pry(main)> Song.all => [#<Song @id=1 @title=nil @lyrics=<not loaded> @length=nil @released_on=nil>, #<Song @id=2 @title="Come fly with me" @lyrics=<not loaded> @length=199 @released_on=nil>] [31] pry(main)> Song.get(1) => #<Song @id=1 @title=nil @lyrics=<not loaded> @length=nil @released_on=nil> [32] pry(main)> Song.first => #<Song @id=1 @title=nil @lyrics=<not loaded> @length=nil @released_on=nil> [33] pry(main)> Song.last => #<Song @id=2 @title="Come fly with me" @lyrics=<not loaded> @length=199 @released_on=nil> [35] pry(main)> x = Song.first(title: 'Come fly with me') => #<Song @id=2 @title="Come fly with me" @lyrics=<not loaded> @length=199 @released_on=nil>
[44] pry(main)> y = Song.first(title: 'My Way') => #<Song @id=1 @title="My Way" @lyrics=<not loaded> @length=435 @released_on=nil> [45] pry(main)> y.length => 435 [46] pry(main)> y.update(length: 275) => true
En Sqlite3:
sqlite> select * from songs; 1|My Way|And now, the end is near ...|275| 2|Come fly with me|Come fly with me, let's fly, let's fly away ...|199|
[47] pry(main)> Song.create(title: "One less lonely girl") => #<Song @id=3 @title="One less lonely girl" @lyrics=<not loaded> @length=<not loaded> @released_on=<not loaded>> [48] pry(main)> Song.last.destroy => true [49] pry(main)> Song.all => [#<Song @id=1 @title="My Way" @lyrics=<not loaded> @length=275 @released_on=nil>, #<Song @id=2 @title="Come fly with me" @lyrics=<not loaded> @length=199 @released_on=nil>]
The examples above are pretty simple, but you might be wondering how we can specify conditions beyond equality without resorting to SQL. Well, thanks to some clever additions to the Symbol class, it's easy!
exhibitions = Exhibition.all(:run_time.gt => 2, :run_time.lt => 5) # => SQL conditions: 'run_time > 1 AND run_time < 5'Valid symbol operators for the conditions are:
gt # greater than lt # less than gte # greater than or equal lte # less than or equal not # not equal eql # equal like # likeVeamos un ejemplo de uso con nuestra clase
Song
:
[31] pry(main)> Song.all.each do |s| [31] pry(main)* s.update(length: rand(400)) [31] pry(main)* end => [#<Song @id=1 @title="My Way" @lyrics=<not loaded> @length=122 @released_on=nil>, #<Song @id=2 @title="Come fly with me" @lyrics=<not loaded> @length=105 @released_on=nil>, #<Song @id=4 @title="Girl from Ipanema" @lyrics=<not loaded> @length=389 @released_on=nil>] [32] pry(main)> long = Song.all(:length.gt => 120) => [#<Song @id=1 @title="My Way" @lyrics=<not loaded> @length=122 @released_on=nil>, #<Song @id=4 @title="Girl from Ipanema" @lyrics=<not loaded> @length=389 @released_on=nil>]
[40] pry(main)> songs = repository(:default).adapter.select('SELECT title FROM songs WHERE length >= 110') => ["My Way", "Girl from Ipanema"]Note that this will not return
Song
objects,
rather the raw data straight from the database
[~/sinatra/sinatra-datamapper-jump-start(master)]$ cat main.rb require 'sinatra' require 'slim' require 'sass' require './song' configure do enable :sessions set :username, 'frank' set :password, 'sinatra' end configure :development do DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/development.db") end configure :production do DataMapper.setup(:default, ENV['DATABASE_URL']) end get('/styles.css'){ scss :styles } get '/' do slim :home end get '/about' do @title = "All About This Website" slim :about end get '/contact' do slim :contact end not_found do slim :not_found end get '/login' do slim :login end post '/login' do if params[:username] == settings.username && params[:password] == settings.password session[:admin] = true redirect to('/songs') else slim :login end end get '/logout' do session.clear redirect to('/login') end