É um design pattern que o Rails implementa a partir da gem ActiveRecord.
Serve para conectar a camada Model da aplicação com tabelas do database, para assim criar um modelo de domínio persistível, onde a lógica (Model) e dados (BD) são apresentados em uma única solução.
Já persiste no BD:
obj.create
Salva mas não persiste (não da commit):
obj.save
Com build_costumer tenho que chamar save. Com create_customer não.
Referência aqui: http://ar.rubyonrails.org/
FK sempre fica no belongs_to.
belongs_to de um lado, e has_one de outro.
Uma maneira mais robusta de se trabalhar com dados que o Rails oferece.
Pequeno exemplo da abstração que a API promove:
user = User.new #Model User
query = user.where("first_name = ? AND last_name = ?","Fulano","de Tal")
query.to_sql #retorna qual o SQL gerado pela abstração
OBS o ? mostrado acima é muito mais recomendado do que se fizesse interpolação de strings. Através do ?, o Rails faz tratamentos para evitar SQL Injection.
Retorna um objeto para poder concatenar no futuro outras queries.
Scope é mais vantajoso do que criar um método, pois caso fosse, necessitaria da menção da classe ao qual pertence.
Uso de scoped (Controller):
@posts = Post.scoped
@posts = @posts.search(params[:search]) if params[:search]
@posts = @posts.published
Uso de scope (Model):
scope :published, lambda {
where("draft = ? AND published_at < ?", false, Time.current)
}
scope :search, lambda { |terms|
where("title LIKE :t OR body LIKE :t", :t => "%#{terms}%")
}
O Rails usa REST, ou seja, usa o protocolo HTTP para mostrar diferentes estados. Dessa maneira, apps conversam entre si, sem a necessidade de webservices SOAP, nem nada disso.
Ao acessar uma URL, o framework acessa seu arquivo de routes, redireciona para o controller responsável por ela, o qual renderiza a action conforme configurado (XML, JSON, JS, HTML).
resources :customers
Gera uma série de rotas pré-definidas para o model Customer.
São 7 as actions/rotas, e se utilizam dos 4 verbos do protocolo HTTP (POST, GET, PUT, DELETE):
Via campo hidden o Rails passa um atributo _method no formulário, para que ele possa emular os verbos PUT e DELETE, que os browsers não suportam.
GET
index -> exibe todos os registros.
show -> exibe UM registro (recebe parâmetro ID).
new -> formulário para criação de um novo registro.
edit -> formulário editação de UM registro (recebe parâmetro ID)
POST
create -> efetua a criação de um registro.
PUT
update -> efetua a alteração de um registro.
DELETE
destroy -> efetua a remoção de um/vários registros.
Juntamente com a criação das rotas, são criados aliases para elas, que facilitam na programação de controllers/views.
Ex:
(index) GET /customers => customers_path, customers_url
(new) GET /customers/new => new_customer_path, new_customer_url
(create) POST /customers => customers_path, customers_url
(edit) GET /customers/1/edit => edit_customer_path(@customer), edit_customer_url(@customer)
(show) GET /customers/1 => customer_path(@customer), customer_url(@customer)
(update) PUT /customers/1 => customer_path(@customer), customer_url(@customer)
(destroy) DELETE /customers/1 => customer_path(@customer), customer_url(@customer)
Aliases terminados em path apresentam caminho relativo, enquanto que terminados em url são absolutos.
No caso de existir aninhamento ou um namespace nas rotas, é essencial especificar isso no código:
#neste caso, comment depende de post na criação das rotas. ex: /post/3/comments
form_for [post, comment] do
...
end
Nested Routes
resources :posts do
member do
post :confirm
get :notify
end
get :search, :on => :collection
end
Named Routes
match "/prices" => "pages#prices", :as => :prices
Parametro opcional:
match "/about(.:format)" => "pages#about", :as => :about
Constraints
match "/mobile" => "pages#mobile", :constraints => {:user_agent => /iPhone/}
match "/:year(/:month)" => "posts#archive", :constraints => {:year => /\d{4}/}
constraints :host => /localhost/ do
match "/secret" => "info#about"
end
Quando a minha view fica muito atrolhada (mesmo com _partials), eu extraio uma funcionalidade dela e transformo em helper, para re-utilização.
Ver /app/helper. Helpers visíveis ao aplicativo ficam em application_helpers.rb, e assim por diante.
Ex:
def my_helper(resource)
render :partial => "shared/partial", :locals => { :resource => resource }
end
Conforme pode ser visto acima, não chamo partials com o underscore (_) da sua nomenclatura.
Unit => são testes de Model.
Functional => são testes de Controller.
Integration => são testes com múltiplos controllers, isto é, servem para simular um usuário acessando o sistema. São testes de workflow.
- Models são no singular.
- Tables são no plural (tenho vários itens).
- Controllers são no plural (via de regra, exceção Session por exemplo)
- Cada ambiente tem a sua configuração (ex: cache, DBMS, etc)
- Qualquer alteração na pasta config, tenho que reiniciar o server
Pensar bastante antes de criar uma nova action em um controller (famosos "fat controllers" - cheios de actions e actions grandes) ou mesmo sair criando rotas.
Em alguns casos, faz muito mais sentido criar um novo controller. Por ex: uma rotina de manipulação de arquivos. Onde vamos poder editar, criar um novo arquivo, deletar, etc. Em outros casos, é melhor pensar e ver se não podemos aproveitar alguma das actions que o Rails já define.
Exemplo:
Para fazer uma busca nos meus posts, posso simplesmente aproveitar a action "index", criando um método scope (search):
def index
@posts = Post.scoped
@posts = @posts.search(params[:search]) if params[:search]
end
- i18n (internationalization) -> preparar sistema para outros idiomas/regiões.
- L10N (globalization) -> implementar as diferenças.
Traduções default já estão em um .yml no fonte do Rails. Ex (no Console):
I18n.translate "error.messages.blank"
=> "can't be blank"
Para traduzir para pt-BR:
-
Criar pt-BR.yml em config/locales/ (pode pegar lá no Git do Rails tradução pronta e colar aqui)
-
config/application.rb => alterar linha config.i18n.default_locale = "pt-BR"
-
Reiniciar o server
-> Posso definir tanto pelo application.rb o idioma corrente, como também posso criar um before_filter. Ex:
Dentro de ApplicationController:
before_filter :change_locale
...
protected
def change_locale
#session[:locale] = :en #poderia deixar na SESSION!
I18n.locale = (params[:locale] || :en)
end
Will_paginate (gem para paginação)
-
Modificar gem file adicionando a gem.
-
No controller desejado, acrescentar:
def index @posts = Post.all.paginate(:page => params[:page], :per_page => 4) end
-
Na view dessa action:
<%= will_paginate @posts %>
Pronto :)
Ao invés de criar um método que chama um método de outra classe. Ex:
class Post < ActiveRecord::Base
...
def full_name
self.author.full_name
end
end
Posso usar o método delegate do Rails:
class Post < ActiveRecord::Base
...
#gera author_full_name
delegate :full_name, :to => :author, :prefix => true
end
-
No caso de ser um formulário, basta acrescentar :remote => true
-
No controller, devemos deixar responder ao format.js - ex:
respond_to do |format| format.html format.js { render "create.js.erb" } end
-
Criar a respectiva view, que tem o <.js> <.erb> Essa view, por ser um template ERB, aceita código Ruby/Rails e JS. Ela que trás JS que será executado no retorno do AJAX. Pode ser usado um append(), html(), val()... O que for.
OU
OBS: Neste caso a rota dashboard_path já existia. will paginate para a paginação.
CONTROLLER
dashboard_controller.rb
def index
@notes = Note.all.paginate(:page => params[:page], :per_page => 5)
end
VIEWS
index.html.erb
<h2>Últimas atividades</h2>
<div id="latest_notes">
<%= render :partial => "note", :collection => @notes %>
</div>
<%= image_tag "loader.gif", :id => "ajax_loading" %>
<%= link_to "Mostrar mais notas...",
dashboard_path(:page => @notes.next_page),
:remote => true,
:id => "more_notes",
:class => "more"
%>
<% content_for :bottom do %>
<script type="text/javascript" charset="utf-8">
var toggleLoading = function() { $("#ajax_loading").toggle(); }
$(document).ready(function(){
$("#more_notes")
.bind("ajaxStart", toggleLoading)
.bind("ajaxComplete", toggleLoading);
});
</script>
<% end %>
_notes.html.erb
<div class="notes" id="<%= dom_id(note) %>">
<div class="note_avatar">
<%= avatar_for note %>
</div>
<div class="note_info">
<h4><%= link_to note.notable.name, note.notable %></h4>
<ul>
<li class="date">criado <%= time_ago_in_words note.created_at %> atrás</li>
<li class="creator">por <%= note.creator.name %></li>
<li class="content"><%= note.content %></li>
</ul>
</div>
<div class="clear"></div>
</div>
index.js.erb
$("#latest_notes").append("<%= escape_javascript(render :partial => "note", :collection => @notes) %>");
<% unless @notes.total_pages == @notes.current_page %>
$("#more_notes").attr("href", "<%= dashboard_path(:page => @notes.next_page) %>");
<% else %>
$("#more_notes").hide();
<% end %>
Mesmo procedimento do ActionMailer normal (gerar um mailer, configurá-lo...)
1 - Nosso Mailer. Criar método no nosso Mailer para que o e-mail seja enviado (método esse que é invocado do nosso model, no método save):
class UserMailer < ActionMailer::Base
default :from => "[email protected]"
def site_contact(contact)
@contact = contact
mail(:to => "[email protected]",
:from => contact.email,
:subject => "[Contato via site] #{contact.subject}")
end
end
2 - Gerar um Model que não herde de ActiveRecord, e incluir o módulo de validações nele. Criar attr_accessor para cada elemento, e mais alguns métodos:
class Contact
include ActiveModel::Validations
attr_accessor :name, :email, :subject, :message
validates_presence_of :name, :email, :subject, :message
def initialize(attributes={})
attributes.each do |key, value|
self.send("#{key}=", value)
end
end
def to_key
end
def to_param
(id = self.id) ? id.to_s : nil
end
def save
if self.valid?
UserMailer.site_contact(self).deliver #UserMailer é o nome que dei neste exemplo para classe do ActionMailer. site_contact o meu método.
return true
end
return false
end
end
OBS: no código acima foi necessário definir o método to_param, pois ao instanciar o formulário, ficava aparecendo o endereço do objeto contact.
3 - Criar um controller para gerenciar as actions new e create:
# encoding=UTF-8
class ContactsController < ApplicationController
def new
@contact = Contact.new
end
def create
@contact = Contact.new(params[:contact])
if @contact.save
redirect_to contact_path, :notice => "Contato enviado com sucesso!"
else
flash[:alert] = "Todos os campos são obrigatórios."
render 'new'
end
end
end
4 - Criar as rotas:
get '/contato' => "contacts#new", :as => "contact"
post '/contato' => "contacts#create", :as => "contact"
5 - Criar views para ActionMailer e para as actions do nosso controller:
#app/views/user_mailer/site_contact.html.erb
Enviado por: <%= @contact.name %> - <%= @contact.email %> <br/>
Mensagem: <br/>
<%= @contact.message %>
#app/views/contacts/new.html.erb
<h2>Entre em contato conosco</h2>
<%= render 'form' %>
#app/views/contacts/_form.html.erb
<%= form_for @contact, contact_path do |f| %>
<p>
<%= f.label "Nome" %>
</p>
<p>
<%= f.text_field :name %>
</p>
<p>
<%= f.label "Email" %>
</p>
<p>
<%= f.text_field :email %>
</p>
<p>
<%= f.label "Assunto" %>
</p>
<p>
<%= f.text_field :subject %>
</p>
<p>
<%= f.label "Mensagem" %>
</p>
<p>
<%= f.text_area :message %>
</p>
<p>
<%= f.submit "Submit" %>
</p>
<% end %>
Dica: Caso seja necessário de fato enviar o e-mail no ambiente de desenvolvimento, basta editar o arquivo em config/environments/development.yml. Acrescentar/modificar as seguintes linhas:
# We want to display errors in development environment, because we are trying to send it
config.action_mailer.raise_delivery_errors = true
# Set true to actually send the email
config.action_mailer.perform_deliveries = true
E, claro, deve ser configurada a conta no config/initializers/mail_settings.rb:
ActionMailer::Base.smtp_settings = {
:address => "smtp.gmail.com",
:port => 587,
:domain => "dominio.com.br",
:user_name => "[email protected]",
:password => "secret",
:authentication => "plain",
:enable_starttls_auto => true
}
Links úteis:
Tudo posto dentro de config/initializers executa junto com o Rails.
Tudo que é modificado dentro de /config, necessita que o server seja reiniciado!