Iterare dati dentro un modello


#1

Hello everybody,

I have 2 tables users and videos. I create another table (video_users)
to link them with easiness of rails.

On the view when user 1 clic play button to view video 1, user_id and
video_id are load automatically on table videos_users.

When user clic again, the same thing append on new record.

Example:

This is what happend on database:

user_id video_id
1 1
1 1

How can I have something like this?

user_id video_id number_view
1 1 2

Model User:

class User < ActiveRecord::Base
has_many :video_users
has_many :videos, through: :video_users
end

Model Video:

class Video < ActiveRecord::Base
has_many :video_users
has_many :users, through: :video_users
end

Model VideoUser

class VideoUser < ActiveRecord::Base
belongs_to :video
belongs_to :user
end

Migration video_users:

class CreateVideoUsers < ActiveRecord::Migration
def change
create_table :video_users do |t|
t.references :video, index: true
t.references :user, index: true

  t.timestamps
end

end
end

When i tried to create column number_view, i have some error:
SQLite3::SQLException: no such table: videos_users: ALTER TABLE
“videos_users” ADD “number_view” integer DEFAULT 1

Thank you very much


#2

Hi Simon,

to achieve your goal you could do this:

video_user = VideoUser.find_or_create_by(user_id: params[:user_id],

video_id: params[:video_id])
video_user.number_view ||= 0
video_user.number_view += 1
video_user.save

It looks like your join table is named “video_users” so within the
migration in which you try to create the column “number_view” you should
specify that name instead of “videos_users”.
On top of that I suggest you to specify the default value of 0 for
“number_view”:

  t.integer :number_view, default: 0

so that you can simply increment that field and avoid the line:

video_user.number_view ||= 0

As a side note, it would be better to name join tables with the
convention
of putting the names alphabetically ordered and in the plural form. This
means that you could’ve named your join table “users_videos” because
“users” comes before “videos” and each of them is specified in the
plural
form.


#3

Ciao Maurizio,

grazie mille per il tuo aiuto.

Ho fatto tutto quello che hai detto. Grazie di cuore.

Funziona!

Però il record viene sempre creato e non va bene cosi.

La situazione è questa nel db:

L’user clicca per la prima volta:

user_id video_id number_view
1 1 1

Lo stesso user clicca sullo stesso video per la seconda volta:

user_id video_id number_view
1 1 2 --> Effetto del secondo click
1 1 0 --> Rails lo fa automaticamente secondo i
legami tra le tavole User, Video e VideoUser

Quindi viene sempre creato un nuovo record con il valore di default di
number_view.

Questo è il codice:

def iterate_number_view(user_id, video_id)
video_user = VideoUser.find_or_create_by(user_id: user_id, video_id:
video_id)
video_user.number_view ||= 0
video_user.number_view += 1
video_user.save
end

Questo è il refactoring della migration di VideoUser

class CreateVideoUsers < ActiveRecord::Migration
def change
create_table :video_users do |t|
t.references :video, index: true
t.references :user, index: true
t.integer :number_view, default: 0
t.timestamps
end
end
end

Come bloccare la second azione cioè quello che fa rails in modo
automatico?

Ho provato di rompere i legami cioè rendere ogni tavola indipendente
però non mi sta bene perché i legami mi aiutano a fare altre azioni.

Grazie per l’aiuto


#4

Ciao Simon (giusto? :-))

il fatto che number_view venga aggiornato ad ogni successiva azione mi
fa
pensare che il metodo #iterate_number_view sia corretto.

  1. Puoi condividere il metodo del controller chiamato in seguito
    all’azione
    dell’utente?
  2. Giusto per essere sicuri, hai per caso aggiunto delle callback
    after_create o after_save ai model User e Video?

2015-08-14 16:11 GMT+02:00 Simon E. removed_email_address@domain.invalid:


#5
  1. Puoi condividere il metodo del controller chiamato in seguito
    all’azione
    dell’utente?

In pratica il codice è cosi
nel video_controller, questo è il metodo che viene chiamato quando
l’utente clicca:

def play
current_user.try :watch_video, @video
VideoUser.iterate_number_view(current_user.id, @video.id)
respond_to do |format|
format.js
end
end

poi viene chiamato questo:

def self.iterate_number_view(user_id, video_id)
video_user = self.find_or_create_by(user_id: user_id, video_id:
video_id)
video_user.number_view ||= 0
video_user.number_view += 1
video_user.save
end

current_user è l’user corrente, try è un metodo per l’eccezione

Ecco il metodo watch_video che è scritto nel model user

def watch_video video
videos << video
end

  1. Giusto per essere sicuri, hai per caso aggiunto delle callback
    after_create o after_save ai model User e Video?

No no

Grazie Maurizio.

Da giorni che cerco di risolvere questo problema -( -(… Ma…


#6

Appunto bel,

Sono un somaro. Quando fatto un giro fuori che mi sono reso conto che
stavo sbagliare.
Ho semplicemente cancellato quella linea là.

Tutto gira come desideravo

Grazie per tutto Maurizio.

Ciao e buon week-end


#7

On 14 August 2015 at 17:31, Simon E. removed_email_address@domain.invalid wrote:

current_user.try :watch_video, @video
video_id)
video_user.number_view ||= 0
video_user.number_view += 1
video_user.save
end

current_user è l’user corrente, try è un metodo per l’eccezione

Ecco il metodo watch_video che è scritto nel model user

Ecco il colpevole :wink:

def watch_video video
videos << video
end

User e Video sono legati da una relazione has_many :through (vedi
“La
Guida” [0]), che condivide molti dei concetti (e metodi) delle relazioni
has_and_belongs_to_many. È una relazione N:M tra due entità, User e
Video, e come tale richiede la presenza della tabella d’appoggio
(video_users, rappresentata esplicitamente in ActiveRecord tramite la
classe VideoUser).
Il motivo per cui non hai potuto optare per la relazione
has_and_belongs_to_many e hai dovuto invece scegliere has_many :through
è perché nel caso di has_and_belongs_to_many non puoi aggiungere
attributi specifici al legame tra le due entità (come la colonna
number_view nel tuo caso).

Ok, tutto molto bello fino a qui.

Come dicevo prima, questo tipo di relazioni condivide alcuni metodi
d’utilità che ti permettono di gestire i record delle tabelle a livello
Ruby.
All’interno del contesto di una variabile d’istanza di User, puoi
avere
accesso a tutti i record di tipo Video collegati ad essa tramite il
metodo videos, il quale ti fornisce l’astrazione di un normale Array
Ruby (più o meno, dato che non è proprio un oggetto della classe Array,
quanto invece un oggetto della classe
ActiveRecord_Associations_CollectionProxy) e ogni volta che aggiungi un
elemento a videos (tramite videos << oggetto), vengono eseguite
delle
callback ActiveRecord che aggiornano lo stato del database, creando un
corrispondente record nella join table video_users.

Quindi, dato che con iterate_number_view te già stai creando e/o
aggiornando i record necessari, non hai più bisogno del metodo
watch_video :slight_smile:

[0] http://guides.rubyonrails.org/association_basics.html


#8

Una passeggiate, una buona dormita e un bel bagno sono le muse migliori
degli sviluppatori :wink:

2015-08-14 18:12 GMT+02:00 Simon E. removed_email_address@domain.invalid:


#9

Ahahahaha Maurizio… ed è proprio così…:slight_smile: