php メモリ使用量測定メモ
Rails系の記事ばかりだったが、久々にphpでメモリの使用量を計測する機会があったので、やり方をメモっておく。
phpに割り当てられた現在のメモリ使用量を測定
memory_get_usage()
を使用する。
http://php.net/manual/ja/function.memory-get-usage.php
<?php echo "first:".memory_get_usage() / (1024 * 1024)."MB\n"; $arr = []; for($i=0;$i<10000;$i++) { $arr[] = $i; } echo "now:".memory_get_usage() / (1024 * 1024)."MB\n";
output
first:0.21486663818359MB now:1.6373443603516MB
phpに割り当てられたメモリ使用量の最大値を取得
memory_get_peak_usage()
を使用する。
http://php.net/manual/ja/function.memory-get-peak-usage.php
<?php echo "first:".memory_get_usage() / (1024 * 1024)."MB\n"; $arr = []; for($i=0;$i<10000;$i++) { $arr[] = $i; } //半分開放する for($i=0;$i<5000;$i++) { unset($arr[$i]); } echo "now:".memory_get_usage() / (1024 * 1024)."MB\n"; echo "peak:".memory_get_peak_usage() / (1024 * 1024)."MB\n";
output
first:0.21579742431641MB now:0.98969268798828MB peak:1.6398239135742MB
rails 4でransackを使った検索機能を実装した
すごく簡単に検索機能を実装できるというransack
。
metasearch
というGemの後継らしい。
ちょうど検索を実装する案件があったので、試しに使ってみた。
流れ
前提
- Authorモデルの検索一覧ページとする
- 例としてモデルの構成は下記のようにする
Author -has_many-> Post authors_table - name - category posts_table - title - author_id - entry
ransack導入
Gem 'ransack'
gemからインストールする。
https://github.com/activerecord-hackery/ransack
searchメソッド(DSL)の実装
controller.rb
def hoge @q = Author.ransack params[:q] @authors = @q.result(distinct: true).includes(:posts) end
パラメータをransack
に投げてあげて、result
で結果を取得する。
今回は関連テーブルがあるので、includes
で指定してあげる。
view.slim.html
= search_form_for(@q, url: hoge_path, method: :get) do |f| .div = f.label :name = f.search_field :name_eq .div = f.label :posts = f.search_field :posts_title_eq .div = f.label :category =f.collection_check_boxes :category_in, category_list, :id, :name, {checked:@q.categgory} do |b| - b.label {b.check_box + b.text}
基本的には項目名
+ 条件
でパラメータを渡してあげれば、その条件
にそった検索を実行してくれる。
以下のQita記事に簡単に条件がまとめられているので参考にするとよい。
http://qiita.com/nysalor/items/9a95d91f2b97a08b96b0
単純に入力値と等しい項目を探したい場合は、name
とeq
と続けて記述し、関連テーブル
のカラムに対して検索したい場合は、posts_title_eq
のようカラムと条件に加え、関連テーブル名
を先頭に記述する。
また、チェックボックス等で選択した複数の項目を検索したい場合は、in
を使うと便利だ。
あとはフリーワード検索なんか↓のように描くことも可
name_or_category_or_posts_title_or_posts_entry_cont
他にも色々な条件がパラメータの書き方を変えるだけで検索可能となる。
ransackable_scopesで柔軟な検索実装
複雑な検索をしたい場合は、scope(あるいはmethod)を定義することで実現可能となる。
author.rb
# custom scope scope :custom_scope, -> (boolean=true) { joins(:posts).where(active: boolean) } private # for ransack scope def self.ransackable_scopes(auth_object=nil) %i(custom_scope) end
view.slim.html
= search_form_for(@q, url:hoge_path,method: :get) do |f| .div = f.label :custom_scope = f.search_field :custom_scope
model内にscopeの実装を記述し、そのscope名をransackable_scopes
にシンボルとして定義してあげる。
あとは定義したscope名をinputメソッドのオプションに記述するだけでよい。
ひとこと
そこまで複雑な検索機能を実装する必要性がないのであれば、自分でゴリゴリかくよりもいいかもしれない。 というか、結構複雑な検索でもscopeとか使えば実装できるし、特に縛りもないのでコレ一本でやっていけるかもですね。
参考URL
https://github.com/activerecord-hackery/ransack
http://qiita.com/ruzia/items/f6003547ca18377a1508
http://qiita.com/nysalor/items/9a95d91f2b97a08b96b0
Rails ajaxでhogehoge.js.slim(erb)を使わずにHTMLを書き換える
概要
前回の記事では、ajaxを実現するためのファイル構成は以下のようになっていた。
- controllers - photo_controller.rb - views - photo - new.html.slim - new.js.slim - _add_photo_form.html.slim
例えばviews
内にjsファイルを配置するのではなく、なるべくassets
内のjsファイルにまとめて記述したほうがシンプルで良いという場合があると思う。
今回は、このajaxアクションのために必要なnews.js.slim
をなくしてajaxを行う方法を書いてみる。
今回のファイル構成は以下のようになる。
- controllers - photo_controller.rb - views - photo - new.html.slim - \_add_photo_form.html.slim - assets - javascript - photo.js.coffee
viewファイル
viewファイルは前回とほとんど同じ。
new.html.slim
#選択したファイルのプレビューを表示 javascript: function readUploadImage(input) { if (input.files && input.files[0]) { var reader = new FileReader(); reader.onload = function (e) { $(input).next() .attr('src', e.target.result) .width(100); }; reader.readAsDataURL(input.files[0]); } } div = form_for(@user, url: form_path, html: {multipart: true}) do |f| .div =link_to 'ボタンを追加する', new_path(@user.id),remote: true,data: {role: 'add_input'} .div data-role="photo_area" - @user.photos.each do |p| .div = image_tag p.image.url(:thumb) = check_box 'delete',"#{p.id}" = label_tag "delete[#{p.id}]", '削除' = render partial: 'photo_upload' .div = f.submit "登録する"
_add_photo_form.html.slim
.div = fields_for "user[photos_attributes][#{Time.now.to_i}]" do |ff| = ff.file_field :image, onchange: "readUploadImage(this)" = image_tag ''
render json(or text) でhtmlコンテンツを返す
photo_controller.rb
def new if request.xhr? content = render_to_string(:partial => 'add_photo_form') render json: {html: content}, status: :ok end end
ajax通信があった場合、まずはrender_to_string
で読み込みたいviewファイルをstring化する。
それをjson
形式で返してあげる。(text
でも可)
photo.js.coffee
$(window).on 'load' ,-> $('[data-role="add_input"]').bind 'ajax:beforeSend', () -> console.log 'start' .bind 'ajax:complete',(e,data,status,xhr) -> if data.responseJSON? $('[data-role="photo_area"]').append data.responseJSON.html
javascriptでajaxのcallbackを使って、上記のcontroller側から返したjson
データを取得し、指定の箇所にappend
する。
以上で終わり。
Railsでhas_manyの複数の画像をajaxを使って動的に一括登録する
概要
表題の通りのことをしたい。
ユーザーモデルと写真モデルは下記の様なhas_many
の関係とする。
ユーザー(user) ==has_many=⇛ 写真(photo)
簡単にやることをまとめると
field_for
を使用して、複数の子モデルを同時に保存するフォームを作成する。- 削除用のcheckboxを実装する。
- 追加ボタンを押したら動的にfile inputを増やす。
なお、画像登録はpaperclip
を使用する。
formの作成
photo_controller.rb
def new @user = User.find(params[:id]) if request.xhr? respond_to do |format| format.js end end end
new.html.slim
#選択したファイルのプレビューを表示 javascript: function readUploadImage(input) { if (input.files && input.files[0]) { var reader = new FileReader(); reader.onload = function (e) { $(input).next() .attr('src', e.target.result) .width(100); }; reader.readAsDataURL(input.files[0]); } } div = form_for(@user, url: form_path, html: {multipart: true}) do |f| .div =link_to 'ボタンを追加する', new_path(@user.id),remote: true .div data-role="photo_area" - @user.photos.each do |p| .div = image_tag p.image.url(:thumb) = check_box 'delete',"#{p.id}" = label_tag "delete[#{p.id}]", '削除' = render partial: 'photo_upload' .div = f.submit "登録する"
それぞれ説明をしていくと
=link_to 'ボタンを追加する', new_path(@user.id),remote: true
file inputをajaxで追加するリンクを実装する。
railsのlink_to
にremote: true
を設定するだけで、ajax通信が可能になる。
- @user.photos.each do |p| .div = image_tag p.image.url(:thumb) = check_box 'delete',"#{p.id}" = label_tag "delete[#{p.id}]", '削除'
更新時に既に写真が登録されていれば、その写真を表示し、同時に削除ボタンも実装する。
= render partial: 'add_photo_form'
_add_photo_form.html.slim
.div = fields_for "user[photos_attributes][#{Time.now.to_i}]" do |ff| = ff.file_field :image, onchange: "readUploadImage(this)" = image_tag ''
fields_for
を使用し、小モデルのinputを実装する。
なお、親モデルuser.rb
内でaccepts_nested_attributes_for
で子モデルを指定することを忘れずに。
accepts_nested_attributes_for :photos, allow_destroy: true
image_tag ''
はnew.html.slim
の一番上に記述されたjavascriptでプレビュー画像を表示するためのもの。
プレビュー画像はhtml5から実装されたFileRender
を使い、fileオブジェクトが保有するバッファの中身を読み込み、
それを上記のimage_tag
のsrc
に指定することで画像を表示させる。
ajaxでHTMLを追加する
=link_to 'ボタンを追加する', new_path(@car.id),remote: true
link_to
から、ajax送信でnew actionを実行する。
photo_controller.rb
if request.xhr? respond_to do |format| format.js end end
ajax送信があった場合は上記の通り、フォーマットはjsファイルでテンプレートを実行する。
new.js.slim
| $('[data-role="photo_area"]').append("#{escape_javascript(render partial: 'add_photo_form')}");
jsではappend()
でviewファイルadd_photo_form.html.slim
をセレクタで指定した箇所に追加する。
以上で下記のように動的にinputボタンを追加できるようになる。
保存する
photo_controller.rb
def update user = User.find(params[:id]) begin User.transaction do User.destroy(params[:delete] .select{ |k,v| v.to_i > 0}.keys) if params[:delete].present? user.update user_params if params[:user].present? end redirect_to redirect_path rescue => e p e.message end end
以上で、↓のように動的に写真を複数枚選択して、一括登録ができるようになる。
railsで部分にajaxを使う selectbox編
概要
selectboxの内容をajaxで動的に変更したいとき用のメモ。
AとBの2つのselectboxがあるとして、Aのselectboxで値を選択したら、それに連動した値の一覧をBのselectboxに反映するといった動作だ。
例としてAを都道府県、Bを市区町村とする。
これらはhas_manyの関係とする。
都道府県(prefecture) ==has_many=⇛ 市区町村(city)
簡単に流れをまとめると
1. jqueryで都道府県のselectboxが変更されたことを探知して、$.get
メソッドでajaxのactionを選択された都道府県idのparameterを渡して呼び出す。
2. ajax actionで都道府県idを基に市区町村をデータを取得する。
3. javascriptで上記で取得した市区町村データをselectboxに反映する。
formを作成
controller.rb
def new @form = Form.new @cities = City.none end
new.html.slim
javascript: $(window).load(function(){ var prefecture_select = $('[data-role="prefecture_select"]'); var url = "#{ajax_path}"; prefecture_select.change(function(){ $.get(url, {prefecture_id: prefecture_select.has('option:selected').val()}); }); }); div = form_for(@form, url:form_path) do |f| .div p= label :prefecture p= collection_select :prefecture,:prefecture_id, Prefecture.all, :id, :name,{selected: @prefecture_id.presence || 0,prompt: true},{'data-role'=>'prefecture_select'} .div p= f.label :city_id p= f.select :city_id, @cities,{prompt: true},{'data-role'=>'city_select'} .div = f.submit "登録"
今回はdata属性からDOM要素を取得するようにしている。
ajaxでselectboxの内容を変更
new.html.slim
javascript: $(window).load(function(){ var prefecture_select = $('[data-role="prefecture_select"]'); var url = "#{ajax_path}"; prefecture_select.change(function(){ $.get(url, {prefecture_id: prefecture_select.has('option:selected').val()}); }); });
jsでは、都道府県のselectboxが変更されたら、次に記述するajaxのaction宛に$.get
を実行する。
その際にparameterに市区町村のselectboxで選択された値を指定する。
controller.rb
def ajax @form = Form.new @cities = City.where(prefecture_id: params[:prefecture_id]) end
contoller内にajax actionを記述。
ajax actionでは、受け取ったparameterを基にselectboxで使用する市区町村オブジェクトを取得する。
ajax.js.slim
| $('[data-role="city_select"]').html("#{escape_javascript(options_for_select(@cities.map{|c| [c.name,c.id]}))}");
html()
でhtmlの書き換えを行う。
ajaxのactionで取得した市区町村データをoptions_for_select()
で都道府県のselectboxの値を更新する。
その際にselectboxで使えるようにobjectからmapを使って配列に変換してあげる。
また、action実行時にhtmlデータがない場合はjsが実行されるが、 下記のように明示的に記述しても良い。
respond_to do |format| format.js end
以上でajaxを使って動的にselectboxを変更することができる。
sinatra+bower+gulpでWEBアプリのスケルトンを作る
概要
簡単なWEBアプリを作りたい時にsinatraをよく使っている。
素のままでも便利なのだが、開発効率をもっと向上させるために、色々とカスタマイズした状態のスケルトンを作ることにした。
具体的に言うと下記のようなことをしたい。
sinatraと合わせて、フロントエンド界では必須ツールでもあるbowerやgulpを使うことにする。
使用する技術と役割
sinatra
- ruby製軽量フレームワーク
- http://www.sinatrarb.com/intro-ja.html
- Gem
- sinatra
- slim
- sqlite3
- activerecord
- railsのactiverecordが使えるようになる
- sinatra-config-file
- ymlファイルを参照可能にする
- shotgun
- ローカルでサーバー起動
- foreman
- コマンドを一括して実行
- rspec
- rack-test
sinatraはshotgunを使ってサーバーを立ち上げる。
slimはsinatra側でコンパイルする。
データベースを使いたい場合があるので、sqlite3とactiverecordを入れておく。
foremanはshotgunの実行とgulpの実行の際に使用する。
bower
- パッケージ管理
- http://bower.io/
- packages
- jquery
- bootstrap
とりあずjqueryとbootstrapをbowerで管理する。
gulp
- ビルドツール
- http://gulpjs.com/
- gruntも考えたけど、今はgulpの方がイケてる感??
- tasks
- sass&coffeeのcompile
- gulp-bower
- browsersync
- ブラウザの同期ツール
complieしたり、bowerで管理しているパッケージを扱ったりする。
あとはファイルとブラウザの同期。
sinatraの導入
bundle init
Gemfile
source "https://rubygems.org" gem 'sinatra' gem 'slim' gem "sqlite3" gem 'activerecord' gem 'sinatra-config-file' group :test do gem 'shotgun' gem 'foreman' gem 'rspec' gem "rack-test" end
bundle install --path vendor/bundle
Gemfileを生成し、sinatra等をインストールする。
次にsinatraで使用するディレクトリやファイルを作っていく。
まずは以下のような構造にする。
- app.rb
- 説明: ルーティングの実行(controller的な役割)
- config.rb
- 説明: アプリケーションの起動
- public
- css
- js
- 説明: 静的ファイルの置き場所
- views
- layout.slim
- index.slim
- 説明: viewファイル
- models
- init.rb
- hoge.rb
- 説明: modelファイル
- spec
- spec_helper
- models
- hoge_spec.rb
- 説明: specファイル
- hoge_spec.rb
- config.yml
- dev.db
上から説明していく。
app.rb
# encoding: utf-8 require 'sinatra/base' #ymlファイルを扱う require "sinatra/config_file" #slimを使用 require 'slim' #activerecordを使用 require 'active_record' #activerecordで使用するdbを指定 ActiveRecord::Base.establish_connection( adapter: 'sqlite3', database: 'dev.db' ) #models/init.rbの読み込み require_relative 'models/init' class App < Sinatra::Base register Sinatra::ConfigFile config_file 'config.yml' get '/' do slim :index end end
slimはsinatraで扱う必要があるので、Gemで対応。
話は変わるが、scssとcoffeeも以下のような記述をapp.rbにすることでsinatraで使えるようになる。
get %r{^/(.*)\.css$} do scss :"scss/#{params[:captures].first}" end get %r{^/(.*)\.js$} do coffee :"coffee/#{params[:captures].first}" end
だが、gulpでbowerのcss,js系のパッケージ管理をしたり、minifyしたりする想定なので、cssとjsはすべてgulpで管理することにした。
config.ru
require 'bundler' Bundler.setup root = ::File.dirname(__FILE__) require ::File.join(root, 'app') run App
sinatraの起動のために必要なファイル。
views/layout.slim
doctype html html head meta charset='utf-8' title sample app script src="js/program.js" link href="css/style.css" rel="stylesheet" body .wrap.container-fluid .main.row == yield
views/index.slim
top page
slimで書く。
ファイル名をlayoutにすれば勝手にlayoutファイルと認識してくれる。
yield
で個別のviewを読み出す。
models/init.rb
Dir[File.dirname(__FILE__) + '/*.rb'].each do |file| next if file == 'init' require_relative file end
app.rb
でmodels/init.rb
を読み込み、そこからmodels配下にあるrbファイルを全て読み込む形にした。
簡単ではあるが、sinatraの構築は以上で終了。
bowerの導入
npm install -g bower
パッケージ管理を行うbowerをインストールする。
node.jsが入ってない場合はインストールする。
bower init
bowerの初期化を行う。
色々と質問されるのでテキトウに答えていくと、bower.jsonが生成される
bower install --save jquery bootstrap
jqueryとbootstrapをインストールする。
bower.json
{ "name": "sinatra-skelton", "version": "0.0.0", "authors": [ "kame" ], "license": "MIT", "ignore": [ "**/.*", "node_modules", "bower_components", "test", "tests" ], "dependencies": { "bootstrap": "~3.2.0", "jquery": "~2.1.1" } }
bower.jsonにbootstrapとjqueryが記述されているのと、 bower_componentsディレクトリとその中にファイルが生成されていればOK。
gulpの導入
npm init
まずは初期化コマンドでpackage.json
を生成する。
今回、gulpで使用するプラグインは下記の通り。
- coffee-script
- gulpをcoffeeで書けるようにする
- gulp
- gulp本体
- gulp-coffee
- coffeeのコンパイル
- gulp-sass
- sassのコンパイル
- gulp-filter
- フィルタリング
- gulp-plumber
- コンパイルエラー時でも監視を中断させないようにする
- main-bower-files
- bowerのパッケージを扱う
- browser-sync
- ファイルとブラウザ同期
- del
- ファイルの削除
npm install --save-dev coffee-script gulp gulp-coffee gulp-sass gulp-filter gulp-plumber main-bower-files browser-sync del
installコマンドで一括インストールする。
--save-devを付けることで自動的にインストールしたプラグインをpackage.jsonに書き出してくれる。
gulpfile.coffee
g = require 'gulp' browserSync = require 'browser-sync' gulpFilter = require 'gulp-filter' mainBowerFiles = require 'main-bower-files' sass = require 'gulp-sass' coffee = require 'gulp-coffee' plumber = require 'gulp-plumber' del = require 'del' g.task 'bower-files', -> jsFilter = gulpFilter('**/*.js') cssFilter = gulpFilter('**/*.css') g.src mainBowerFiles() .pipe jsFilter .pipe g.dest('public/js/libs/') .pipe jsFilter.restore() .pipe cssFilter .pipe g.dest('public/css/libs/') g.task 'css', -> g.src 'assets/sass/*.scss' .pipe plumber() .pipe sass() .pipe g.dest('public/css') g.task 'js', -> g.src 'assets/coffee/*.coffee' .pipe plumber() .pipe coffee() .pipe g.dest('public/js') g.task 'bs', -> browserSync.init(null, { proxy: 'yourdomain' }) g.task 'bsReload', -> browserSync.reload() g.task 'clean' , -> del [ "public/js/libs/*" "public/css/libs/*" "public/js/*.js" "public/css/*.css" ] g.task 'build' , [ 'bs' 'bower-files' 'js' 'css' ] g.task 'default', ['clean','build'], -> g.watch 'assets/coffee/*.coffee' ,['js'] g.watch 'assets/sass/*.scss' ,['css'] g.watch 'app.rb', ['bsReload'] g.watch 'views/*.slim', ['bsReload']
上から順に説明していく。
require
g = require 'gulp' browserSync = require 'browser-sync' gulpFilter = require 'gulp-filter' mainBowerFiles = require 'main-bower-files' sass = require 'gulp-sass' coffee = require 'gulp-coffee' plumber = require 'gulp-plumber' del = require 'del'
最初に必要なプラグインを読みだしておく。
bower
g.task 'bower-files', -> jsFilter = gulpFilter('**/*.js') cssFilter = gulpFilter('**/*.css') g.src mainBowerFiles() .pipe jsFilter .pipe g.dest('public/js/libs/') .pipe jsFilter.restore() .pipe cssFilter .pipe g.dest('public/css/libs/')
bowerで管理しているパッケージを指定のディレクトリに配置する処理だ。
基本的な流れはgulp.src()
でファイルを指定し、何らかの処理をした後にgulp.dest()
でファイルの書き出し先を指定する。
ここでは、gulp.src()
でmainBowerFilesを指定し、そこからgulpFilter
でjsとcss読み込み、gulp.dest()
でpublicディレクトリ配下に書き出すといった流れだ。
gulpの基本的な使い方は以下の記事がわかりやすい。
タスクランナーgulp.js最速入門
sass,coffeeのcompile
g.task 'css', -> g.src 'assets/sass/*.scss' .pipe plumber() .pipe sass() .pipe g.dest('public/css') g.task 'js', -> g.src 'assets/coffee/*.coffee' .pipe plumber() .pipe coffee() .pipe g.dest('public/js')
bowerの処理とほとんど一緒で、scssとcoffeeファイルを読み出し、コンパイルして、publicディレクトリ配下に書き出している。
plumber()
はコンパイルエラーが起きても監視(watch)を続行するためのもの。
browsersync
g.task 'bs', -> browserSync.init(null, { proxy: 'yourdomain' }) g.task 'bsReload', -> browserSync.reload()
BrowserSyncの設定タスクとファイルのwatchにつかうタスクを記述。
今回のようにプログラムを使用する場合はそれが動くサーバーを立ち上げて、設定タスクに使用するドメインをproxyで指定する必要がある。
livereloadというのもあるんだが、BrowerSyncはファイルとブラウザの同期だけではなくて、ブラウザ間同期も可能なのと、なんとスクロールやクリック等の操作も同期される。
なので、1つのブラウザを操作するだけで他のブラウザ(端末を選ばず)も同時に動作チェックすることができるので、かなり効率があがりますね。
実行時にBrowserSyncの管理画面が本ちゃん以外のポートで立ち上がって、色々カスタマイズできたり、変更履歴を簡単に確認できたりととても便利そう。
clean
g.task 'clean' , -> del [ "public/js/libs/*" "public/css/libs/*" "public/js/*.js" "public/css/*.css" ]
cleanタスクの定義。 cssやjsが生成される前に、以前のデータを削除しておく。
build
g.task 'build' , [ 'bs' 'bower-files' 'js' 'css' ]
buildタスクとして、各実装をまとめる。
watch
g.task 'default', ['clean','build'], -> g.watch 'assets/coffee/*.coffee' ,['js'] g.watch 'assets/sass/*.scss' ,['css'] g.watch 'app.rb', ['bsReload'] g.watch 'views/*.slim', ['bsReload']
cleanタスクとbuildタスクをdefaultタスク統合。
defaultタスクは、文字通りデフォルトで実行されるタスクなので、gulp
コマンドだけで実行できる。
同時にcompileとBrowserSyncの実装をファイル変更のたびに行うために、watchで監視対象ファイルを指定する。
以上でgulpの設定は完了。
実行
foremanを使う
ローカルサーバーの立ち上げも一緒にやってしまいたいので、shotgunとgulpコマンドをforemanを使って一括で実行する。
その際にshotgunで生成されるIPアドレスをgulpfile.coffee
のBrowserSyncのドメインに設定する。
Procfile
application: bundle exec shotgun -o localhost --port=6000 gulp: gulp
gulpfile.coffee
g.task 'bs', -> browserSync.init(null, { proxy: 'localhost:6000' })
shotgunでport6000でサーバーを立ち上げ、そのサーバーのproxyの設定をBrowserSyncで行う。
bundle exec foreman start
20:14:40 application.1 | started with pid 14604 20:14:40 gulp.1 | started with pid 14605 20:14:40 gulp.1 | [20:14:40] Requiring external module coffee-script/register 20:14:41 application.1 | [2015-03-25 20:14:41] INFO WEBrick 1.3.1 20:14:41 application.1 | [2015-03-25 20:14:41] INFO ruby 2.1.2 (2014-05-08) [x86_64-linux] 20:14:41 application.1 | [2015-03-25 20:14:41] INFO WEBrick::HTTPServer#start: pid=14604 port=6000 20:14:43 gulp.1 | [20:14:43] Using gulpfile ~/skelton/sinatra-skelton/gulpfile.coffee 20:14:43 gulp.1 | [20:14:43] Starting 'clean'... 20:14:43 gulp.1 | [20:14:43] Finished 'clean' after 8.16 ms 20:14:43 gulp.1 | [20:14:43] Starting 'bs'... 20:14:43 gulp.1 | [20:14:43] Finished 'bs' after 11 ms 20:14:43 gulp.1 | [20:14:43] Starting 'bower-files'... 20:14:43 gulp.1 | [20:14:43] Starting 'js'... 20:14:43 gulp.1 | [20:14:43] Starting 'css'... 20:14:43 gulp.1 | [BS] Proxying: http://localhost:6000 20:14:43 gulp.1 | [BS] Now you can access your site through the following addresses: 20:14:43 gulp.1 | 20:14:43 gulp.1 | [BS] Local (this machine): 20:14:43 gulp.1 | [BS] >>> http://localhost:3002 20:14:43 gulp.1 | [BS] External (other devices etc): 20:14:43 gulp.1 | [BS] >>> http://133.xxx.xxx.xxx:3002 20:14:43 gulp.1 | 20:14:43 gulp.1 | [20:14:43] Finished 'js' after 152 ms 20:14:43 gulp.1 | [20:14:43] Finished 'css' after 146 ms 20:14:43 gulp.1 | [20:14:43] Finished 'bower-files' after 207 ms 20:14:43 gulp.1 | [20:14:43] Starting 'build'... 20:14:43 gulp.1 | [20:14:43] Finished 'build' after 26 μs 20:14:43 gulp.1 | [20:14:43] Starting 'default'... 20:14:43 gulp.1 | [20:14:43] Finished 'default' after 24 ms
あとはforemanを実行すればshotgunとgulpが実行され、サーバーが立ち上がるはずだ。
git cloneして使う
git cloneして、いくつかコマンドを打てば、すぐに使用できるようになっている。
npm install
package.json
に書かれたパッケージをインストール
npm install -g bower
bowerをインストールしていなければインストール。
bower install
bower.json
に書かれたパッケージをインストール。
Procfile
内のshotgunのドメインとgulpfile.coffee
内のBrowserSyncのproxyドメインを指定のドメインに変更。
bundle exec foreman start
shotgunとgulpの実行
以上でカスタマイズされたsinatraでの開発環境が整う。
最後に
下記のQitaの記事にもあるように、railsとか使う場合でもjsとかのパッケージ管理はgemとか素でいれるんじゃなくて、bower-railsとか使ってbowerで管理したほうが良さげな感じですね。
gemだと対応遅い場合もあるし、素でいれるとしても更新作業とか面倒ですしね。
http://qiita.com/reikubonaga/items/5a6037e067b79e5f9849
gulpに関しては今回のようにちょっとしたWEBアプリや静的サイトを作る場合に適してるのかなー。
ただ下記のような記事もあった。
Gulp on Rails: Replacing the Asset Pipeline
railsの場合、assetsは時代遅れで、gulpをrailsに統合して使うべきだと。
railsとgulpを統合してて、railsの仕様通りapp/assetsを読みに行って、そこからgulpでbuildされたファイルを見に行く感じかな。
また、記事の中でフロントエンドよりの最新で便利な技術使いたいなら、railsに任せるのではなくて、そこは分けて使いましょうよと、言っていますね。
For me, there's no better tool than Gulp to glue these incredible build tools together. Unfortunately, Rails disagrees and reeeally wants us to use its tech stack to handle assets. Sure, there are a few gems out there that let you cobble together some of the technology above, but I've found that they quickly fall behind, don't expose all the same options, and some things just aren't possible.
あとはこんな記事も。
http://qiita.com/oreshinya/items/3d025dde2edc56622cc4
この辺に関しては、もう少し考えてみることにする。
参考URL
rails4.2でAuthlogicを使ってみた
railsでユーザー登録機能をつける際は勝手に色々とユーザー登録周りの機能を付けてくれるDeviseを普段よく使ってる。
しかし、少しレールから外れたことをしようとすると、Deviseのソースをoverrideしたり、直接さわらないといけなかったりと結構面倒くさいことをしないといけない。それによりコードが複雑になることによる管理コストも上がってきてしまう。
仕事でDeviseを使っていて、痒いところに手が届かなくなってしまったので、Deviseより機能がシンプルなAuthlogicを使うことにした。
簡単ではあるが、その際の構築メモをまとめておく。
導入
gem 'authlogic'
bundle install
DB/Migrate
db/migrate/create_users.rb
class CreateUsers < ActiveRecord::Migration def change create_table :users do |t| t.string :email, null: false t.string :crypted_password, null: false t.string :password_salt, null: false t.string :persistence_token, null: false t.integer :login_count, null: false, default: 0 t.integer :failed_login_count, null: false, default: 0 t.datetime :last_request_at t.datetime :current_login_at t.datetime :last_login_at t.string :current_login_ip t.string :perishable_token, null: false t.timestamps end add_index :users, :email,unique: true add_index :users, :perishable_token end end
rake db:migrate
上記のカラムは全てauthlogicで使用するカラム。
また、perishable_token
については、パスワードの再設定機能をつける際に必要なカラムなので、必要に応じて設定すると良い。
ここでの説明は省くので下記を参照
authlogic-password-reset-tutorial
ファイル構成
- app
- controllers
- user
- registrations_controller.rb
- sessions_contrller.rb
- user
- views
- user
- registrations
- new.html.slim
- sessions
- news.html.slim
- registrations
- user
- controllers
ユーザー周りの機能はuserディレクトリにまとめる。
Routeの設定
config/routes.rb
get "sign_up" => "user/registrations#new" get "sign_in" => "user/sessions#new" delete "sign_out" => "user/sessions#destroy" namespace :user do resources :registrations, only: :create resources :sessions, only: :create end
登録(registration)とログイン(session)のそれぞれのルーティングの設定をする。
Modelの実装
app/models/user.rb
acts_as_authentic do |c| c.login_field = :email c.validates_uniqueness_of_email_field_options({value: true}) c.merge_validates_length_of_password_field_options({minimum: 6}) end
ユーザーモデルに上記を設定する。
基本的には中の記述は必要ないが、どの項目でログインするかだとか、validationの細かい設定だとかを設定できる。
とりあえず、email
をunique
にしたり、password
の最小値を設定したりした。
何が出来るかは下記参照。
ドキュメント - Module: Authlogic::ActsAsAuthentic
ドキュメント見難い・・・もっとわかりやすくまとめてほしい
ControllerとViewの実装
まずは基板から
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base protect_from_forgery with: :exception #ログインユーザーのセット helper_method :current_user, :logged_in? protected #UserSessionから現在ログインしているユーザーを取得 def current_user_session return @current_user_session if defined?(@current_user_session) @current_user_session = UserSession.find end #現在のログインユーザーの値を設定 def current_user return @current_user if defined?(@current_user) @current_user = current_user_session && current_user_session.user end #ログインが必要 def require_login unless current_user redirect_to sign_in_path return false end end #ログイン状態か判定 def logged_in? current_user_session != nil end #ログイン後のパス def after_login_path mypage_path end end
ログインユーザーの情報取得機能やログインのフィルター機能等を記述する。
説明はコメントで書いている通り。
UserSession
は後ほどログイン機能追加のところで説明。
app/controllers/user/base_controller.rb
class User::BaseController < ApplicationController layout "application" before_action :require_login end
registrations_controller.rb
とsessions_controller.rb
の親となるcontrollerを作成。
今後、registrations_controller.rb
にユーザー情報の変更機能等を付けることを想定し、before_action
にrequire_login
を設定。
登録時とログイン時以外は上記で権限の制限をする。
ユーザー登録
やっと本題。
app/controllers/user/registrations_controller.rb
class User::RegistrationsController < User::BaseController skip_before_action :require_login, only: [:new, :create] def new @user = User.new end def create @user = User.new(user_params) begin User.transaction do if @user.valid? @user.save! flash[:notice] = '会員登録が完了しました。' redirect_to after_login_path else render action: :new end end rescue => e p e.message end private def user_params params.require(:user).permit(:email,:password,:password_confirmation) end end
app/views/registrations/news.html.slim
= form_for(@user,url: user_registrations_path,method: :post, html: {class: 'form-horizontal'}) do |f| .form-group p= f.label :email,class: 'col-sm-4 control-label' .col-sm-8 .error= errors_for @user, :email p= f.text_field :email, autofocus: true, class: 'form-control' .form-group p= f.label :password,class: 'col-sm-4 control-label' .col-sm-8 .error= errors_for @user, :password - if @validatable em | ( = @minimum_password_length | 文字以上) p= f.password_field :password, autocomplete: "off", class: 'form-control' .form-group p= f.label :password_confirmation,class: 'col-sm-4 control-label' .col-sm-8 .error= errors_for @user, :password_confirmation p= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control' .form-group .col-sm-offset-4.col-sm-8 = f.submit "登録",:class => 'btn btn-default'
newアクションとcreateアクションはskip_before_action
でrequire_login
を実行しないようにする。
viewに関してはbootstrap
を使っているのであしからず。
以上でユーザー登録ができるようになる。
パスワードの入力確認や暗号化は勝手によろしくやってくれる。
ユーザーログイン
app/controllers/user/sessions_controller.rb
class Account::SessionsController < Account::BaseController skip_before_filter :require_login, only: [:new, :create] def new @user_session = UserSession.new end def create @user_session = UserSession.new(user_session_params) if @user_session.save flash[:notice] = 'ログインしました。' redirect_to after_login_path else render action: :new end end def destroy current_user_session.destroy flash[:notice] = 'ログアウトしました。' redirect_to root_url end private def user_session_params params.require(:user_session).permit(:email, :password) end end
app/views/user/sessions/new.html.slim
h2 ログイン div = form_for(@user_session, url: user_sessions_path, method: :post, html: {class: 'form-horizontal'}) do |f| = errors_for_session(@user_session) .form-group p= f.label :email,class: 'col-sm-4 control-label' .col-sm-8 = errors_for @user_session, :email p= f.text_field :email, autofocus: true, class: 'form-control' .form-group p= f.label :password,class: 'col-sm-4 control-label' .col-sm-8 = errors_for @user_session, :password p= f.password_field :password, autocomplete: "off", class: 'form-control' .form-group .col-sm-offset-4.col-sm-8 = f.submit "ログイン",:class => 'btn btn-default'
ログインの管理はUserSession
で行う。
UserSession
はデータベースに登録するものではないが、ActiveRecordのようにsaveメソッドが使えたりする。
その他
app/views/application.html.erb
- if logged_in? = link_to "マイページ", mypage_path = link_to "ログアウト", sign_out_path,:method => :delete - else = link_to "ログイン", sign_in_path = link_to "新規登録", sign_up_path
上記のようにリンクを追加する。
最後に
上記の場所以外のどこからでも簡単に登録・ログイン機能が実装可能だ。
ただ、メールの送信やパスワード再発行などの細かい機能は自分で実装しなければいけないので、凝ったことをしないのであればDeviseを選ぶのが無難かなあ。