Continuiamo la nostra analisi delle Rails Best Practices.
Nel post precedente abbiamo visto Named Scope, Model Association e Scope Access come metodi per spostare la logica dell’applicazione dai controller ai model.
Proseguendo nella stessa direzione oggi prendiamo in esame l’utilizzo di Model Callback e di Virtual Attribute.
1. Virtual Attribute
Supponiamo di avere una tabella di anagrafica clienti definita come segue
2
3
4
5
6
create_table "clients", :force => true do |t|
t.string "first_name"
t.string "last_name"
t.string "street"
t.string "city"
end
ma di voler però definire una maschera di input dove street e city siano raggruppati in uno solo campo chiamato “address”.
La form con il campo “address” sarà definita come segue:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<h1>Create Client</h1>
<% form_for @client do |f| %>
<ol class="formList">
<li>
<%= f.label :first_name, 'First Name' %>
<%= f.text_field :first_name %>
</li>
<li>
<%= f.label :last_name, 'Last Name' %>
<%= f.text_field :last_name %>
</li>
<li>
<%= f.label :address, 'Address' %>
<%= text_field_tag :address %>
</li>
</ol>
<% end %>
A questo punto nel metodo create avremo qualcosa del tipo:
2
3
4
5
6
7
8
9
10
11
12
class ClientsController < ApplicationController
def create
@client = Client.new(params[:client])
@client.street = params[:address].split(' ', 2).first
@client.city = params[:address].split(' ', 2).last
@client.save
end
...
end
Possiamo ora migliorare questo metodo definendo il campo address come attributo virtuale del model Client.
2
3
4
5
6
7
8
9
10
11
12
class Client < ActiveRecord::Base
def address
[street, city].join(' ')
end
def address=(addr)
split = addr.split(' ', 2)
self.street = split.first
self.city = split.last
end
end
Nella form possiamo ora definire il campo address come f.field_tag anzichè come text_field_tag:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<h1>Create Client</h1>
<% form_for @client do |f| %>
<ol class="formList">
<li>
<%= f.label :first_name, 'First Name' %>
<%= f.text_field :first_name %>
</li>
<li>
<%= f.label :last_name, 'Last Name' %>
<%= f.text_field :last_name %>
</li>
<li>
<%= f.label :address, 'Address' %>
<%= f.text_field :address %>
</li>
</ol>
<% end %>
Infine, il controller diventerà più semplice, ovvero:
2
3
4
5
6
7
8
9
10
class ClientsController < ApplicationController
def create
@client = Client.new(params[:client])
@client.save
end
...
end
2. Model Callback
Vediamo ora un metodo, detto Model Callback che ci permette, come con il virtual attribute appena visto, di semplificare form e controller spostando parte della logica nel model.
Supponiamo di voler implementare una funzionalità che associ automaticamente una serie di tag ad un post.
Questa funzione, detta calculate_tags ritornerà un elenco di tag in base alle parole più frequenti contenute nel post.
Diamo per scontato il codice di questa funzione e vediamo invece come e quando invocare la generazione automatica dei tag.
Una prima implementazione può essere la seguente. Data la seguente form:
2
3
4
<% form_for @post do |f| %>
<%= f.text_field :content %>
<%= check_box_tag 'calculate_tags' %>
<% end %>
il corrispondente metodo create sarà:
2
3
4
5
6
7
8
9
10
class PostController < ApplicationController
def create
@post = Post.new(params[:post])
if params[:calculate_tags] == '1'
@post.tags = TagGenerator.generate(@post.content)
else
@post.tags = ""
end
@post.save
end
Modificando il model Post, vediamo ora come migliorare il codice appena scritto.
Per prima cosa introduciamo nel nostro model un attributo calculate_tags ed un filtro di tipo before_save chiamato generate_tags
2
3
4
5
6
7
8
9
10
class Post < ActiveRecord::Base
attr_accessor :calculate_tags
before_save :generate_tags
private
def generate_tags
return unless calculate_tags == '1'
self.tags = TagGenerator.generate(self.content)
end
end
Tornando alla form possiamo ora ridefinire il campo calculate_tags come f.check_box
2
3
4
<% form_for @post do |f| %>
<%= f.text_field :content %>
<%= f.check_box 'calculate_tags' %>
<% end %>
Infine il nostro metodo create nel controller tornerà alla sua forma più semplice.
2
3
4
5
6
class PostController < ApplicationController
def create
@post = Post.new(params[:post])
@post.save
end
end