Screencast: Bullet

Bullet ist ein Ruby gem das die abgesetzten SQL Befehle analysiert und beim Endecken von Verbesserungspotial den Anwender auf diese hinweist.
Es unterstützt dabei eine Vielzahl an Benachrichtigungsmöglichkeiten.

 

Downloads in verschiedenen Formaten:

mp4
m4v
webm
ogg

 

Resourcen:

terminal

[bash]
rails g migration add_products_count_to_categories products_count:integer
rails g migration cache_products_count
rake db:migrate
[/bash]

Gemfile

[ruby]

gem ‚bullet‘, group: :development
[/ruby]

config/initializers/bullet.rb

[ruby]

if defined? Bullet
Bullet.enable = true
# Bullet.alert = true
Bullet.bullet_logger = true
end
[/ruby]

categories_controller.rb

[ruby]

def index
@categories = Category.order(:name).includes(:products)
end
[/ruby]

db/migrate/cache_products_count.rb

[ruby]

class CacheProductsCount < ActiveRecord::Migration
def up
execute "update categories set products_count=(select count(*) from products where category_id=categories.id)"
end

def down
end
end
[/ruby]

Screencast: Ransack

Mit Ransack können auf eine Weise komplexe und anspruchsvolle Suchformulare erstellt werden. Es unterstützt auch bei der Darstellung der Suchergebnisse, indem es sortierbare Listen und dynamische Suchseiten erstellt.

 

Downloads in verschiedenen Formaten:

mp4
m4v
webm
ogg

 

Resourcen:

Gemfile

[ruby]

gem ‚ransack‘
[/ruby]

products_controller.rb

[ruby]

def index
@search = Product.search(params[:q])
@products = @search.result
end
[/ruby]

products/index.html.erb

[html]

<%= search_form_for @search, url: search_products_path, method: :post do |f| %>
<%= f.condition_fields do |c| %>
<%= render "condition_fields", f: c %>
<% end %>
<p><%= link_to_add_fields "Add Conditions", f, :condition %></p>
<div class="field">
Sort:
<%= f.sort_fields do |s| %>
<%= s.sort_select %>
<% end %>
</div>
<div class="actions"><%= f.submit "Search" %></div>
<% end %>

<table id="products">
<tr>
<th><%= sort_link @search, :name, "Product Name" %></th>
<th><%= sort_link @search, :released_on, "Release Date" %></th>
<th><%= sort_link @search, :price, "Price" %></th>
</tr>
<% @products.each do |product| %>
<tr>
<td><%= link_to(product.name, product) %></td>
<td><%= product.released_on.strftime("%B %e, %Y") %></td>
<td><%= number_to_currency(product.price) %></td>
</tr>
<% end %>
</table>
[/html]

config/routes.rb

[ruby]

