9 - Resque #271¶ ↑
(12/1/16)
Resque uses Redis
Need to add Resque tasks
in '/lib/tasks/resque.rake' require "resque/tasks" task "resque:setup" => :environment This task can then be used by Resque workers by: $ rake resque:work QUEUE='*'
The highlighting can be run in the background by adding to a resque worker queue
Whatever we pass in to 'enqueue' is converted to JSON so that it can be stored in the Redis database
/app/controllers/snippets_controller.rb def create ... Resque.enqueue(SnippetHighlighter, @snippet.id) ... end
Then create a worker in the 'app' so it's automatically in Rails loadpath
/app/workers/snippet_highlighter.rb class SnippetHighlighter @queue = :snippets_queue def self.perform(snippet_id) snippet = Snippet.find(snippet_id) uri = URI.parse('http://pygments.simplabs.com/') request = Net::HTTP.post_form(uri, {'lang' => snippet.language, 'code' => snippet.plain_code}) snippet.update_attribute(:highlighted_code, request.body) end end
Embed Resque web interface
-
So don't have to start up Sinatra based process with '$ resque-web'
-
Mount Sinatra Rack app via:
/config/routes.rb Coderbits::Application.routes.draw do resources :snippets root :to => "snippets#new" authenticate :admin do mount Resque::Server, :at => "/resque" end end
-
Make sure Resque server is loaded by adding to the 'Gemfile'
gem 'resque', :require => 'resque/server'
-
Devise will authenticate by wrapping route in 'authenticate' block
-
If no Devise then use HTTP Basic Auth in initialiser file
/config/initializers/resque_auth.rb Resque::Server.use(Rack::Auth::Basic) do |user, password| password == "secret" end
GitHub uses Resque
10 - Turbolinks #390 ¶ ↑
(8/2/16)
Get AJAX functionality with just normal HTML
Gem included by default in Rails 4 Browser needs to support it
github.com/turbolinks/turbolinks
"Turbolinks 5 is a complete rewrite that adds support for iOS and Android hybrid applications."
Add to your JavaScript manifest file (app/assets/javascripts/application.js)
//= require turbolinks
Turbolinks then listens for 'click' events for each link on the page
Issues with existing JS functionality¶ ↑
/app/assets/javascripts/projects.js.coffee
jQuery -> $('.edit_task input[type=checkbox]').click -> $(this).parent('form').submit()
This uses 'jQuery' function to listen for the 'ready' event on the document
With Turbolinks this is only triggered for the initial page so navigating to the task's page will NOT trigger the 'ready' event
-
OPTION 1
/app/assets/javascripts/projects.js.coffee
ready = -> $('.edit_task input[type=checkbox]').click -> $(this).parent('form').submit() $(document).ready(ready) $(document).on('page:load', ready)
This way the events for the checkboxes will be attached whether we’ve loaded the page via Turbolinks or not
-
OPTION 2
github.com/kossnocorp/jquery.turbolinks gem will add this in automatically
-
OPTION 3
Listen for document's 'click' event and THEN CHECK WHETHER THE ORIGIN WAS A CHECKBOX
/app/assets/javascripts/projects.js.coffee $('document').on 'click', '.edit_task input[type=checkbox]', -> $(this).parent('form').submit()
Performance increase metrics¶ ↑
github.com/steveklabnik/turbolinks_test
11 - RubyGems #384 ¶ ↑
(4/3/16)
Deciding which gem to use or build from scratch
see most Popular gems see how Activity of gems
GitHub can also be used to see last commits dates and resolution of issues
-
Compare activity on rubygems.org/
see Runtime dependencies dum dee Dum
hey it's only coloured after a URL, bla bla Bla
Gemfile
'Gemfile.lock' used to stick to particular versions until "$ bundle update" Use '~>' operator to prevent upgrading to incompatible gem versions
Find lines of code with 'Cloc'
$ brew install cloc $ cd gem-name ; cloc app lib Compare against $ cloc spec
Check source code
'lib/[gemname]' often first file loaded and shows Dependencies + Overview of structure Railtie file (eg 'lib/devise/rails.rb') gives an idea what happens when gem loaded Rails Engine has 'app' directory
12 - Tagging #382 ¶ ↑
(11/3/16)
'acts-as-taggable-on' gem¶ ↑
github.com/mbleigh/acts-as-taggable-on
$ rails g acts_as_taggable_on:migration $ rake db:migrate /app/models/article.rb: class Article < ActiveRecord::Base attr_accessible :content, :name, :tag_list acts_as_taggable end
'tag_list' attribute returns an array of strings
-
Need to prevent escaping of HTML so use 'raw' (should be moved into a helper method…)
/app/views/articles/index.html.erb: <% @articles.each do |article| %> ... <p>Tags: <%= raw article.tag_list.map { |t| link_to t, tag_path(t) }.join(', ') %></p> ... <% end %>
Now need to set up routes for tags
/config/routes.rb: Blog::Application.routes.draw do get 'tags/:tag', to: 'articles#index', as: :tag resources :articles root to: 'articles#index' end
Filter Article listing based on clicking tag link (created by 'raw article.tag_list.map …')
/app/controllers/articles_controller.rb: def index if params[:tag] @articles = Article.tagged_with(params[:tag]) else @articles = Article.all end end
Tag cloud (each link's font proportional to popularity)
Gem supplies 'tag_cloud' method
/app/views/articles/index.html.erb: <div id="tag_cloud"> <% tag_cloud Article.tag_counts, %w{s m l} do |tag, css_class| %> <%= link_to tag.name, tag_path(tag.name), class: css_class %> <% end %> </div>
github.com/mbleigh/acts-as-taggable-on/blob/master/lib/acts_as_taggable_on/tags_helper.rb
-
First argument = set of tag objects we want to display
github.com/mbleigh/acts-as-taggable-on/blob/master/lib/acts_as_taggable_on/tagging.rb
-
Second argument = array of CSS classes
/app/assets/stylesheets/articles.css.scss: #tag_cloud { width: 400px; line-height: 1.6em; .s { font-size: 0.8em; } .m { font-size: 1.2em; } .l { font-size: 1.8em; } }
-
Also a Block which is passed 'tag' and matching CSS class in 'def tag_cloud(tags, classes)'
yield tag, classes[index.nan? ? 0 : index.round]
Implementing tagging from scratch¶ ↑
$ rails g model tag name $ rails g model tagging tag:belongs_to article:belongs_to
/app/models/tag.rb:
class Tag < ActiveRecord::Base attr_accessible :name has_many :taggings has_many :articles, through: :taggings end
/app/models/article.rb:
class Article < ActiveRecord::Base attr_accessible :content, :name, :tag_list has_many :taggings has_many :tags, through: :taggings def self.tagged_with(name) Tag.find_by_name!(name).articles end def self.tag_counts Tag.select("tags.*, count(taggings.tag_id) as count"). joins(:taggings).group("taggings.tag_id") end def tag_list tags.map(&:name).join(", ") end def tag_list=(names) self.tags = names.split(",").map do |n| Tag.where(name: n.strip).first_or_create! end end end
/app/views/articles/index.html.erb:
<%= raw article.tags.map(&:name).map { |t| link_to t, tag_path(t) }.join(', ') %>
/app/helpers/application_helper.rb:
module ApplicationHelper def tag_cloud(tags, classes) max = tags.sort_by(&:count).last tags.each do |tag| index = tag.count.to_f / max.count * (classes.size - 1) yield(tag, classes[index.round]) end end end
13 - Ransack #370 ¶ ↑
(16/4/16)
Plugin extensions¶ ↑
'search' method added to Product model by github.com/activerecord-hackery/ransack/blob/rails-4.2/lib/ransack/adapters/active_record/base.rb and github.com/activerecord-hackery/ransack/blob/rails-4.2/lib/ransack/adapters/active_record.rb
'q' method added by github.com/activerecord-hackery/ransack/blob/rails-4.2/lib/ransack/configuration.rb
Basic features using GET¶ ↑
Controller changes
/app/controllers/products_controller.rb class ProductsController < ApplicationController def index # Originally "@products = Product.all" @search = Product.search(params[:q]) @products = @search.result end end
View changes
/app/views/products/index.html.erb <%= search_form_for @search do |f| %> ... <% end %>
Making results table headers into links that sort the results
/app/views/products/index.html.erb <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>
Dynamic search form¶ ↑
/app/views/products/index.html.erb <%= search_form_for @search do |f| %> <%= f.condition_fields do |c| %> ... <% end %> <% end %> /app/controllers/products_controller.rb class ProductsController < ApplicationController def index ... @search.build_condition end end
Using this we can also select on associations (as part of the 'condition_fields'
<%= a.attribute_select associations: [:category] %>
Adding and removing conditions dynamically via JavaScript¶ ↑
Move conditions fields into partial Create 'link_to_add_fields' in /app/helpers/application_helper.rb module ApplicationHelper 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 end Alter /app/views/products/index.html.erb <%= search_form_for @search do |f| %> <%= f.condition_fields do |c| %> <%= render "condition_fields", f: c%> <% end %> <p><%= link_to_add_fields "Add Conditions", f, :condition %> <div class="actions"><%= f.submit "Search" %></div> <% end %> /app/assets/javascripts/products.js.coffee 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()
GET to POST¶ ↑
If too many conditions then hit limit of data sent over GET request so use POST instead
-
Alter /config/routes.rb
-
Change View search form
<%= search_form_for @search, url: search_products_path, method: :post do |f| %>
-
However sort links still use GET so use Ransack sorting select methods
/app/controllers/products_controller.rb class ProductsController < ApplicationController def index ... @search.build_sort if @search.sorts.empty? end end
14 - Image Manipulation #374 ¶ ↑
(24/5/16)
www.imagemagick.org/script/command-line-tools.php
$ man ImageMagick $ convert octocat.png -resize '70x70^' -gravity center -crop '70x70+0+0' -quantize GRAY -colors 256 -contrast source.png $ composite stamp_overlay.png source.png source.png 'convert' not only processes existing images but can also create from scratch $ convert -size 70x70 canvas:red colour.png $ convert -size 70x70 canvas:red source.png -compose copy-opacity -composite stamp.png $ convert -size 70x70 canvas:red \( source.png -negate \) -compose copy-opacity -composite stamp.png $ convert -size 70x70 canvas:red \( octocat.png -resize '70x70^' -gravity center -crop '70x70+0+0' -quantize GRAY -colors 256 -contrast stamp_overlay.png -composite -negate \) -compose copy-opacity -composite stamp.png
Simple ruby script first, 'stamp.rb':
require "rmagick" source = Magick::Image.read("octocat.png").first source = source.resize_to_fill(70, 70).quantize(256, Magick::GRAYColorspace).contrast(true) overlay = Magick::Image.read("stamp_overlay.png").first source.composite!(overlay, 0, 0, Magick::OverCompositeOp) colored = Magick::Image.new(70, 70) { self.background_color = "red" } colored.composite!(source.negate, 0, 0, Magick::CopyOpacityCompositeOp) colored.write("stamp.png")
or by using command line tools from ruby:
# Or through the command line: system <<-COMMAND convert -size 70x70 canvas:red \\( octocat.png \ -resize '70x70^' -gravity center -crop '70x70+0+0' \ -quantize GRAY -colors 256 -contrast stamp_overlay.png \ -composite -negate \ \\) -compose copy-opacity -composite stamp.png COMMAND
/app/models/stamp.rb
class Stamp < ActiveRecord::Base attr_accessible :image mount_uploader :image, StampUploader end
/app/uploaders/stamp_uploader.rb
class StampUploader < CarrierWave::Uploader::Base # ... %w[red green blue purple black].each do |color| version(color) { process stamp: color } end def stamp(color) manipulate! format: "png" do |source| overlay_path = Rails.root.join("app/assets/images/stamp_overlay.png") overlay = Magick::Image.read(overlay_path).first source = source.resize_to_fill(70, 70).quantize(256, Magick::GRAYColorspace).contrast(true) source.composite!(overlay, 0, 0, Magick::OverCompositeOp) colored = Magick::Image.new(70, 70) { self.background_color = color } colored.composite(source.negate, 0, 0, Magick::CopyOpacityCompositeOp) end end end
15 - FnordMetric #378 ¶ ↑
(25/5/16)
github.com/whitesmith/capistrano-recipes/blob/master/fnordmetric.rb
Add to 'cloudreach' app:
-
/fnordmetric_app.rb
From “Show Notes” in railscasts.com/episodes/378-fnordmetric
-
/config/initializers/fnordmetric.rb
FNORD_METRIC = FnordMetric::API.new
-
/app/controllers/contacts_controller.rb
def show @contact = Contact.find(params[:id]) FNORD_METRIC.event(@contact.attributes.merge(_type: :view_contact)) end
-
/Gemfile
gem 'fnordmetric'
Uses Redis
-
TERM 1: $ redis-server /usr/local/etc/redis.conf
-
TERM 2: $ ruby fnordmetric_app.rb
>> Thin web server (v1.3.1 codename Triple Espresso) >> Listening on 0.0.0.0:4242, CTRL+C to stop
Update of fnordmetric-1.2.9 gem¶ ↑
(30/8/16)
Solving Ctrl-C block for when Thin is started via Rack
'~/.rvm/gems/ruby-2.2.2/gems/fnordmetric-1.2.9/lib/fnordmetric/web/web.rb': class FnordMetric::Web def initialize(opts) @opts = opts @opts[:server] ||= "thin" @opts[:host] ||= "0.0.0.0" @opts[:port] ||= "4242" end ... def initialized ... Rack::Server.start( :app => dispatch, :server => server, :Host => host, :Port => port, # 30/8/16 DH: Allowing Ctrl-C to work with Thin recent versions :signals => @opts[:signals] ) end end
FnordMetric::Web.new(port: 4242, signals: false)
16 - MiniProfiler #368¶ ↑
(3/6/16)
/Gemfile
gem 'rack-mini-profiler'
MiniProfiler enabled by default in 'development'
/app/views/projects/index.html.erb
<%= pluralize project.tasks.length, "task" %> to <%= pluralize project.tasks.size, "task" %>
To make it more efficient we need to write some SQL:
/app/controllers/projects_controller.rb
def index @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") end
-
'tasks_count' is an important name since 'tasks.size' will use it
-
Using 'joins(:tasks)' will miss any projects without tasks so need custom SQL
Can measure specific piece of code with 'Rack::MiniProfiler.step':
/app/controllers/projects_controller.rb
def index @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 end
-
The projects are lazy-loaded by the view in this action so calling '@projects.all' loads them here for duration measurement
Profiling app in 'production' mode¶ ↑
/config/environments/production.rb
# Enable Rails's static asset server config.serve_static_assets = true
Then:
$ rake assets:precompile $ rake db:setup RAILS_ENV=production $ rails s -e production
Show MiniProfiler in Production mode
/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base protect_from_forgery before_filter :miniprofiler private def miniprofiler Rack::MiniProfiler.authorize_request end end
-
We can change this behaviour based on user (eg only for admin)
samsaffron.com/archive/2012/07/12/miniprofiler-ruby-edition
github.com/SamSaffron/MiniProfiler/blob/master/Ruby/README.md
17 - Twitter Bootstrap Basics #328¶ ↑
(16/8/16)
One option is to download from getbootstrap.com/ + move files into appropriate places in filesystem
…but better if using Rails to use a gem (because Bootstrap uses Less)
twitter-bootstrap-rails (github.com/seyhunak/twitter-bootstrap-rails)
Works directly with Less Has generators built in
$ rails g bootstrap:install
Layouts
-
Fixed
<div class="container">
-
Fluid
<div class="container-fluid">
Wrap '<%= yield %>' in 'application.html.erb'
Bootstrap 3¶ ↑
Railscast uses 'twitter-bootstrap-rails (2.0.3)'
(as seen from 'Gemfile.lock' in 'ryanb-railscasts-episodes-f72b83a.tar.gz')
blog.jetstrap.com/2013/08/bootstrap-3-grids-explained/
twitter-bootstrap-rails generators¶ ↑
Themed generators to improve the look of scaffold generated code view
-
$ rails g scaffold product name price:decimal –skip-stylesheets
-
$ rails g bootstrap:themed products -f
The '-f' forces it to overwrite the generated views
www.sitepoint.com/twitter-bootstrap-less-and-sass-understanding-your-options-for-rails-3-1/