Creando un Tinder

Parte 1, Modelos y Test

Definir el modelo y los requisitos

Vamos a crear un clon básico de Tinder, en esta guía abordaremos el tema de los likes y matchs.

Tinder es un aplicación que te permite hacer like o dislike a muchos usuarios y si dos usuarios se hacen like mutuamente estos pueden comunicarse.

Primero necesitamos un modelo que sea capaz de guardar a los usuarios y sus fotos.

Además debemos guardar la información de las interacciones, una interacción puede ser de like o dislike, y necesitamos guardar ambas para no mostrar nuevamente a los usuarios con las que ya hemos interactuado.

Y por último sería útil guardar los matches, no es necesario que estos estén relacionados con las interacciones puesto que una vez que ya identifcamos y creamos el match no las necesitamos.

Crear el modelo en rails de usuarios

El modelo de usuario lo crearemos directamente con devise.

Primero instalamos la gema

gem 'devise'

Luego corremos el gerador de devise

rails g devise:install
rails g devise user

Corremos las migraciones

rake db:migrate

Generamos las vistas

rails g model devise:views

Crear el modelo en rails de interacciones

Aquí es donde se pone un poco más difícil, primero tenemos que crear el modelo con el generador, pero tenemos que tener cuidado con las referencias a usuario, puesto que hay dos y por lo mismo hay que nombrarlas.

rails g model interaction user_one:integer user_two:integer like:boolean

Antes de correr las migraciones vamos a editar el archivo para agregar las claves foraneas

La migración de interacciones debería quedar así

class CreateInteractions < ActiveRecord::Migration
  def change
    create_table :interactions do |t|
      t.integer :user_one_id, index: true, foreign_key: true
      t.integer :user_two_id, index:true, foreign_key: true 
      t.timestamps null: false
    end
    add_foreign_key :interactions, :users, column: :user_one_id 
    add_foreign_key :interactions, :users, column: :user_two_id
  end
end

El paso siguiente es crear tests para validar las reglas del negocio, particularmente nos interesa que:

Un usuario no puede interactuar consigo mismo:

test 'cant interact with myself' do
  i = Interaction.new(user_one:users(:one), user_two:users(:one))
  assert_not i.valid?, "cant interact with myself"
end

Un usuario si puede interactuar con otros usuarios:

test 'can interact with other user' do
  i = Interaction.new(user_one:users(:one), user_two:users(:two))
  assert i.valid?, "can interact with other user"
end

Un usuario no pueda interactuar dos veces con otro usuario:

test 'cant interact twice with the same user' do
  i = Interaction.new(user_one:users(:one), user_two:users(:two))
  i2 = Interaction.new(user_one:users(:one), user_two:users(:two))
  assert i2.valid?, "can interact with other user"
end

Si dos usuarios interactúan y se gustan entonces debe generarse un match:

test 'if two users like each other a match must exist' do
  i = Interaction.create(user_one:users(:one), user_two:users(:two), like:true) 
  i2 = Interaction.create(user_one:users(:two), user_two:users(:one), like:true)

  m = Match.where(user_one:i.user_one, user_two:i.user_two)
  assert_not_nil m, "a match must exist"
end

Y si dos usuarios interactúan y no se gustan entonces no debe generarse un match:

test 'if two users interact with each other but one doesnt like two, the is no match' do
  i = Interaction.create(user_one:users(:one), user_two:users(:two), like:true) 
  i2 = Interaction.create(user_one:users(:two), user_two:users(:one), like:false)
  m = Match.where(user_one:i.user_one, user_two:i.user_two)
  assert_empty m, "there must be no match"
end

Creando el modelo en rails de match

Ahora estamos hablando de matches y no tenemos un modelo para match, ahora la idea aquí es muy similar, son las mismas relaciones.

rails g model interaction user_one:integer user_two:integer like:boolean

Debemos hacer la misma modificación que hicimos previamente con las interacciones.

class CreateMatches < ActiveRecord::Migration
  def change
    create_table :matches do |t|
      t.integer :user_one_id, index: true, foreign_key: true
      t.integer :user_two_id, index: true, foreign_key: true
      t.timestamps null: false
    end
   add_foreign_key :matches, :users, column: :user_one_id 
   add_foreign_key :matches, :users, column: :user_two_id 
  end
end

Con los modelos listos ya podemos correr los tests, pero hasta que no hagamos las validaciones y las relaciones no lograremos pasarlos.

Para el modelo user.rb:

 has_many :interactions_one, class_name: "Interaction", foreign_key: :user_one_id, dependent: :destroy
has_many :interactions_two, class_name: "Interaction", foreign_key: :user_two_id, dependent: :destroy

has_many :matches_one, class_name: "Match", foreign_key: :user_one_id, dependent: :destroy
has_many :matches_two, class_name: "Match", foreign_key: :user_two_id, dependent: :destroy

Para el modelo interaction.rb

class Interaction < ActiveRecord::Base
  belongs_to :user_one, class_name: 'User' 
  belongs_to :user_two, class_name: 'User'

  validates :user_one_id, uniqueness: {scope: :user_two_id, message: "cant interact twice with the same user"}
  validate :cant_interact_myself
  after_save :check_match

  def cant_interact_myself
    if self.user_one.id == self.user_two.id
      errors.add(:expiration_date, "can't interact with myself")
    end
  end

  def check_match
    i = Interaction.where(user_one: self.user_two, user_two:self.user_one)
    unless i.empty?
      Match.create(user_one_id: self.user_one.id, user_two_id: self.user_two.id)    end 
  end
end

La línea 2 y 3 crean las relaciones con el modelo de usuario, es necesario especificar la clase puesto que al cambiarle el nombre rails no es capaz de deducirlo.

La línea 5 valida que la interacción sea única.

La línea 6 y el método cantinteractmyself validan que el usuario no pueda interactuar con el mismo

Finalmente la línea 7 y el método check_match ingresan automáticamente un match cuando existe la interacción de like del usuario 1 al 2 y del usuario 2 al 1.

Para el modelo de match.rb

class Match < ActiveRecord::Base
  belongs_to :user_one, class_name: 'User', foreign_key: :user_one_id 
  belongs_to :user_two, class_name: 'User', foreign_key: :user_two_id

  def get_user(id)
    if self.user_one_id == id
      return self.user_two
    else
      return self.user_one
    end
  end
end

En los registros de match no nos importa quien hizo match con quien, por lo mismo agregaremos un método que necesitaremos después que nos permite obtener quien es la persona a la que se hizo match dado el otro usuario.

Si quieres aprender a hacer más cosas como éstas y dedicarte de lleno del desarrollo con RoR, te recomiendo te sumes a nuestro bootcamp.