resources :products do
collection { post :search, to: ‚products#index‘ }
end
[/ruby]

products_controller.rb

[ruby]
def index
@search = Product.search(params[:q])
@products = @search.result
@search.build_condition if @search.conditions.empty?
@search.build_sort if @search.sorts.empty?
end
[/ruby]

products/index.html.erb

[html]
<%= search_form_for @search, url: search_products_path, method: :post do |f| %>
<%= f.condition_fields do |c| %>
<%= render "condition_fields", f: c %>
<% end %>
<p><%= link_to_add_fields "Add Conditions", f, :condition %></p>
<div class="field">
Sort:
<%= f.sort_fields do |s| %>
<%= s.sort_select %>
<% end %>
</div>
<div class="actions"><%= f.submit "Search" %></div>
<% end %>
[/html]

products/_condition_fields.html.erb

[html]

<div class="field">
<%= f.attribute_fields do |a| %>
<%= a.attribute_select associations: [:category] %>
<% end %>
<%= f.predicate_select %>
<%= f.value_fields do |v| %>
<%= v.text_field :value %>
<% end %>
<%= link_to "remove", ‚#‘, class: "remove_fields" %>
</div>
[/html]

application_helper.rb

[ruby]

def link_to_add_fields(name, f, type)
new_object = f.object.send "build_#{type}"
id = "new_#{type}"
fields = f.send("#{type}_fields", new_object, child_index: id) do |builder|
render(type.to_s + "_fields", f: builder)
end
link_to(name, ‚#‘, class: "add_fields", data: {id: id, fields: fields.gsub("n", "")})
end
[/ruby]

products.js.coffee

[javascript]
jQuery ->
$(‚form‘).on ‚click‘, ‚.remove_fields‘, (event) ->
$(this).closest(‚.field‘).remove()
event.preventDefault()

$(‚form‘).on ‚click‘, ‚.add_fields‘, (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data(‚id‘), ‚g‘)
$(this).before($(this).data(‚fields‘).replace(regexp, time))
event.preventDefault()
[/javascript]

Screencast: MiniProfiler

MiniProfiler erlaubt es sich Geschwindigkeitsmessungen direkt auf der aufgerufenen Seite anzuschauen. Dabei werden auch die aufgerufenen SQL Befehle und wenn gewünscht Messungen für ausgesuchte Code-Blöcke.

 

Downloads in verschiedenen Formaten:

mp4
m4v
webm
ogg

 

Resourcen:

Gemfile

[ruby]

gem ‚rack-mini-profiler‘
[/ruby]

products/index.html.erb

[html]
<%= pluralize project.tasks.size, "task" %>
[/html]

projects_controller.rb

[ruby]

@projects = Project.order(:created_at).select("projects.*, count(tasks.id) as tasks_count").joins("left outer join tasks on project_id=projects.id").group("projects.id")
Rack::MiniProfiler.step("fetch projects") do
@projects.all
end
[/ruby]

config/environments/production.rb

[ruby]

config.serve_static_assets = true
[/ruby]

terminal

[bash]
rake assets:precompile
rake db:setup RAILS_ENV=production
rails s -e production
[/bash]

application_controller.rb

[ruby]

before_filter :miniprofiler

private

def miniprofiler
Rack::MiniProfiler.authorize_request # if user.admin?
end
[/ruby]

Screencast: Sidekiq

Sidekiq erlaubt es Prozesse im Hintergrund asynchron weiter zu verarbeiten während die Applikation bereits neue Anfragen entgegen nehmen kann. Es verwendet dabei Threads anstatt Forks und geht dadurch effizienter mit Resourcen um als vergleichsweise Resque.

 

Downloads in verschiedenen Formaten:

mp4
m4v
webm
ogg

 

Resourcen:

terminal

[bash]

brew install redis
redis-server /usr/local/etc/redis.conf
bundle exec sidekiq
bundle exec sidekiq -q high,5 default
[/bash]

Gemfile

[ruby]
gem ’sidekiq‘
gem ’sinatra‘, require: false
gem ’slim‘
[/ruby]

snippets_controller.rb

[ruby]
PygmentsWorker.perform_async(@snippet.id)
# PygmentsWorker.perform_in(1.hour, @snippet.id)
[/ruby]

app/workers/pygments_worker.rb

[ruby]

class PygmentsWorker
include Sidekiq::Worker
sidekiq_options queue: "high"
# sidekiq_options retry: false

def perform(snippet_id)
snippet = Snippet.find(snippet_id)
uri = URI.parse("http://pygments.appspot.com/")
request = Net::HTTP.post_form(uri, lang: snippet.language, code: snippet.plain_code)
snippet.update_attribute(:highlighted_code, request.body)
end
end
[/ruby]

routes.rb

[ruby]

require ’sidekiq/web‘
# …
mount Sidekiq::Web, at: ‚/sidekiq‘
[/ruby]

Screencast: Active Records Reputation System

Wenn der Durchschnittswert für ein Bewertungssystem oder die Summe von abgegebenen Stimmen berechnet werden müssen, dann lohnt sich ein Blick auf das activerecord-reputation-system gem. Ryan zeigt in diesem Screencast wie es verwendet wird und wie eine selbsterstellte Basis-Version gebaut werden kann.

 

Downloads in verschiedenen Formaten:

mp4
m4v
webm
ogg

 

Resourcen:

terminal

[bash]

rails g reputation_system
rake db:migrate
[/bash]

Gemfile

[ruby]

gem ‚activerecord-reputation-system‘, require: ‚reputation_system‘
[/ruby]

models/haiku.rb

[ruby]

has_reputation :votes, source: :user, aggregated_by: :sum
[/ruby]

models/user.rb

[ruby]

has_many :evaluations, class_name: "RSEvaluation", as: :source

has_reputation :votes, source: {reputation: :votes, of: :haikus}, aggregated_by: :sum

def voted_for?(haiku)
evaluations.where(target_type: haiku.class, target_id: haiku.id).present?
end
[/ruby]

config/routes.rb

[ruby]

resources :haikus do
member { post :vote }
end
[/ruby]

haikus_controller.rb

[ruby]

def index
@haikus = Haiku.find_with_reputation(:votes, :all, order: "votes desc")
end

def vote
value = params[:type] == "up" ? 1 : -1
@haiku = Haiku.find(params[:id])
@haiku.add_or_update_evaluation(:votes, value, current_user)
redirect_to :back, notice: "Thank you for voting"
end
[/ruby]

_haiku.html.erb

[html]

| <%= pluralize haiku.reputation_value_for(:votes).to_i, "vote" %>
<% if current_user && !current_user.voted_for?(haiku) %>
| <%= link_to "up", vote_haiku_path(haiku, type: "up"), method: "post" %>
| <%= link_to "down", vote_haiku_path(haiku, type: "down"), method: "post" %>
<% end %>
[/html]

application.html.erb

[html]

<%= current_user.reputation_value_for(:votes).to_i %>
[/html]

models/haiku_vote.rb

[ruby]

validates_uniqueness_of :haiku_id, scope: :user_id
validates_inclusion_of :value, in: [1, -1]
validate :ensure_not_author

def ensure_not_author
errors.add :user_id, "is the author of the haiku" if haiku.user_id == user_id
end
[/ruby]

models/haiku.rb

[ruby]

def self.by_votes
select(‚haikus.*, coalesce(value, 0) as votes‘).
joins(‚left join haiku_votes on haiku_id=haikus.id‘).
order(‚votes desc‘)
end

def votes
read_attribute(:votes) || haiku_votes.sum(:value)
end
[/ruby]

models/user.rb

[ruby]

def total_votes
HaikuVote.joins(:haiku).where(haikus: {user_id: self.id}).sum(‚value‘)
end

def can_vote_for?(haiku)
haiku_votes.build(value: 1, haiku: haiku).valid?
end
[/ruby]

haikus_controller.rb

[ruby]

def index
@haikus = Haiku.by_votes
end

def vote
vote = current_user.haiku_votes.new(value: params[:value], haiku_id: params[:id])
if vote.save
redirect_to :back, notice: "Thank you for voting."
else
redirect_to :back, alert: "Unable to vote, perhaps you already did."
end
end
[/ruby]

_haiku.html.erb

[html]

| <%= pluralize haiku.votes, "vote" %>
<% if current_user && current_user.can_vote_for?(haiku) %>
| <%= link_to "up", vote_haiku_path(haiku, value: 1), method: "post" %>
| <%= link_to "down", vote_haiku_path(haiku, value: -1), method: "post" %>
<% end %>
[/html]

application.html.erb

[html]

<%= current_user.total_votes %>
[/html]

Screencast: Authentifizierung über Facebook

In diesem Screencast zeigt Ryan wie eine neue Facebook erstellt und konfiguriert werden kann. Zusätzlich zeigt er wie das omniauth-facebook gem eingesetzt wird.

 

Downloads in verschiedenen Formaten:

mp4
m4v
webm
ogg

 

Resourcen:

Gemfile

[ruby]
gem ‚omniauth-facebook‘
[/ruby]

config/initializers/omniauth.rb

[ruby]
OmniAuth.config.logger = Rails.logger

Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, ENV[‚FACEBOOK_APP_ID‘], ENV[‚FACEBOOK_SECRET‘]
end
[/ruby]

terminal

[bash]
rails g model user provider uid name oauth_token oauth_expires_at:datetime
rake db:migrate
[/bash]

models/user.rb

[ruby]
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.save!
end
end
[/ruby]

config/routes.rb

[ruby]
match ‚auth/:provider/callback‘, to: ’sessions#create‘
match ‚auth/failure‘, to: redirect(‚/‘)
match ’signout‘, to: ’sessions#destroy‘, as: ’signout‘
[/ruby]

sessions_controller.rb

[ruby]
class SessionsController < ApplicationController
def create
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_url
end

def destroy
session[:user_id] = nil
redirect_to root_url
end
end
[/ruby]

application_controller.rb

[ruby]
private

def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
[/ruby]

layouts/application.html.erb

[html]
<div id="user_nav">
<% if current_user %>
Signed in as <strong><%= current_user.name %></strong>!
<%= link_to "Sign out", signout_path, id: "sign_out" %>
<% else %>
<%= link_to "Sign in with Facebook", "/auth/facebook", id: "sign_in" %>
<% end %>
</div>
[/html]

app/assets/javascripts/facebook.js.coffee.erb

[javascript]

jQuery ->
$(‚body‘).prepend(‚<div id="fb-root"></div>‘)

$.ajax
url: "#{window.location.protocol}//connect.facebook.net/en_US/all.js"
dataType: ’script‘
cache: true

window.fbAsyncInit = ->
FB.init(appId: ‚<%= ENV["FACEBOOK_APP_ID"] %>‘, cookie: true)

$(‚#sign_in‘).click (e) ->
e.preventDefault()
FB.login (response) ->
window.location = ‚/auth/facebook/callback‘ if response.authResponse

$(‚#sign_out‘).click (e) ->
FB.getLoginStatus (response) ->
FB.logout() if response.authResponse
true
[/javascript]

Screencast: Squeel

Squeel bietet eine umfangreiche DSL an um SQL Abfrage in Ruby zu erstellen. Es baut auf ARel auf und bietet somit auch Zugriff auf dessen Features.

 

Downloads in verschiedenen Formaten:

mp4
m4v
webm
ogg

 

Resourcen:

terminal

[bash]
rails g squeel:initializer
rails c
[/bash]

rails console

[ruby]
Product.where{released_at < 3.months.ago}
Product.where{released_at.lt 3.months.ago}
Product.where{released_at.lt(3.months.ago) & price.gt(20)}
Product.where{released_at.lt(3.months.ago) | price.gt(20)}
[/ruby]

models/product.rb

[ruby]
def self.search(query)
where do
(released_at <= Time.zone.now) &
((discontinued_at == nil) | (discontinued_at > Time.zone.now)) &
(stock >= my{low_stock}) & (name =~ "%#{query}%")
end
end

def self.low_stock
2
end
[/ruby]

Screencast: Das Rails API Gem

Applikationen die eine reine API zur Verfügung erstellen kommen immer häufiger vor und haben einen anderen Anspruch als konventionelle Web-Applikationen. Sie benötigen in der Regel keine/andere Views um Daten zur Verfügung zu stellen. In Rails kann für Applikationen die hauptsächlich APIs anbieten das Rails API Gem eingesetzt werden. Ryan zeigt in dieser Woche wie es verwendet werden kann.

 

Downloads in verschiedenen Formaten:

mp4
m4v
webm
ogg

 

Resourcen:

terminal

[bash]
gem install rails-api
rbenv rehash
rails-api new todo
rails g scaffold task name
rake middleware
diff api_middleware.txt full_middleware.txt
[/bash]

tasks_controller.rb

[ruby]
include ActionController::MimeResponds
include ActionController::Helpers
include ActionController::Cookies
include ActionController::ImplicitRender

# GET /tasks
# GET /tasks.json
def index
@tasks = Task.all

# respond_to do |format|
# format.json { render }
# format.xml { render xml: @tasks }
# end
end
[/ruby]

views/tasks/index.json.rabl

[javascript]
collection @tasks
attributes :id, :name
[/javascript]

config/application.rb

[ruby]
config.middleware.insert_after ActiveRecord::QueryCache, ActionDispatch::Cookies
[/ruby]

Screencast: Mehrseitige Formulare mit Wicked

Mehrseitige Formulare sind nicht immer einfach zu erstellen. Wicket ist ein Plugin das genau diese Nische bedient und die Erstellung vereinfacht. Mit Wicket lassen sich Controller in eine Serie von Schritten verwandeln um seitenweise Daten zu erfassen.

 

Downloads in verschiedenen Formaten:

mp4
m4v
webm
ogg

 

Resourcen:

terminal

[bash]
rails g controller user_steps
[/bash]

user_steps_controller.rb

[ruby]
class UserStepsController < ApplicationController
include Wicked::Wizard
steps :personal, :social

def show
@user = current_user
render_wizard
end

def update
@user = current_user
@user.attributes = params[:user]
render_wizard @user
end

private

def redirect_to_finish_wizard
redirect_to root_url, notice: "Thank you for signing up."
end
end
[/ruby]

user_steps/personal.html.erb

[html]
<h1>Tell us a little about yourself.</h1>

<%= render layout: ‚form‘ do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :date_of_birth %><br />
<%= f.date_select :date_of_birth, start_year: 1900, end_year: Date.today.year, include_blank: true %>
</div>
<div class="field">
<%= f.label :bio %><br />
<%= f.text_area :bio, rows: 5 %>
</div>
<% end %>
[/html]

user_steps/social.html.erb

[html]
<h1>Where can we find you?</h1>

<%= render layout: ‚form‘ do |f| %>
<div class="field">
<%= f.label :twitter_username %><br />
<%= f.text_field :twitter_username %>
</div>
<div class="field">
<%= f.label :github_username %><br />
<%= f.text_field :github_username %>
</div>
<div class="field">
<%= f.label :website %><br />
<%= f.text_field :website %>
</div>
<% end %>
[/html]

user_steps/_form.html.erb

[html]
<%= form_for @user, url: wizard_path do |f| %>
<% if @user.errors.any? %>
<div class="error_messages">
<h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= yield f %>

<div class="actions">
<%= f.submit "Continue" %>
or <%= link_to "skip this step", next_wizard_path %>
</div>
<% end %>
[/html]

users/new.html.erb

[html]
<h1>Sign Up</h1>

<%= form_for @user do |f| %>
<% if @user.errors.any? %>
<div class="error_messages">
<h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>

<div class="field">
<%= f.label :email %><br />
<%= f.text_field :email %>
</div>
<div class="field">
<%= f.label :password %><br />
<%= f.password_field :password %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %>
</div>

<div class="actions">
<%= f.submit "Sign Up" %>
</div>
<% end %>
[/html]

users_controller.rb

[ruby]
def create
@user = User.new(params[:user])
if @user.save
session[:user_id] = @user.id
redirect_to user_steps_path
else
render :new
end
end
[/ruby]

models/user.rb

[ruby]
validates_format_of :twitter_username, without: /W/, allow_blank: true
# validates_presence_of :twitter_username, if: :on_social_step?
[/ruby]

Screencast: Queue Classic

Nachdem letzte Woche PostgreSQL allgemein vorgestellt wurde, geht es diese Woche weiter mit weiteren Features für die PostgreSQL benutzt werden kann. Neben der normal Datenbank-Funktionalität kann PostgreSQL auch als Queue für die Prozess-Verarbeitung eingesetzt werden. Somit wäre es nicht nötig noch eine zusätzliche Komponente für Hintergrundprozesse zu installieren und zu verwalten. Ryan zeigt diese Woche wie dies alles mit queue_classic gelöst werden kann.

 

Downloads in verschiedenen Formaten:

mp4
m4v
webm
ogg

 

Resourcen:

config/application.rb

[ruby]
# Although not shown in the episode, it is a good idea to uncomment this line in config/application.rb
# This is because the schema we are generating here cannot be represented in Ruby.
config.active_record.schema_format = :sql
[/ruby]

terminal

[bash]
rails g migration add_queue_classic
rails g migration setup_queue_classic
rake db:migrate
rake qc:work
rails c
[/bash]

rails console

[ruby]
QC.enqueue "puts", "hello world"
QC.enqueue "puts", msg: "hello world"
QC.enqueue "puts", "msg" => "hello world"
[/ruby]

Gemfile

[ruby]
gem ‚queue_classic‘, ‚2.0.0rc12‘
[/ruby]

lib/tasks/queue_classic.rake

[ruby]
require "queue_classic"
require "queue_classic/tasks"
[/ruby]

config/initializers/queue_classic.rb

[ruby]
ENV["DATABASE_URL"] = "postgres://localhost/mailer_development"
[/ruby]

migrations/*add_queue_classic.rb

[ruby]
def up
create_table :queue_classic_jobs do |t|
t.string :q_name
t.string :method
t.text :args
t.timestamp :locked_at
end
add_index :queue_classic_jobs, :id
end

def down
drop_table :queue_classic_jobs
end
[/ruby]

migrations/*setup_queue_classic.rb

[ruby]
def up
QC::Queries.load_functions
end

def down
QC::Queries.drop_functions
end
[/ruby]

newsletters_controller.rb

[ruby]
QC.enqueue "Newsletter.deliver", params[:id]
[/ruby]

models/newsletter.rb

[ruby]
def self.deliver(id)
newsletter = find(id)
# raise "Oops"
sleep 10 # simulate long newsletter delivery
newsletter.update_attribute(:delivered_at, Time.zone.now)
end
[/ruby]