Hoy vamos a ver como se crean, modifican y eliminan registros en una tabla de una base de datos Mysql en Rails, obviamente también todo el proceso para tener creada la base de datos.
Lo primero será crear el proyecto de Rails indicando que queremos mysql como motor de bases de datos, eso se hacía con la opción -d y luego mysql.
$ rails new railsymysql -d mysql create create README.rdoc create Rakefile create config.ru create .gitignore [...] Use `bundle show [gemname]` to see where a bundled gem is installed. run bundle exec spring binstub --all * bin/rake: spring inserted * bin/rails: spring inserted
El proyecto se llamará railsymysql y con esto ya tenemos creada la estructura de ficheros necesaria.
Ahora lo primero será generar un modelo, al que vamos a llamar Usuario (los modelos se generan en singular en Rails)
~/railsmysql$ rails generate model Usuario Running via Spring preloader in process 465 invoke active_record create db/migrate/20161030125505_create_usuarios.rb create app/models/usuario.rb invoke test_unit create test/models/usuario_test.rb create test/fixtures/usuarios.yml
Al crear el modelo creamos el fichero de migración inicial para definir nuestra base de datos y definimos el modelo en si como ActiveRecord
Antes de nada como tenemos vamos a usar MySQL tendremos que crear la base de datos y configurar el fichero /config/database.yml
[...] default: &default adapter: mysql2 encoding: utf8 pool: 5 username: edu password: password socket: /var/run/mysqld/mysqld.sock development: <<: *default database: railsmysql_development [...]
En mi caso voy a usar de usuario edu y como contraseña password, así que configuramos esto en el mysql
mysql> CREATE DATABASE railsmysql_development; Query OK, 1 row affected (0,00 sec) mysql> GRANT ALL PRIVILEGES ON railsmysql_development.* TO "edu"@"localhost" IDENTIFIED BY "password"; Query OK, 0 rows affected, 1 warning (0,00 sec)
En este punto procedemos a crear una tabla usuarios (plural del modelo) con sus campos, en este caso db/migrate/20161030125505_create_usuarios.rb
class CreateUsuarios < ActiveRecord::Migration def change create_table :usuarios do |t| t.column "nombre", :string, :limit => 25, :null => false t.string "apellidos", :limit => 50 t.string "email", :limit => 40 t.timestamps null: false end end end
Y generaremos la migración
ubuntu@ronr:~/railsmysql$ rake db:migrate == 20161030125505 CreateUsuarios: migrating =================================== -- create_table(:usuarios) -> 0.0474s == 20161030125505 CreateUsuarios: migrated (0.0475s) ==========================
Y comprobamos en MySQL que se haya aplicado todo bien:
mysql> use railsmysql_development; Database changed mysql> show tables; +----------------------------------+ | Tables_in_railsmysql_development | +----------------------------------+ | schema_migrations | | usuarios | +----------------------------------+ 2 rows in set (0,00 sec) mysql> show columns from usuarios; +------------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+-------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | nombre | varchar(25) | NO | | NULL | | | apellidos | varchar(50) | YES | | NULL | | | email | varchar(40) | YES | | NULL | | | created_at | datetime | NO | | NULL | | | updated_at | datetime | NO | | NULL | | +------------+-------------+------+-----+---------+----------------+ 6 rows in set (0,00 sec)
Ahora vamos a añadir una nueva columna, que se llame nickname y que vaya después de apellidos
ubuntu@ronr:~/railsmysql$ rails generate migration Nickname Running via Spring preloader in process 753 invoke active_record create db/migrate/20161030133824_nickname.rb
Y editamos el fichero de la migración
class Nickname < ActiveRecord::Migration def change add_column("usuarios","nickname",:string, :limit => 40, :after => "email") end end
Y ejecutamos la migración comprobando que termina, que vuelve al inicio (VERSION=0) y que vuelve al final:
ubuntu@ronr:~/railsmysql$ rake db:migrate == 20161030133824 Nickname: migrating ========================================= -- add_column("usuarios", "nickname", :string, {:limit=>40, :after=>"email"}) -> 0.0637s == 20161030133824 Nickname: migrated (0.0638s) ================================ ubuntu@ronr:~/railsmysql$ rake db:migrate VERSION=0 == 20161030133824 Nickname: reverting ========================================= -- remove_column("usuarios", "nickname", :string, {:limit=>40, :after=>"email"}) -> 0.0683s == 20161030133824 Nickname: reverted (0.0705s) ================================ == 20161030125505 CreateUsuarios: reverting =================================== -- drop_table(:usuarios) -> 0.0161s == 20161030125505 CreateUsuarios: reverted (0.0163s) ========================== ubuntu@ronr:~/railsmysql$ rake db:migrate == 20161030125505 CreateUsuarios: migrating =================================== -- create_table(:usuarios) -> 0.0410s == 20161030125505 CreateUsuarios: migrated (0.0412s) ========================== == 20161030133824 Nickname: migrating ========================================= -- add_column("usuarios", "nickname", :string, {:limit=>40, :after=>"email"}) -> 0.0643s == 20161030133824 Nickname: migrated (0.0644s) ================================
Ahora ya podemos ver como en MySQL se ha creado
mysql> show columns from usuarios; +------------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+-------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | nombre | varchar(25) | NO | | NULL | | | apellidos | varchar(50) | YES | | NULL | | | email | varchar(40) | YES | | NULL | | | nickname | varchar(40) | YES | | NULL | | | created_at | datetime | NO | | NULL | | | updated_at | datetime | NO | | NULL | | +------------+-------------+------+-----+---------+----------------+ 7 rows in set (0,00 sec)
Una vez creado vamos a editar el campo nickname para que no pueda ser NULL y que sea un índice. Podríamos hacerlo deshaciendo los cambio y volviendo a una versión anterior, nuestro caso podríamos ver el status y luego decidir ir a la versión 20161030125505 y luego editar el fichero de migración, pero sería muy fácil.
ubuntu@ronr:~/railsmysql$ rake db:migrate:status database: railsmysql_development Status Migration ID Migration Name -------------------------------------------------- up 20161030125505 Create usuarios up 20161030133824 Nickname
Lo que queremos es crear una nueva migración que no sea reversible, es decir, crear una tabla es reversible porque se borra, pero modificar un campo no es directamente reversible ya que hay que indicar el cambio que hay que hacer y ya no nos serviría el método change de la migración teniendo que definir el método up y el método down.
ubuntu@ronr:~/railsmysql$ rails generate migration ModificarNickname Running via Spring preloader in process 1184 invoke active_record create db/migrate/20161030135054_modificar_nickname.rb
Y modificaríamos el fichero de migración cambiando el método change por up y por down, muy importante que down se ejecute de forma simétria a up, primero lo último que hizo up y terminar por lo primero que se ejecutó en el método up.
class ModificarNickname < ActiveRecord::Migration def up change_column("usuarios","nickname",:string,:limited => 40, :null => false) add_index("usuarios","nickname") end def down remove_index("usuarios","nickname") change_column("usuarios","nickname",:string,:limited => 40, :null => true) end end
Y comprobamos que todo funciona perfectamente
ubuntu@ronr:~/railsmysql$ rake db:migrate == 20161030135054 ModificarNickname: migrating ================================ -- change_column("usuarios", "nickname", :string, {:limited=>40, :null=>false}) -> 0.0636s -- add_index("usuarios", "nickname") -> 0.0381s == 20161030135054 ModificarNickname: migrated (0.1019s) ======================= ubuntu@ronr:~/railsmysql$ rake db:migrate VERSION=0 == 20161030135054 ModificarNickname: reverting ================================ -- remove_index("usuarios", "nickname") -> 0.0228s -- change_column("usuarios", "nickname", :string, {:limited=>40, :null=>true}) -> 0.0779s == 20161030135054 ModificarNickname: reverted (0.1009s) ======================= == 20161030133824 Nickname: reverting ========================================= -- remove_column("usuarios", "nickname", :string, {:limit=>40, :after=>"email"}) -> 0.0619s == 20161030133824 Nickname: reverted (0.0651s) ================================ == 20161030125505 CreateUsuarios: reverting =================================== -- drop_table(:usuarios) -> 0.0242s == 20161030125505 CreateUsuarios: reverted (0.0244s) ========================== ubuntu@ronr:~/railsmysql$ rake db:migrate == 20161030125505 CreateUsuarios: migrating =================================== -- create_table(:usuarios) -> 0.0433s == 20161030125505 CreateUsuarios: migrated (0.0434s) ========================== == 20161030133824 Nickname: migrating ========================================= -- add_column("usuarios", "nickname", :string, {:limit=>40, :after=>"email"}) -> 0.0628s == 20161030133824 Nickname: migrated (0.0629s) ================================ == 20161030135054 ModificarNickname: migrating ================================ -- change_column("usuarios", "nickname", :string, {:limited=>40, :null=>false}) -> 0.0714s -- add_index("usuarios", "nickname") -> 0.0295s == 20161030135054 ModificarNickname: migrated (0.1012s) =======================
Ahora ya tenemos la tabla creada en nuestra base de datos y lo que vamos a hacer es jugar con los datos es guardarlos, buscarlos, modificarlos y borrarlos.
Para introducir datos lo que vamos a hacer es crear un objeto instanciado con el modelo Usuario, tal y como lo hemos creado antes.
Para hacer todo esto lo haremos desde la consola de rails. Hay que fijarse que además nos muestra el comando SQL que ejecuta.
ubuntu@ronr:~/railsmysql$ rails console Running via Spring preloader in process 1371 Loading development environment (Rails 4.2.6) irb(main):001:0> datos = Usuario.new(:nombre => "Eduardo", :apellidos => "Collado Cabeza", :email => "asd@asd.com", :nickname => "ecollado") => #<Usuario id: nil, nombre: "Eduardo", apellidos: "Collado Cabeza", email: "asd@asd.com", nickname: "ecollado", created_at: nil, updated_at: nil> irb(main):002:0> datos.save (0.3ms) BEGIN SQL (0.6ms) INSERT INTO `usuarios` (`nombre`, `apellidos`, `email`, `nickname`, `created_at`, `updated_at`) VALUES ('Eduardo', 'Collado Cabeza', 'asd@asd.com', 'ecollado', '2016-10-30 14:07:44', '2016-10-30 14:07:44') (8.3ms) COMMIT => true
Y en MySQL veremos reflejada la inserción
mysql> mysql> select * from usuarios; +----+---------+----------------+-------------+----------+---------------------+---------------------+ | id | nombre | apellidos | email | nickname | created_at | updated_at | +----+---------+----------------+-------------+----------+---------------------+---------------------+ | 1 | Eduardo | Collado Cabeza | asd@asd.com | ecollado | 2016-10-30 14:07:44 | 2016-10-30 14:07:44 | +----+---------+----------------+-------------+----------+---------------------+---------------------+ 1 row in set (0,00 sec)
Ahora ya sabemos insertar datos en la base de datos ahora tenemos que buscar, para ello podemos hacerlo con el campo clave, el ID que automáticamente nos ha generado rails o por cualquier campo.
Para bucar por campo ID o usando search_by_xxxx
irb(main):019:0> Usuario.find(4) Usuario Load (0.7ms) SELECT `usuarios`.* FROM `usuarios` WHERE `usuarios`.`id` = 4 LIMIT 1 => #<Usuario id: 4, nombre: "Björk", apellidos: "Guðmundsdóttir", email: "bguomindsdf@islandia.com", nickname: "sugarcubes", created_at: "2016-10-30 14:20:00", updated_at: "2016-10-30 14:20:00"> irb(main):020:0> Usuario.find_by_apellidos("Gato") Usuario Load (0.8ms) SELECT `usuarios`.* FROM `usuarios` WHERE `usuarios`.`apellidos` = 'Gato' LIMIT 1 => #<Usuario id: 5, nombre: "Isidoro", apellidos: "Gato", email: "elgatoisidoro@gately.com", nickname: "gatelycat", created_at: "2016-10-30 14:21:14", updated_at: "2016-10-30 14:21:14">
Vamos a moficar el apellido a Isidoro Gato, con el id número 5 y le vamos a poner de apellido Gato con Botas en vez de Gato
irb(main):001:0> a_modificar = Usuario.find_by_apellidos("Gato") Usuario Load (0.5ms) SELECT `usuarios`.* FROM `usuarios` WHERE `usuarios`.`apellidos` = 'Gato' LIMIT 1 => #<Usuario id: 5, nombre: "Isidoro", apellidos: "Gato", email: "elgatoisidoro@gately.com", nickname: "gatelycat", created_at: "2016-10-30 14:21:14", updated_at: "2016-10-30 14:21:14"> irb(main):002:0> a_modificar.apellidos="Gato con Botas" => "Gato con Botas irb(main):003:0> a_modificar.save (0.4ms) BEGIN SQL (1.1ms) UPDATE `usuarios` SET `apellidos` = 'Gato con Botas', `updated_at` = '2016-10-30 14:29:07' WHERE `usuarios`.`id` = 5 (6.6ms) COMMIT => true
Y MySQL vemos el cambio
mysql> select * from usuarios; +----+---------+------------------+--------------------------+------------+---------------------+---------------------+ | id | nombre | apellidos | email | nickname | created_at | updated_at | +----+---------+------------------+--------------------------+------------+---------------------+---------------------+ | 1 | Eduardo | Collado Cabeza | asd@asd.com | ecollado | 2016-10-30 14:07:44 | 2016-10-30 14:07:44 | | 2 | Maria | Peña Hernandez | mph@asd.com | mpena | 2016-10-30 14:17:38 | 2016-10-30 14:17:38 | | 3 | Alberto | Smith Garcia | cholo@jotmeil.com | cholo | 2016-10-30 14:18:20 | 2016-10-30 14:18:20 | | 4 | Björk | Guðmundsdóttir | bguomindsdf@islandia.com | sugarcubes | 2016-10-30 14:20:00 | 2016-10-30 14:20:00 | | 5 | Isidoro | Gato con Botas | elgatoisidoro@gately.com | gatelycat | 2016-10-30 14:21:14 | 2016-10-30 14:29:07 | +----+---------+------------------+--------------------------+------------+---------------------+---------------------+ 5 rows in set (0,00 sec)
Aquí el problema es que hemos tenido que hacer tres cosas
- Buscar
- Modificar
- Guardar
Y lo podemos hacer juntando los pasos 2 y 3 mediante update_attributes, vamos a volver a ponerle de apellidos simplemente Gato
irb(main):004:0> a_modificar = Usuario.find_by_apellidos("Gato con Botas") Usuario Load (0.7ms) SELECT `usuarios`.* FROM `usuarios` WHERE `usuarios`.`apellidos` = 'Gato con Botas' LIMIT 1 => #<Usuario id: 5, nombre: "Isidoro", apellidos: "Gato con Botas", email: "elgatoisidoro@gately.com", nickname: "gatelycat", created_at: "2016-10-30 14:21:14", updated_at: "2016-10-30 14:29:07"> irb(main):005:0> a_modificar.update_attributes(:apellidos => "Gato") (0.3ms) BEGIN SQL (0.7ms) UPDATE `usuarios` SET `apellidos` = 'Gato', `updated_at` = '2016-10-30 14:31:50' WHERE `usuarios`.`id` = 5 (8.4ms) COMMIT => true
El resultado en ambos casos sería el mismo, sólo que con update_attributes nos ahorramos el guardar.
Ya sólo nos quedaría borrar un registro, eso lo haremos con destroy, que no es lo mismo que delete, en nuestro caso usaremos siempre destroy.
irb(main):008:0> a_modificar = Usuario.find_by_apellidos("Gato") Usuario Load (0.8ms) SELECT `usuarios`.* FROM `usuarios` WHERE `usuarios`.`apellidos` = 'Gato' LIMIT 1 => #<Usuario id: 5, nombre: "Isidoro", apellidos: "Gato", email: "elgatoisidoro@gately.com", nickname: "gatelycat", created_at: "2016-10-30 14:21:14", updated_at: "2016-10-30 14:31:50"> irb(main):009:0> a_modificar.destroy (0.1ms) BEGIN SQL (0.4ms) DELETE FROM `usuarios` WHERE `usuarios`.`id` = 5 (11.8ms) COMMIT => #<Usuario id: 5, nombre: "Isidoro", apellidos: "Gato", email: "elgatoisidoro@gately.com", nickname: "gatelycat", created_at: "2016-10-30 14:21:14", updated_at: "2016-10-30 14:31:50">
Ya sólo nos quedaría algo para bordar este post, que serían los named scopes, que vendría a ser algo parecido a una macro de SQL que nos permitirá reutilizar código y hacer la lectura y escritura de nuestro código más sencilla.
Los named scopes se llaman como si fueran métodos de ActiveRelation y requieren sintaxis lambda.
scope :active, lambda {where(:active => true)}
O con parámetros
scope :with_content_type, lambda {|ctype| where(:content_type => true)}
Se definen en el directorio /app/models dentro de los modelos correspondientes, en nuestro caso definiremos una nueva columna en la base de datos y crearemos un scope para sacar todos aquellos visibles e invisbles:
ubuntu@ronr:~/railsmysql$ rails generate migration Visible Running via Spring preloader in process 1506 invoke active_record create db/migrate/20161030144303_visible.rb ubuntu@ronr:~/railsmysql$ vim db/migrate/20161030144303_visible.rb
Y configuraremos el fichero de esta manera
class Visible < ActiveRecord::Migration def change add_column("usuarios","visible", :boolean, :default =>true, :after => "nickname") end end
Luego haremos la migración
ubuntu@ronr:~/railsmysql$ rake db:migrate == 20161030144303 Visible: migrating ========================================== -- add_column("usuarios", "visible", :boolean, {:default=>true, :after=>"nickname"}) -> 0.1167s == 20161030144303 Visible: migrated (0.1168s) =================================
Y configuraremos en el fichero del modelo, en nuestro caso app/models/usuario.rb
class Usuario < ActiveRecord::Base scope :visible, lambda {where(:visible => true)} scope :invisible, lambda {where(:visible => false)} scope :ordenado, lambda {order("usuarios.id ASC")} scope :nuevos_primero, lambda {order("usuarios.id DESC")} end
Así tendremos por ejemplo:
irb(main):009:0* Usuario.visible Usuario Load (0.8ms) SELECT `usuarios`.* FROM `usuarios` WHERE `usuarios`.`visible` = 1 => #<ActiveRecord::Relation [#<Usuario id: 1, nombre: "Eduardo", apellidos: "Collado Cabeza", email: "asd@asd.com", nickname: "ecollado", visible: true, created_at: "2016-10-30 14:07:44", updated_at: "2016-10-30 14:07:44">, #<Usuario id: 2, nombre: "Maria", apellidos: "Peña Hernandez", email: "mph@asd.com", nickname: "mpena", visible: true, created_at: "2016-10-30 14:17:38", updated_at: "2016-10-30 14:17:38">, #<Usuario id: 3, nombre: "Alberto", apellidos: "Smith Garcia", email: "cholo@jotmeil.com", nickname: "cholo", visible: true, created_at: "2016-10-30 14:18:20", updated_at: "2016-10-30 14:18:20">, #<Usuario id: 4, nombre: "Björk", apellidos: "Guðmundsdóttir", email: "bguomindsdf@islandia.com", nickname: "sugarcubes", visible: true, created_at: "2016-10-30 14:20:00", updated_at: "2016-10-30 14:20:00">]> irb(main):010:0> Usuario.invisible Usuario Load (0.7ms) SELECT `usuarios`.* FROM `usuarios` WHERE `usuarios`.`visible` = 0 => #<ActiveRecord::Relation []> irb(main):011:0> Usuario.ordenado Usuario Load (0.7ms) SELECT `usuarios`.* FROM `usuarios` ORDER BY usuarios.id ASC => #<ActiveRecord::Relation [#<Usuario id: 1, nombre: "Eduardo", apellidos: "Collado Cabeza", email: "asd@asd.com", nickname: "ecollado", visible: true, created_at: "2016-10-30 14:07:44", updated_at: "2016-10-30 14:07:44">, #<Usuario id: 2, nombre: "Maria", apellidos: "Peña Hernandez", email: "mph@asd.com", nickname: "mpena", visible: true, created_at: "2016-10-30 14:17:38", updated_at: "2016-10-30 14:17:38">, #<Usuario id: 3, nombre: "Alberto", apellidos: "Smith Garcia", email: "cholo@jotmeil.com", nickname: "cholo", visible: true, created_at: "2016-10-30 14:18:20", updated_at: "2016-10-30 14:18:20">, #<Usuario id: 4, nombre: "Björk", apellidos: "Guðmundsdóttir", email: "bguomindsdf@islandia.com", nickname: "sugarcubes", visible: true, created_at: "2016-10-30 14:20:00", updated_at: "2016-10-30 14:20:00">]> irb(main):012:0> Usuario.nuevos_primero Usuario Load (0.7ms) SELECT `usuarios`.* FROM `usuarios` ORDER BY usuarios.id DESC => #<ActiveRecord::Relation [#<Usuario id: 4, nombre: "Björk", apellidos: "Guðmundsdóttir", email: "bguomindsdf@islandia.com", nickname: "sugarcubes", visible: true, created_at: "2016-10-30 14:20:00", updated_at: "2016-10-30 14:20:00">, #<Usuario id: 3, nombre: "Alberto", apellidos: "Smith Garcia", email: "cholo@jotmeil.com", nickname: "cholo", visible: true, created_at: "2016-10-30 14:18:20", updated_at: "2016-10-30 14:18:20">, #<Usuario id: 2, nombre: "Maria", apellidos: "Peña Hernandez", email: "mph@asd.com", nickname: "mpena", visible: true, created_at: "2016-10-30 14:17:38", updated_at: "2016-10-30 14:17:38">, #<Usuario id: 1, nombre: "Eduardo", apellidos: "Collado Cabeza", email: "asd@asd.com", nickname: "ecollado", visible: true, created_at: "2016-10-30 14:07:44", updated_at: "2016-10-30 14:07:44">]>