ホーム ブログ ページ 43

【Rails】RailsにAdminLTEを導入【AdminLTE】

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

RailsにAdminLTEを導入する手順を紹介します。

1. AdminLTEのダウンロード

ここからダウンロード (2015/03/31現在)

2. ダウンロードしたファイルを解凍し、設置

「dist」と「plgins」のフォルダを使用。下記に設置します。 ※/lib/にパスが通っている必要がある

RAILS_ROOT/lib/assets/adminlte/

3. 読み込む必要があるjsとcssをapplication.jsとapplication.cssに記入(導入するファイルは解凍したフォルダのindex.htmlを参考すると良い)

$ vim app/assets/javascripts/application.js

・
・
・
//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require adminlte/dist/js/app.js                         ここら辺から読み込む必要があるファイルを記述
//= require adminlte/dist/js/raphael-min
//= require adminlte/plugins/morris/morris.min
//= require adminlte/plugins/sparkline/jquery.sparkline
//= require adminlte/plugins/jvectormap/jquery-jvectormap-1.2.2.min
//= require adminlte/plugins/jvectormap/jquery-jvectormap-world-mill-en
//= require adminlte/plugins/knob/jquery.knob
//= require adminlte/plugins/daterangepicker/daterangepicker
//= require adminlte/plugins/datepicker/bootstrap-datepicker
//= require adminlte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min
//= require adminlte/plugins/slimScroll/jquery.slimscroll.min
・
・
・

$ vim app/assets/stylesheets/application.css

 ・
 ・
 ・
 *= require adminlte     ←このファイルにAdminLTEの読み込むcssをまとめる
 ・
 ・
 ・

$ vim app/assets/stylesheets/adminlte.css.scss

@import url('https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css');
@import url('http://code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css');
@import 'dist/css/AdminLTE.min';
@import 'dist/css/skins/_all-skins.min';
@import 'plugins/iCheck/flat/blue';
@import 'plugins/morris/morris';
@import 'plugins/jvectormap/jquery-jvectormap-1.2.2.css';
@import 'plugins/datepicker/datepicker3';
@import 'plugins/daterangepicker/daterangepicker-bs3';
@import 'plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min';

以上で完了

flashのメッセージ以外の使い道

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

社会人2年目になった、まだまだ技術的に新人域を抜けきっていない気がする中西です。

今回は、flashが使えるのは、メッセージだけでないことについて紹介します。

環境

Rails 4.1.4

Ruby 2.1.2

flashに渡せる値

flashトとは、セッションに一時的にメッセージを入れておける機能です。

エラー等のメッセージを値として渡し、リダイレクト先でそのメッセージを表示する使い方が多いと思います。

test_controller.rb
flash[:notice] = "テストメッセージです。"
flash[:error] = "エラーが発生しました。"
redirect_to test_path
test/show.html.slim
= flash[:notice]
= flash[:error]

しかし、flashの実態はHashです。

そのため、そのため、任意のキーを使用することができます。

したがって、メッセージの他に、独自に設定したキーで、名前やフラグを値として渡すこともできます。

 名前の例

test_controller.rb
flash[:test_name] = "welcome"
redirect_to test_path
test/show.html.slim
- if flash[:test_name]
  = link_to "#{flash[:test_name]}", "http://www.test.#{flash[:test_name]}"

 flagの例

test_controller.rb
flash[:test_view_flag] = true
redirect_to test_path
test/show.html.slim
- if flash[:test_view_flag]
  | テスト経由

まとめ

flashを一時的なsessionとしてうまく使うと、様々なことができそうです。

参照

http://guides.rubyonrails.org/action_controller_overview.html#the-flash

http://thepugautomatic.com/2012/08/the-rails-flash/

CentOS MuninとプラグインのInstall方法

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

rickNo36です。
最近CentOS7でmunin入れたのでインストール方法を記載します。

環境

CentOS7
Apache(yumにてinstall済)

Muninインストール

yum install epel-release
yum install munin munin-node
systemctl start munin-node
apachectl configtest
systemctl restart httpd.service

アクセス

http://xxx/munin
これだけ、とても簡単

ラグイン追加

設定されているplugin
/etc/munin/plugins
用意されているplugin
/usr/share/munin/plugins

追加例:Apache

# アクセス数
ln -s /usr/share/munin/plugins/apache_accesses /etc/munin/plugins/apache_accesses
# プロセス数
ln -s /usr/share/munin/plugins/apache_processes /etc/munin/plugins/apache_processes
# データ転送量
ln -s /usr/share/munin/plugins/apache_volume /etc/munin/plugins/apache_volume
cd /etc/httpd/conf
vim httpd.conf
———————
nclude conf.modules.d/*.conf
下記追加
ExtendedStatus On

SetHandler server-status
Require local

———————
vim extra/httpd-vhosts.conf
———————

ServerName hoge

RewriteEngine On
RewriteRule ^/balancer-manager$ /balancer-manager/ [L]
RewriteRule ^/server-status$ /server-status/ [L]
RewriteRule ^(.*)$ [R=404,L]

SetHandler server-status
Order deny,allow
Deny from all
Allow from 127.0.0.1

SetHandler balancer-manager
Order Deny,Allow
Deny from all
Allow from 127.0.0.1

———————
systemctl restart httpd.service
systemctl restart munin-node

Rails4 + Mongoidでデータ収集するあれこれ

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

MongoDBのObjectId型を物検索するのににこれたたいいかか、

  1. ObjectId型の_idを着する
  2. $ whereディレクター
  3. 分類の指定方法
  4. モンゴイドのセレクション展開の指定方法
  5. 集合体group_by?

 ObjectId型の_idを着検索する

▼サンプル

> db.members.find();
{"_id":ObjectId( "54daf7307da4107a3694f5af")、 "name": "hoge"、 "count":1、 "status":1123、 "created_at":ISODate( "2015-02-11T12:20:18.766Z" )、 "updated_at":ISODate( "2015-02-11T12:20:18.766Z")、 "deleted_at":0}

MongoDBの_idはObjectId型要素ので、

ざに「/ ^ 54d /」九なぷあり勝しませんでした。

文字列型に変換して「ObjectId( “」からなっ一致するようにすししししますがううーん。

Member.where( '$ where' => 'function(){return this._id.match(/ ^ ObjectId \(\ "%s /)}'%Regexp.escape(@ search.id))

とたたら定的に変換する

http://docs.mongodb.org/manual/reference/method/ObjectId.valueOf/

ObjectId.valueOf()を外とカッコ内の文字列をしてせます。

ObjectId( "54daf7307da4107a3694f5af")。valueOf()=> "54daf7307da4107a3694f5af"

をしたばば↓このように書けます。(※ももするばができます)

Member.where( '$ where' => 'function(){return this._id.valueOf()。match(/ ^%s /);}'%Regexp.escape(@ search.id))
Member.where( '$ where' => 'this._id.valueOf()。match(/ ^%s /)'%Regexp.escape(@ search.id))

省略、検索条件が$ where省略の属性は$ whereも省略可能。

Member.where( 'this._id.valueOf()。match(/ ^%s /)'%Regexp.escape(@ search.id))

使いまわすそうえる、モデルの方でスコープにPlanskお実行かと。

コントローラ
Member.id(@ search.id)

モデル
scope:id、->(id){'this._id.valueOf()。match(/ ^%s /)'%Regexp.escape(id)if id.present?}

mongoシェルメソッド

http://docs.mongodb.org/manual/reference/method/

IntegerもtoString()で文字列に変換すし、正規表現での検索が可能です。

 $ where

ここ$ whereです。

http://docs.mongodb.org/manual/reference/operator/query/where/

た内でjavascriptを実行したいいは$ whereをするようにとのこと。

公式のコメント通りですが、ざ解説

使いろろはえくらありようです。

  • 陳ができない
  • 絶された内でありない(表レベルであること)
  • $ whereBEGINの条件と國可能。先に$ whereBEGINの変形を評価する。(その結婚が0の属性$ where内は評価されない)
  • ファル3微妙
  • ず$ whereえずにありならそっちをててね
  • でできてなら、$ whereEVERの条件もいして、$ whereでで立てをすると、でなるのできる

検索条件の指定方法

ももな指定方法がますます。

モンゴイドの選択

http://mongoid.org/en/origin/docs/selection.html#standard

http://mongoid.org/en/origin/docs/selection.html#symbol

▼名前に「ほ」をするもの

(文字列型のフィールドフィールド正規表現表現が有効です)

Member.where(name:/ ho /)

▼1と3コンポーネント

Member.where(:status.all => [1,3])

▼1と3をこれ

Member.where(:status.in => [1,3])

▼1と3をこれないもの

Member.where(:status.nin => [1,3])

▼ステータスがnilfalseRefer

Member.where(:status.exists => true)

▼ステータスが1

Member.where(:status.gt => 1)

▼ステータスが1>

Member.where(:status.gte => 1)

▼ステータスが3

Member.where(:status.lt => 3)

▼ステータスが3

Member.where(:category.lte => 3)

▼ステータスが1

Member.where(:category.ne => 1)

▼updated_atg2015-02-1110:15:00.000円

Member.where(:updated_at.gte => '2015-02-11 10:15') 

▼updated_atが2015-02-1110:15:01.000前前

 ミリ秒表ので、「2015-02-11 10:15.725」等男勝表1秒後のdatetimeで

Member.where(:updated_at.lt => Time.at(Time.parse( '2015-02-11 10:15')。to_i + 1))

モンゴイドのセレクション展開の選択方法

Model.where()の術モンゴイドのセレクションダブルモデル、

なりのように指定する、MongoDBのシェルとありのくらがいようです。

db = Mongoid :: Sessions.default
コレクション= db [:models]
collection.find()

矯正

Model.collection.find()

※「Mongoid :: Sessions.default」はconfig / mongoid.ymlの設定内容?

もはもももをあります。

mongoDBdb.models.find({count:{‘$ gte’ => 1}})
RailsModel.where(:count.gte => 1)モンゴイド::基準
RailsModel.collection.find(count:{‘$ gte’ => 1})原付::クエリ

オブジェクトオブジェクトオブジェクトオブジェクトオブジェクトオブジェクト。

基準= Model.where(:count.gte => 1)

ppcriteria.class
 => Mongoid :: Criteria

ppcriteria.class.ancestors
 => [Mongoid :: Criteria、
 Mongoid :: Sessions :: Options、
 Mongoid :: Criteria :: Scopable、
 Mongoid :: Criteria :: Modified、
 Mongoid :: Criteria :: Marshalable、
 Mongoid :: Criteria :: Inspectable、
 Mongoid :: Criteria :: Findable、
 Origin :: Queryable、
 Origin ::オプション、
 Origin :: Selectable、
 Origin :: Aggregable、
 起源::マージ可能、
 Mongoid :: Contextual、
 可算、
 オブジェクト、
 Delayed :: MessageSending、
 Mongoid :: Extensions :: Object、
 BSON :: Object、
 Origin :: Extensions :: Object、
 ActiveSupport :: Dependencies :: Loadable、
 PP :: ObjectMixin、
 V8 :: Conversion :: Object、
 JSON :: Ext :: Generator :: GeneratorMethods :: Object、
 カーネル、
 BasicObject]
moped = Model.collection.find(count:{'$ gte' => 1})

pp moped.class
 =>原付::クエリ

pp moped.class.ancestors
 => [Moped :: Query、
 Mongoid :: QueryCache :: Query、
 Mongoid :: QueryCache :: Cacheable、
 可算、
 オブジェクト、
 Delayed :: MessageSending、
 Mongoid :: Extensions :: Object、
 BSON :: Object、
 Origin :: Extensions :: Object、
 ActiveSupport :: Dependencies :: Loadable、
 PP :: ObjectMixin、
 V8 :: Conversion :: Object、
 JSON :: Ext :: Generator :: GeneratorMethods :: Object、
 カーネル、
 BasicObject]

 aggregatedegroup_byする

MongoDBのシェルがされて↓ここにあるメソッドもメソッドを…

http://docs.mongodb.org/manual/reference/method/

data = Member.collection.aggregate({'$ group' => {'_id' => '$ count'、 'count' => {'$ sum' => 1}}})

=> [{"_ id" => 3、 "count" => 1}、{"_ id" => 2.0、 "count" => 2}、{"_ id" => 1.0、 "count" => 1} ]

できました!

pp data.class
 =>配列

pp data.class.ancestors
 => [配列、
 Mongoid :: Extensions :: Array、
 BSON :: Array、
 BSON :: Encodable、
 Origin :: Extensions :: Array、
 カラム化、
 V8 :: Conversion :: Array、
 JSON :: Ext :: Generator :: GeneratorMethods :: Array、
 可算、
 オブジェクト、
 Delayed :: MessageSending、
 Mongoid :: Extensions :: Object、
 BSON :: Object、
 Origin :: Extensions :: Object、
 ActiveSupport :: Dependencies :: Loadable、
 PP :: ObjectMixin、
 V8 :: Conversion :: Object、
 JSON :: Ext :: Generator :: GeneratorMethods :: Object、
 カーネル、
 BasicObject]

SQL特徴group_byとは別ですが、比較にらない

Enumerable#group_byがますます。

(Mongoid :: CriteriaもMoped :: QueryもEnumerableファンを実装してます)

Member.where(:count.gte => 1).group_by {| member | member.status}
Member.collection.find(count:{'$ gte' => 1})。group_by {| member | member [:status]}

jsファイルを渡して、言いからコマンドは

mongo sample_dev〜 / mongo / batch.js

SQLとの比較表も定にありました。

決定に。

http://docs.mongodb.org/manual/reference/sql-comparison/

浮動小数点誤差対策

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

小数の計算が必要な場面というのは多々ある。Rubyでは、例えば「1.5」と書けばFloatクラスのオブジェクトになるが、Floatクラスで注意しなければならないのが、浮動小数点誤差である。

この浮動小数点誤差に関して、原因、注意点、対策方法を纏める。

 浮動小数点誤差が発生する必要条件

Floatクラスのオブジェクトは内部的に2進数にて表現される。

2進数で表現した場合に無限小数となる数の演算は全て、浮動小数点誤差が発生する可能性がある。

「2進数で表現した場合に有限小数となる」とは、既約数で表現した場合の分母に2以外の素因数がない事である。

以下に例を示す。

1.5 = 3/2 … 分母を素因数分解すると2^1なので浮動小数点誤差は発生しない。

0.75 = 3/4 … 分母を素因数分解すると2^2なので浮動小数点誤差は発生しない。

0.1 = 1/10 … 分母を素因数分解すると2^1 * 5^1 なので浮動小数点誤差が発生する可能性がある。

 浮動小数点誤差が発生しがちな計算の例

浮動小数点誤差が発生しがちな計算は、例えば消費税の計算がある。

浮動小数点誤差の知識はあったとしても、意識していないと、225円の商品の税込み価格計算で「225 * 1.08」の様に書いてしまいがちである。

この計算は「243.0」を期待しているが、実際は「243.00000000000003」となる。

この結果の問題点は、税込価格が端数切り上げで計算している場合に、本来の価格より1円高くなってしまう点である。

(225 * 1.08).ceil

=> 244

 浮動小数点誤差に対する対策

対策1 : 演算の順序を工夫する

小数は分数で表現できる。分数は除算で表現できる。

整数の集合は、加算・減算・乗算において閉じているので、

除算を最後の1回のみ行う様に式を置き換える。

(225 * 108 / 100.0).ceil

=> 243

デメリット

・有効になる場面が限られている。

・複雑な演算には向かない。

対策2 : BigDecimalクラスを使用する

人間が数値として表現できる数は殆どが10進法の有限小数または整数である。

10進法で表記した各桁の精度を落とさずに演算が可能なBigDecimalクラスを使用すれば、有効桁数がそれ程多くない数同士の加算・減算・乗算では誤差は発生しない。

(恐らく電卓と同じ様なロジックになっていると思われる)

(225 * BigDecimal(“1.08”)).ceil

=> 243

デメリット

・分子の素因数に2, 5以外の数を持つ既約有理数による除算は誤差が発生する可能性がある。

・String型に変換する必要がある。

・遅い。

対策3 : Rationalクラスを使用する

有理数の集合は四則演算において閉じている(0による除算を除く)ので、

既約有理数の分母分子の精度を落とさずに演算が可能なRationalクラスを使用すれば、有理数同士の四則演算では0による除算を除いて誤差は発生しない。

(225 * 1.08r).ceil

=> 243

デメリット

・数値的に求めるには型変換をしなければならない。

 Rationalクラスを使用する場合の注意点

Rationalクラスは丸め誤差の影響を考えなくて良い便利なクラスであるが、RationalオブジェクトとFloatオブジェクトの四則演算を行う場合、RationalオブジェクトはFloatオブジェクトに暗黙型変換されて演算される為、注意が必要となる。

(例えば、「225.0 * 1.08r」は「225.0 * 1.08」と同じになってしまう)

つまり型の優先度としては、Rationalは、Integerより強く、Floatより弱い様である。

Rationalクラスの利点を活用するのであれば、Floatオブジェクトをto_rメソッドによってRationalオブジェクトにしてから演算を行う必要がある。

テストデータ作成時の注意点

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

テストデータで決定する数値は無作為に決定していると、予期せぬ矛盾が発生する事がある。

その注意点を記述する。

 問題点

例えば商品のテストデータを作成する場合を考える。

商品の値段は当然自然数である。

これは税抜価格であっても税込価格であっても当てはまるが、1点異なる部分がある。

税抜価格は単純に決定された値であり、この値を導いている値はないのに対して、税込価格は税抜価格から導かれて決定する。

つまり税込価格は税抜価格の関数になっている。

この関数をfとした場合、f(x)は

f(x) = floor(1.08x)

と表現できる。このfはN→Nの写像になっており、下記の様にF(y)を定義すると、Fはfの逆関数になる

F(y) = ceil(y/1.08)

テストデータとして、税込価格を決定する場合、ランダムに自然数が発生する様なプログラムでは、例えば1700という値になる可能性がある。これを関数Fによって

F(1700) = 1575

と税抜価格を算出したとすると、実際のf(1575)は1701の為、誤差が発生する。

 原因

この誤差はfの逆関数が存在する事と矛盾する様に見えるが、そうではなく、この写像fがN→Nにおいて全射でない点が問題である。

このfの定義域はN全体であるが、値域はN全体ではなく、1700というのが値域外の数値である事が原因である。

(つまり f(x) = 1700 を満足するxは存在しない)

 結論

テストデータを作成する際は、それを導いている値があるかないかを意識する必要がある。

導いている値がある場合、その関数の写像において全射であるかを考え、

全射でない場合は、値域外の数値にならない様、データを工夫する必要がある。

devise ログイン後のリダイレクトについて(POST編)

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Rails の Devise でログイン後にログインする直前のPOSTリクエストにリダイレクトさせます。

 目標

RailsでPOSTリクエストではリダイレクトできないため、deviserログイン後はGETリクエストのみリダイレクトできます。

下記の場合、お気に入り登録が出来ないし、お気に入り一覧にリダイレクトできないです。

未ログイン⇒商品詳細⇒お気に入り登録(POST)⇒ログイン画面⇒ログインする⇒お気に入り一覧

 対策

POSTなので、直接遷移が出来なくて、after_login_to_postを作成して、その後自動サブミットすれば、POSTリクエストにリダイレクトを実現します。

動き:

未ログイン⇒商品詳細⇒お気に入り登録(POST)⇒ログイン画面⇒ログインする⇒after_login_to_post⇒真っ白ページ(自動サブミット)⇒お気に入り一覧

 セッションに POST リクエストのパラメータを保存

POSTリクエスト中で、POSTリクエストのパラメータ、コントローラ、アクションをセッションに保存します。

class CustomersFavoritesController < ApplicationController
  before_action :authenticate_customer!, except: :create
  def create
    if current_customer.signed_in?
      current_customer.favorites.create(product_id: params[:id]) unless current_customer.favorites.find_by(product_id: params[:id])
      redirect_to action: :index
    else
      #セッションに POST リクエストのパラメータを保存
      session[:after_login_to_post] = {
        controller_name: controller_name,
        action_name: action_name,
        params: { id: params[:id] }
      }
      redirect_to new_customer_session_path
    end
  end
end

 ログイン後のリダイレクトをカスタマイズ

ログイン後、セッションにPOST情報があれば、after_login_to_post_pathにリダイレクトさせます。

class CustomersSessionsController < Devise::SessionsController
  # ログイン後、リダイレクト先メッソドafter_sign_in_path_forをオーバーライドする。
  def after_sign_in_path_for(resource)
   #お気に入り登録(POST)の場合、after_login_to_post_pathに遷移します。
    if session[:after_login_to_post]
      after_login_to_post_path
    else
      super
    end
  end
end

 ログイン後にセッション情報からログイン直前のページ情報を復元

セッション情報から、POSTリクエストの情報を取得し、設定します。

class TopController < ApplicationController
  def after_login_to_post
   #セッションにafter_login_to_postがあれば、
    if session[:after_login_to_post]
      @back_controller = session[:after_login_to_post]['controller_name']
      @back_action = session[:after_login_to_post]['action_name']
      @params = session[:after_login_to_post]['params']
   #セッションをクリア
      session[:after_login_to_post] = nil
      render :after_login_to_post, layout: false
    else
      #直接アクセスが来たら、TOPに遷移する。
      redirect_to top_path
    end
  end
end

 真っ白VIEW

after_login_to_postのVIEWは自動サブミットします。

一瞬だけ真っ白なページが表示されますが、

自動サブミットによって、POST送信が出来ます。

after_login_to_post.slim

doctype transitional
html[xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja" dir="ltr"]
  head
    meta[http-equiv="Content-Type" content="text/html; charset=UTF-8"]
    title = '戻る'
  body onLoad="document.forms[0].submit()"
    - if @back_controller && @back_action && @params
      = form_tag( { controller: @back_controller, action: @back_action }, { method: 'post' } ) do
        = hidden_field_tag 'id', @params['id']
        <noscript>
        = button_tag '戻る', class: 'btn_sub'
        </noscript>

 routes

  ### ログイン後、POSTアクションに戻る ###
  get 'after_login_to_post', to: 'top#after_login_to_post'

 参照

http://easyramble.com/cannot-post-redirect-on-rails.html

devise ログイン後のリダイレクトについて(GET編)

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Rails の Devise でログイン後はログインする直前のGETリクエストにリダイレクトさせます。

 デフォルトの動き

before_action :authenticate_user!を通して、ログイン画面に遷移する場合、ログイン後、ログイン直前の画面に戻します。

下記のようなredirect_toを利用して、ログイン画面に遷移する場合、ログイン後、元の画面に戻らなくて、root_pathに遷移します。

unless user_signed_in?

redirect_to new_user_session_path

 対策

ログイン直前のリクエストをセッションに保存しておいて、

ログイン成功後のafter_sign_in_path_forメソッドをオーバーライドして、戻る先をセッションから取得して設定します。

注意:ログイン関連のリクエストをセッションに入れないこと。

 ログイン直前のリクエストをセッションに保存

applictaion_controller.rb

class ApplicationController < ActionController::Base
  after_action  :store_location
  def store_location
    if (request.fullpath != "/users/sign_in" &&
        request.fullpath != "/users/sign_up" &&
        request.fullpath !~ Regexp.new("\\A/users/password.*\\z") &&
        !request.xhr?) # don't store ajax calls
      session[:previous_url] = request.fullpath 
    end
end

 deviseのリダイレクをカスタマイズ

devise標準のafter_sign_in_path_forをオーバーライドして、ログイン後のリダイレクト先はカスタマイズします。

SessionsControllerをカスタマイズしなければ、applictaion_controller.rbに追加します。

SessionsControllerをカスタマイズすれば、カスタマイズしたコントローラ中に追加します。

class UsersSessionsController < Devise::SessionsController
  ・・・
  def after_sign_in_path_for(resource)
    if (session[:previous_url] == root_path)
      super
    else
      session[:previous_url] || root_path
    end
  end
end

 参照

https://github.com/plataformatec/devise/wiki/How-To:-Redirect-back-to-current-page-after-sign-in,-sign-out,-sign-up,-update

http://easyramble.com/friendly-forwarding-redirect-on-devise.html

Rails 4.2 にアップデートすると他のマシンからサーバーにアクセスできなくなる?

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

一瞬ハマったのでメモとして書き残します。

 Rails 4.2 にアップデートすると他のマシンからサーバーにアクセスできなくなる?

「rails s」で起動するサーバーの IP アドレスはもともとは「0.0.0.0」でしたが、

Rails 4.2 から「localhost」になっているようです。

この影響で他のマシンや、ホスト OS から立ち上げたサーバーにアクセスできない!

ということがあります。

 解決方法

いままで通りにアクセスできるようにするには簡単で、「rails s -b 0.0.0.0」のような

オプションを指定するだけです。オプションの詳細は「rails –help」で確認できます。

rails s
#=> 'http://localhost:3000' で起動するため外部からアクセス不可
rails s -b 0.0.0.0
#=> 'http://0.0.0.0:3000' となり、いままで通りアクセス可能

 どこで変わったの?

Rails のサーバー部分の実装に使われている Rack に、デフォルト IP の変更があったみたいです。単純に「その方が安全だよね」という話ですね。

参考: https://github.com/rack/rack/commit/28b014484a8ac0bbb388e7eaeeef159598ec64fc

Railsの可逆暗号化方法 ActiveSupport::MessageEncryptorの使用方法

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Railsで簡単に可逆暗号化方法を提供しています。ActiveSupport::MessageEncryptorです。

今日は、ActiveSupport::MessageEncryptorの使い方についてご紹介します。


①プレーンテキスト(message)、鍵(secret)を以下のようにする。
2.1.1 :001 > message = ‘test’
 => “test”
2.1.1 :002 > secret = SecureRandom::hex(50)
 => “06e40eedc81374bbef74bb2ac1c4a5d9928287c5146949d8d56a566495a5c8ecb8499177bce629d55a8e485d7dc66a183d47”
2.1.1 :003 >

②ecryptorの宣言(AES-256, Cipher Block Chainingで暗号化)
2.1.1 :003 > encryptor = ::ActiveSupport::MessageEncryptor.new(secret, cipher: ‘aes-256-cbc’)
 => #, @serializer=Marshal>

③暗号化(encrypt_and_sign)
2.1.1 :006 > encrypt_message = encryptor.encrypt_and_sign(message)
 => “YjBvd0dOemF5YWN3eHI1OXRUUmk5Zz09LS11OXR6akNlK0IxWUFTdyt5YTlQUWNBPT0=–737d367a2db323c2890084a3139e1e39e7e16c5d”

④復号化(decrypt_and_verify)
2.1.1 :007 > encryptor.decrypt_and_verify(encrypt_message)
 => “test”

以上は、通常の使い方です。

でも、ここで、注意しないとできないのは、
③暗号化(encrypt_and_sign)
2.1.1 :006 > encrypt_message = encryptor.encrypt_and_sign(message)
は、実行するたびに、結果が変わります。
2.1.1 :012 >   encrypt_message = encryptor.encrypt_and_sign(message)
 => “bjZOekxmZnlqRUtzT3V4NFBNb2lqdz09LS1FVEFVZ0xsTUFRK2U2dWZtUmRQZ0RBPT0=–8f8882f9d87d60d7ac85069cd7d2d58d105bbf25”
2.1.1 :013 > encrypt_message = encryptor.encrypt_and_sign(message)
 => “am1sQzI5SUhweG43TFVrK05hb3lPdz09LS1Cb0d3SHZKa2dobjQxU3ZLWVRpd3B3PT0=–b951c5800a881b79d309cace046349b41866936b”

ソースを見れば、原因がわかると思いますが、
.rvm/gems/ruby-2.1.1/gems/activesupport-4.1.4/lib/active_support/message_encryptor.rb

 67
 68     def _encrypt(value)
 69       cipher = new_cipher
 70       cipher.encrypt
 71       cipher.key = @secret
 72
 73       # Rely on OpenSSL for the initialization vector
 74       iv = cipher.random_iv #### => ここです。ivが実行するたびにランダムで生成する
 75
 76       encrypted_data = cipher.update(@serializer.dump(value))
 77       encrypted_data << cipher.final
 78
 79       “#{::Base64.strict_encode64 encrypted_data}–#{::Base64.strict_encode64 iv}”
 80     end
 81

こうすると、暗号化の結果を別のところで、使いたい場合、ivを固定させるカスタマイズする必要があります。

例えば:

 44     def initialize(secret, *signature_key_or_options)
 45       options = signature_key_or_options.extract_options!
 46       sign_secret = signature_key_or_options.first
 47       @secret = secret
 48       @sign_secret = sign_secret
 49       @cipher = options[:cipher] || ‘aes-256-cbc’
 50       @verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer)
 51       @serializer = options[:serializer] || Marshal
 52       @iv = options[:iv]

53     end

 68
 69     def _encrypt(value)
 70       cipher = new_cipher
 71       cipher.encrypt
 72       cipher.key = @secret
 73
 74       # Rely on OpenSSL for the initialization vector
 75       iv = @iv || cipher.random_iv
 76
 77       encrypted_data = cipher.update(@serializer.dump(value))
 78       encrypted_data << cipher.final
 79
 80       “#{::Base64.strict_encode64 encrypted_data}–#{::Base64.strict_encode64 iv}”
 81     end

こうすると、実行するたびに結果が同じです。


irb: warn: can’t alias context from irb_context.
2.1.1 :009 > message = ‘test’
 => “test”
2.1.1 :010 >
2.1.1 :011 > message.encoding
 => #
2.1.1 :012 >
2.1.1 :013 >   secret = SecureRandom::hex(50)
 => “b9aa9c2b5af0349c3b334ead4f8b1e2a3ff42921425bf2df1aefd3d3aefd4b4b5ce816e38fdcad231b985dee8dbf40200d06”
2.1.1 :014 > iv = SecureRandom::hex(20)
 => “fa4ad72743b789ff188c7c50a56b3b563e1e68e9”
2.1.1 :015 > encryptor = ::ActiveSupport::MessageEncryptor.new(secret, cipher: ‘aes-256-cbc’, iv: iv)
 => #, @serializer=Marshal, @iv=”fa4ad72743b789ff188c7c50a56b3b563e1e68e9″>
2.1.1 :016 > encrypt_message = encryptor.encrypt_and_sign(message)
 => “b0NGSXRYQVY2OCs4OS8vcDcrYU12Zz09LS1abUUwWVdRM01qYzBNMkkzT0RsbVpqRTRPR00zWXpVd1lUVTJZak5pTlRZelpURmxOamhsT1E9PQ==–c249ada02f27b5482b682537180c751e266bd608”
2.1.1 :017 >
2.1.1 :018 >   encrypt_message = encryptor.encrypt_and_sign(message)
 => “b0NGSXRYQVY2OCs4OS8vcDcrYU12Zz09LS1abUUwWVdRM01qYzBNMkkzT0RsbVpqRTRPR00zWXpVd1lUVTJZak5pTlRZelpURmxOamhsT1E9PQ==–c249ada02f27b5482b682537180c751e266bd608”
2.1.1 :019 >
2.1.1 :020 >   encrypt_message = encryptor.encrypt_and_sign(message)
 => “b0NGSXRYQVY2OCs4OS8vcDcrYU12Zz09LS1abUUwWVdRM01qYzBNMkkzT0RsbVpqRTRPR00zWXpVd1lUVTJZak5pTlRZelpURmxOamhsT1E9PQ==–c249ada02f27b5482b682537180c751e266bd608”
2.1.1 :021 >


もう一つ、目で見るとき、同じ”test”で、encodingが違うと、暗号化の結果が変わります。


2.1.1 :023 > message.encoding
 => #
2.1.1 :024 > encrypt_message = encryptor.encrypt_and_sign(message)
 => “b0NGSXRYQVY2OCs4OS8vcDcrYU12Zz09LS1abUUwWVdRM01qYzBNMkkzT0RsbVpqRTRPR00zWXpVd1lUVTJZak5pTlRZelpURmxOamhsT1E9PQ==–c249ada02f27b5482b682537180c751e266bd608”
2.1.1 :025 >
2.1.1 :026 > message_sjis = message.force_encode_utf8_to_sjis
2.1.1 :028 > message_sjis.encoding
 => #
2.1.1 :031 > message_sjis == message
 => true
2.1.1 :033 > encrypt_message_sjis = encryptor.encrypt_and_sign(message_sjis)
 => “NkRsUTVWU0M5N3Z4Sm1VUGI3czNsY3poYnRyVGJhdHB3NE5IeE1qMGdQaz0tLVptRTBZV1EzTWpjME0ySTNPRGxtWmpFNE9HTTNZelV3WVRVMllqTmlOVFl6WlRGbE5qaGxPUT09–daf7901c7949383921e3b1b47dc389defd40892d”
2.1.1 :036 > encrypt_message_sjis == encrypt_message
 => false

プロジェクトにRubocopを導入することのメリットとデメリット

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Ruby でコーディングルールを設けるのに役立つ gem「Rubocop」をチームで利用するメリットとデメリットについて紹介します。

 はじめに

チームでプログラミングをするときに立ちはだかる壁のひとつに「コードの統一」があると思います。

どんなに有能な人が複雑な機能を瞬く間に作り上げたとしても、チームで開発し保守する以上は、それが他人に理解できるものでなければあまり意味がありません。

なぜならばたったひとりしか理解できないコードは、その人にしか保守できないからです。

この「コードの統一」を図るために出てくるのがコーディングルールかと思います。

コーディングルールがあれば、皆が理解できるコードを共有し合うことができます。

が、このルールを作るのも守るのも面倒くさい!

そこで導入を検討したのが Rubocop です。

この gem は Ruby のコードを静的に評価して「ここはこう書いた方が良いよ」というアドバイスをくれるというものです。

「これがあればコーディングルールなしにキレイなコードを担保できるかも?!」という希望をもとに、新しいプロジェクトで実際に使ってみることにしました。

この記事では、Rubocop をチームで運用してみて感じたメリットとデメリットを紹介したいと思います。

 Rubocopとは

Rubocop 自体の説明は割愛します。

どんなものかをご存知でない方は下記記事などをご参照ください。

Rubocopを使ってコーディングルールへの準拠チェックを自動化

http://qiita.com/yaotti/items/4f69a145a22f9c8f8333

 Rubocopの運用方法

今回のプロジェクトでは Rubocop を自動テストの一端として組み込む形で利用しました。

この自動テストと GitLab のマージリクエスト (GitHub でいうプルリクエスト) を組み合わせることで

「テストが成功するまでマージできませーん」という流れができるので、 [45/1090]

Rubocop によるコーディングルールを守らざるを得ない環境を作ることができます。

ただし、Rubocop のデフォルト設定はチームで運用するには敷居が高い内容となっているので最初にある程度縛りを緩くしておき、

また途中からでもメンバーからの意見が多ければ設定を変更する体制を取りました。

 Rubocopのメリット

ようやく本題になります。

メリットとしては今回挙げるのは下記の3つになります。

1. コーディングスタイルをある程度統一できる

長いメソッド/モジュールの分割をほぼ強制されるので、

数百行のメソッドや数千行のモジュールがプロジェクト内に生まれることがありませんでした。

2. Ruby 歴が短い人、改善意識の高い人の良い矯正になる

Rubocop が提案してくる内容を素直に受け止められる人は、Rubocop 色に染まることで、

どんどん怒られにくいコードを自発的に書く方向に矯正できているように感じました。

メソッドひとつひとつがシンプルになったり、早い時期からメソッド郡のモジュール化を考えたりするようになったり、と良い感じです。

3. Ruby の新しい構文、便利な構文を知る良い機会になる

Rubocop を使っていると「こういう風に書きたいならこう書いた方が分かりやすいよ」という提案を受けることがあります。

これによっていままで知らなかったメソッドや慣習などを学ぶ機会を得ることができました。

 Rubocopのデメリット

Rubocop 単体のデメリット、というよりも、チームで使う際の注意点というような観点になります。

メリットと同じく、こちらも3つ挙げています。

1. コーディングスタイルをある程度しか統一できない

「メソッドが長いなら分ければ良いんでしょ」と安易に考え、

「method1」「method2」「method3」…のように無意味な名前でメソッドを分割するケースがありました。

残念ながらいま現在の Rubocop はそれを良しとして通してしまうため、

ただ単にメソッド1つの長さが短いだけの、本質的に意味のないメソッド分割を許容してしまうことがあります。

この辺は、「なぜ Rubocop を使うのか」についてチームで共通認識を持っておくべきだと感じました。

2. Ruby 歴が長い人、改善意識の低い人の阻害となる

Rubocop の提案を良しとしない人(たとえば手続き的な記述で書いた方が、

それでメソッドが長くなったとしても分かりやすいと考える人、など)とは、当然衝突することになります。

これは本人にとってもストレスですし、嫌々で書いたコードが良いものになるとも思えません。

ただしこれはどんなコーディングルールにも共通することなのかなと思います。

基本的にはルールを尊重しつつ、どうしてもという場合にはルールを見直していくしかないと思います。

3. 急いでいるときなどに本質的でない指摘で開発が止まる

Aさん「急ぎでこの処理を追加して欲しいんだけど」

Bさん「1行追加するだけでできました!」

Rubocop「メソッドが長いのでエラーです」

Aさん「・・・」

Bさん「・・・」

ということが何度かありました(あと1行だけでも追加すれば Rubocop に怒られる、という罠が巧妙に仕込んである)。

こうなってしまえばリファクタリングを余儀なくされることになるので、後のことを考えれば良いことなのですが、その場では大きなストレスになります。

 さいごに

Rubocop は自分ひとりで利用するのであれば本当に良い味方になると思います。

とくに最近のバージョンはABCメトリクス値の計測も取り入れられて、よりいっそう養成ギブスとしての能力を増しています。

あと、僕個人としては Rubocop が大好きなので心の中でもっと広まれ!と思っています(もっと広まれ!)。

ただしチームやプロジェクトで利用するには使い方に工夫が必要だと感じました。

チームメンバーにコーディングルールの意識をしっかりと共有しておくことである程度は回避可能だと思いますが、もっと良い方法があればぜひ教えてください!

Mac で 暗号化zip を作成する

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

ローカル環境で圧縮を行うときは gzip や tar zcf を使うことが多いが

社外の人にファイルを渡すときに暗号化zipを使う。

Windowsでは定番の圧縮解凍ツールに暗号化zipの作成機能があるが

Macは暗号化zip作成ソフトの話は聞かない。

というのもMacで暗号化zipを作成するのに特別なツールをインストールする必要はない。

ターミナルの zip コマンドにオプションをつけるだけで暗号化zipは作成できる。

簡単な操作だが、オプションやファイル名指定の順番をよく忘れてしまい再度検索して試すということが何度かあったので備忘の為に記載する。

単体のファイルを暗号化zipする場合

zip -e target_file.zip target_file.ext

ディレクトリを暗号化zipにする場合

zip -e -r target_directory.zip ./target_directory
-eオプションについて

※ man zip の出力の一部

--encrypt
       Encrypt the contents of the zip archive using a password which is entered on the terminal in response to
       a prompt (this will not be echoed; if standard error is not a tty, zip will exit with  an  error).   The
       password prompt is repeated to save the user from typing errors.

意訳的なもの

--暗号化
     (コマンドの)応答でターミナル上から入力するパスワードを使ってのzipアーカイブ内容の暗号化。
     (このパスワードは画面出力されない;もし標準エラーがttyでなければ、zip(コマンド)はエラー終了する) 
      このパスワード入力は打ち間違いを防ぐ為に再度行う。

仕事で使うScala入門

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

プログラミング言語Scalaは、昨今注目が集まっている「関数型プログラミング」を実際のプロジェクトで実践するのに適した有力なプラットフォームです。本エントリではScalaの概要について紹介します。

Scalaとは

スイス連邦工科大学のMartin Odersky教授によって開発されたプログラミング言語です。
Scala(スカラ)という名前は、言語設計の理念であるスケーラブル(Scalable)という言葉からとられています。

特徴について箇条書きでいくつか挙げると

  • JVM(Java仮想マシン)で動作し、Javaで書かれたライブラリやクラスを利用することができる
  • 静的型付け+型推論を行いJVM用のclassファイルにコンパイルされる
  • オブジェクト指向と関数型プログラミングの双方をサポートする(マルチパラダイム)
  • Java8で導入されたラムダ式などの高度な機能をを簡潔な文法で使うことができる

などが挙げられます。

文法は、基本的にJavaやC++などのスタンダードな文法をベースにしていますが、OCamlやHaskellなどの関数型言語に近い簡潔な記述ができるように工夫がこらされています。
弊社で主に使われているRubyにも若干似ているため、親しみが持てるひともいるかもしれません。

本家Webサイト
http://www.scala-lang.org/

Scalaの開発体制

Odersky教授によって開発が始まったScalaですが、現在では教授らが設立したTypesafe社を中心に、 オープンソースソフトウェアとして精力的に開発が続けられています。
https://github.com/scala/scala

開発環境の構築

JDK 1.6以上がインストールされたPCであれば、すぐにインストールして動かすことができます。
http://www.scala-lang.org/download/

また、Macを使っていれば、Homebrewで簡単にインストールすることができます。

しかしなんといってもおすすめは、ScalaをサポートしたIDEをインストールして、その中で開発することです。

IntelliJ IDEAを使いましょう。フルスペックのUltimate Editionは有償ですが、軽量版のCommunity Editionが無料で使えます。
https://www.jetbrains.com/idea/

現在Emacs, Vimを使っているひとは、設定やプラグインによってキーバインドを合わせることができるので問題ありません。私はIdeaVimというVim互換のキーバインドを実現するプラグインを使っています。

長年Eclipseを使っていてもどうしても手放せない、というひとは Scala IDE for Eclipseというプラグインを入れるとScalaが使えるようになるみたいですよ。
http://scala-ide.org/

最初の一歩

お約束として、Hello Worldを書いてみましょう。Scalaのコードはいろいろな方法で実行することができます。

  • コンパイルなしでスクリプトとして実行する
  • REPL(Read Eval Print Loop)の対話環境で実行する
  • .classファイルにコンパイルしてJVMで直接実行する

お仕事で使う場合も、対話環境でいろいろ試しながらコンパイル用のソースコードを書き進めるやり方が便利です。

対話環境でHello World

コマンドラインから scalaコマンドで対話環境を起動します。 scala> という文字列がプロンプトです。


% scala
Welcome to Scala version 2.11.5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_71).
Type in expressions to have them evaluated.
Type :help for more information.

scala> println("Hello World!")
Hello World!

scala> _

println()というのが画面に文字列を出力するメソッドです。正式には Console.println() ですが、デフォルトでimportされているため直接使うことができます。

スクリプトでHello World

拡張子 .scala のファイルにコードを書き、 scalaコマンドで実行します。


% cat hello.scala
println("Hello World!")
% scala hello.scala
Hello World!

ソースコードに書いたメソッドがそのまま実行されるので、Rubyなどのスクリプト言語とほぼ同じように書くことができます。

コンパイルしてHello World


% cat HelloWorld.scala
object HelloWorld {
  def main(args: Array[String]): Unit = {
    println("Hello World!")
  }
}
% scalac HelloWorld.scala
% ls
HelloWorld$.class	HelloWorld.class	HelloWorld.scala
% scala HelloWorld
Hello World!

これで、ソースコードを書き、コンパイルしてからそれを実行することができました。

Javaでは public static void main(String args[]) というシグニチャのメソッドを含むクラスを定義しますが、 Scalaのクラスにはstaticメソッド(Rubyでいうクラスメソッド)というものが存在せず、同じようなことをするためにはクラス(class)ではなくオブジェクト(object)というものを定義します。
Scalaで定義されたobjectも、コンパイルするとJVM用のクラスファイル(拡張子が.class)にコンパイルされます。
用語が紛らわしいですね。でもすぐに慣れます。

おわりに

Scalaという言語がどういうものなのか、その概要を駆け足で説明しました。
インストールも実行も簡単ですので、是非ためしてみて下さい。

Mac homebrew の imagemagick のバージョンを下げる

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

大人の事情(保守の関係など)でどうしても古いバージョンのミドルウェアを使用しなければならないことがある。自分が遭遇したのは imagemagick のバージョンを下げなければならないということだった。これはそのメモ。

とあるアプリケーションを動かす為に枯れた ruby をインストール し

gem もさらにバージョンを下げておいた。

 [kadosaway@imac ~]$ ruby -v
ruby 1.9.3p551 (2014-11-13 revision 48406) [x86_64-darwin13.4.0]
[kadosaway@imac ~]$ gem -v
1.7.2
bundle install を実行しようとするとエラーが発生し必要な gem が整えられなかった。
This installation of RMagick was configured with ImageMagick 6.8 but ImageMagick 6.9.0-3 is in use. (RuntimeError)

どうも versions というツールでバージョンを管理できるらしいが

インストールされていなかったのでリポジトリを追加。

[kadosaway@imac ~]$ brew tap homebrew/boneyard
Cloning into '/usr/local/Library/Taps/homebrew/homebrew-boneyard'...
remote: Counting objects: 717, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 717 (delta 0), reused 1 (delta 0)
Receiving objects: 100% (717/717), 172.84 KiB | 196.00 KiB/s, done.
Resolving deltas: 100% (336/336), done.
Checking connectivity... done.
Tapped 66 formulae

そして imagemagick のバージョンリストを取得

[kadosaway@imac ~]$ brew versions imagemagick
Warning: brew-versions is unsupported and will be removed soon.
You should use the homebrew-versions tap instead:
  https://github.com/Homebrew/homebrew-versions

6.9.0-3  git checkout 8b2b988 /usr/local/Library/Formula/imagemagick.rb
6.8.9-8  git checkout 9efbcda /usr/local/Library/Formula/imagemagick.rb
...
そしてフォーミュラ置き場へ移動し、目的のバージョンを再インストールする。
[kadosaway@imac ~]$ cd /usr/local/
[kadosaway@imac local]$ git checkout 9efbcda /usr/local/Library/Formula/imagemagick.rb
[kadosaway@imac local]$ brew reinstall imagemagick
==> Reinstalling imagemagick
==> Downloading https://downloads.sf.net/project/machomebrew/Bottles/imagemagick
Already downloaded: /Library/Caches/Homebrew/imagemagick-6.8.9-8.mavericks.bottle.tar.gz
==> Pouring imagemagick-6.8.9-8.mavericks.bottle.tar.gz

with_optionsでifを用いる時の注意

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

社会人1年目真っ最中の中西です。

今回は、model内でwith_optionsを用いた時の、ifの扱いについて紹介します。

環境

Rails 4.1.4

Ruby 2.1.2

with_optionsのif

model内で、バリデーションを書く時に、特定の場合のみバリデーションを動かしたい場合、ifを用います。

validates :test_id, length: { minimum: 50 }, if: test?

def test?
  return false if hogehuga == 'hogehuga'
  true
end

特定の場合のバリデーションが、1つの場合上記で問題ありません。

しかし、特定の場合のバリデーションが複数ある場合、それそれにifをつけるのは大変です。

そこで使用するのが、with_optionsになります。

with_options if: :test? do |v|
  v.validates :test_id, length: { minimum: 50 }
  v.validates :hoge, presence: true
end

with_optionsに条件を付けることにより、バリデーションをグルーピングすることができます。

この時に、with_optionsでまとめたバリデーションの1つに、さらに条件を加えたくなった場合、下記のように書きました。

with_options if: :test? do |v|
  v.validates :test_id, length: { minimum: 50 }
  v.validates :hoge, presence: true, if: :hoge
end

これで、大丈夫だと思っていたら、with_optionsでまとめたバリデーションを行う条件が、

「if: :test?」ではなく「if: :hoge」になっていました。

どうも、with_optionsの条件がifの場合、その中でさらにifを用いるとグルーピングの条件であるifを上書きしてしまうようです。

 解決方法

解決方法は簡単で、with_optionsの条件がifの場合は、その中ではifは使わない様にするだけです。

中でif文を使いたい場合は、逆のunlessを使います。

unlessを使えば、with_optionsのifを上書きすることはありません。

with_options if: :test? do |v|
  v.validates :test_id, length: { minimum: 50 }
  v.validates :hoge, presence: true, unless: 'hoge.blank?'
end

また、with_optionsの条件がunlessの場合には、その中でunlessは使わずに、ifを用いると条件を上書きせずにすみます。

rails4.2のwith_options

上記問題とは全く関係ありませんが、rails4.2では、with_optionsの書き方が少し簡略化されたようです。

 rails4.1

with_options if: :test? do |v|
  v.validates :test_id, length: { minimum: 50 }
  v.validates :hoge, presence: true
end

 raile4.2

with_options if: :test? do
  validates :test_id, length: { minimum: 50 }
  validates :hoge, presence: true
end

ブロック引数をつけなければいけなかったのが、つけなくて良くなったようです。

https://github.com/rails/rails/pull/16339

地味に楽になる気がします。

【Ruby】Railsを使用せずにRubyのCGIをRailsっぽくする【CGI/Apache】

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

「一部のページを動的に表示したいけどRailsを入れる程の規模ではない」というような場合に使える方法です。 普通のhtmlと共存できます。 ディレクトリは下記のような場合を想定しています。

http.confの修正

RubyのCGIが使えるようにhttp.confを変更します。 ここでは詳しくは書きません。

1.htaccessを作成

$ cd ドキュメントルート
$ vim .htaccess
=============
RewriteEngine on
RewriteCond %{REQUEST_URI} ^/$ [OR]
RewriteCond %{REQUEST_URI} ^/news/$ [OR]
  ・
  ・    CGI表示したいページを[OR]でつなげて記述していく
  ・
RewriteCond %{REQUEST_URI} ^/news/pickup/$
RewriteRule .* /routes.rb [L]
=============

2.routes.rbの作成

$ which ruby
---rubyのディレクトリ---          ←メモ
$ vim routes.rb
=============
#!---rubyのディレクトリ---

begin
  require './model/application'
  require './controller/application_controller'
  LOAD_DIR = %w(model controller)
  LOAD_DIR.each do |dir|
    Dir.glob("./#{dir}/*.rb").each do |file|
      require file
    end
  end

  # コントローラ生成
  controller_name = Application.get_controller_and_action_name[:controller]
  action_name = Application.get_controller_and_action_name[:action] || 'index'
  @controller = Kernel.const_get([controller_name, '_controller'].join.split('_').map(&:capitalize).join).new

  # アクション呼び出し
  eval "@controller.#{action_name}”

  # view呼び出し
  @controller.render

rescue Exception
  # 例外発生時404 Not Found

  puts 'Status: 404 Not Found\n'
  puts 'Content-Type: text/html\n\n'
  puts ''
  puts '404 Not Found'
  puts "ページは移動または削除されました。"
  puts "トップページへ”

  # エラーログに書き込み
  $stderr.puts "#{$!} (#{$!.class})"
  $stderr.puts $@.join("\n")
end
=============

3. model view controlle のディレクトリ作成

$ mkdir model
$ mkdir view
$ mkdir controller
$ vim model/.htaccess
=============
Deny from all
=============
$ cp model/.htaccess controller
$ cp model/.htaccess view

4. Application モデルの作成

$ vim model/application.rb
=============
class Application
  ROOT_CONTROLLER = :top
  DEFAULT_ACTION = :index

  class << self
    def get_controller_and_action_name
      url_split = ENV['REQUEST_URI'].split('/').delete_if{|element| element.empty? || element.match(/\?/) != nil}
      controller = url_split.first || ROOT_CONTROLLER
      action = url_split[1] || DEFAULT_ACTION
      {controller: controller, action: action}
    end
  end
end
=============

5. Application コントローラの作成

$ vim controller/application_controller.rb
=============
require 'erb'
require "cgi"
require "cgi/session"

class ApplicationController
  attr_accessor :cgi, :session, :params, :controller, :action

  def initialize
    @cgi = CGI.new
    @params = @cgi.params
    @session = CGI::Session.new(@cgi)
    @render_once = true
    params_parse
    @controller = Application.get_controller_and_action_name[:controller]
    @action = Application.get_controller_and_action_name[:action]
  end

  def params_parse
    if @params != nil
      form_array = @params.map do |key, value|
        if value.size == 1 && value.kind_of?(Array)
          

[key.to_sym, value.first]

else

[key.to_sym, value]

end end.flatten(1) end @params = Hash[*form_array] end def redirect_to(url, status=”REDIRECT”) print @cgi.header( { “status” => status, “Location” => url }) end def render(viewfile=nil) if @render_once viewfile ||= “view/#{@controller}/#{@action}.html.erb” @cgi.out(){ ERB.new(File.read(viewfile).force_encoding(“utf-8”)).result(binding) } @render_once = false end end end =============

6. CGIで表示したいコンテンツのviewの作成(ここではTOPページ)

$ mkdir view/top 
$ vim view/top/index.html.erb
==============
  ・
  ・
  ・
<%= @access %> 回目のアクセスです
  ・
  ・
  ・
==============

7. CGIで表示したいコンテンツのmodelの作成(ここではTOPページ)

$ vim model/top.rb
==============
class Top
  SESSION_KEY_ID = ’top'
end
==============

8. CGIで表示したいコンテンツのcontrollerの作成(ここではTOPページ)

$ vim controller/top_controller.rb
==============
require 'csv'
require "time"

class TopController < ApplicationController
  def index
    init_session
    @access = @session[Top::SESSION_KEY_ID][:access]
  end

  private

  def init_session
    @session[Top::SESSION_KEY_ID] ||= {}
    @session[Top::SESSION_KEY_ID][:access] ||= {}
  end
end
==============

これでTOPページヘアクセスするたびにと「XX 回目のアクセスです」がカウントされていくと思います。
動的なページを増やしたいのあれば.htaccessに「/controller/action/」の形式で追記してviewmodelcontrollerを作成するだけで増やせます。

super の話

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

こんにちは、bibioです。ruby には super というオーバーライドしているメソッドを呼び出す機能があります。

何気なく使うとはまることがあるので違いを区別します。

説明した通り、superはオーバーライドしているメソッドを呼び出す機能です。

class A
  def foo
    puts "A#foo"
  end
end

class B < A
  def foo
    super
    puts "B#bar"
  end
end

のとき、次のように出力されます。

B.new.foo 
=>
A#foo
B#bar

では、以下のような例だとどうでしょうか?

class A
  def foo
    puts "A#foo"
  end
end

module M
  def foo
     puts "M#foo"
  end
end

class B < A
  include M
  def foo
    super
    puts "B#bar"
  end
end

突然ですが、問題です。 B.new.foo() を実行したときに出力されるのは次のうちどれでしょうか?

(1)

M#bar

A#bar

B#bar

(2)

A#bar

B#bar

(3)

M#bar

B#bar

正解は・・・(3)

super はオーバーライドをするメソッドを呼び出すのであって、常に親クラスのメソッドを呼び出すわけではありません。名前に引きずられそうですね。

何もMix-inされていない場合、オーバーライドする親クラスのメソッドが呼び出されます。(なければ、その親クラスの親)といって、どれにもマッチしなければ method_missing が呼ばれます。通常は、NoMethodError になります。

オーバーライドされたメソッド呼び出しの階層を注意します。

include でモジュールをMix-inした場合は、元のクラスの上位に割り込みます。

メソッドの探索順にいえば、A->M->B になります。

メソッドの階層は ancestorsメソッドを使って調べます。上の例だと次の通り(Ruby2.1.xの場合)

irb> B.ancestors
=> [B, M, A, Object, Kernel, BasicObject]

Git 〜やらかしてしまった場合の戻し方〜

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

rickNo35です。
最近Gitでやらかしてしまった時の戻し方を記載します

環境

git 1.7.1

前提

masterブランチ
hogeブランチを保持

状態

hogeブランチにてmasterブランチをマージしたところ
conflictが発生修正してcommitしたが、
conflict発生以外のマージしたファイルもcommitしてしまった。
(自分は編集していないのに他の人の修正が自分hogeブランでコミットされてしまう)
別のブランチをマージしてしまった。
などを行いなおかつ気付かずに開発を続けてしまった時など色々

修正手順

# hogeブランチ移動
git checkout hoge
# hogeの変更履歴表示(別ターミナルで表示)
git log –stat master..hoge
# masterブランチに切り替わり最新にする
git checkout master
git pull
# 新しくブランチを作成+new/hogeに移動
git checkout -b new/hoge
# 別ターミナルで表示しているhoge変更履歴より必用なcommitのみ抜き出して入れる
git cherry-pick 取り出したいlogのcommitの番号
# hogeブランチを移動
git branch -m hoge old/hoge
# new/hogeブランチをhogeに置き換える
git branch -m new/hoge hoge
# hogeにpushする
# forceを付けないとコミット順番おかしいと言われるが強制的にforceでpushする(自分以外がいじっている場合はやらないほうがいい)
git push –force origin hoge
# 問題なければold/hogeブランチを削除
git branch -d old/hoge

image_submit_tagとsubmit_tagのパラメータの違いについて

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

初めまして、中西です。

今年社会人になったばかりの新人です。

今回は、自分が少し詰まった、image_submit_tagとsubmit_tagの挙動の違いについて紹介します。

環境

Rails 4.1.4

Ruby 2.1.2

image_submit_tagとsubmit_tagの違い

form_tag等でくくった中で、submitボタンを複数配置し、押下するボタンによって処理を変えたい場合、

アクションを1つしか設定できないことから、:nameのオプションをつけて、アクション内でパラメータにより処理を分けるといったことができます。

このとき、submit_tagとimage_submit_tagで渡されるパラメータに違いが生じます。

image_submit_tagとsubmit_tagの例

例えば下記のようなsubmitボタンを書いたとします。

view
= submit_tag 'HOGE', :name => 'hoge'

= image_submit_tag 'image/huga.png', :name => 'huga'

それぞれのボタンを押下した時に、期待するパラメータはparams[:hoge]とparams[:huga]です。

しかし、実際のパラメータの中身は下記のようになります。

paramsの中
"hoge"  => "HOGE"

"huga.x" => "12", "huga.y" => "10"

上記のように、image_submit_tagの場合、params[:huga]ではなく、

params[“huga.x”]とparams[“huga.y”]という2つのパラメータとなります。

どうも、ボタンの画像の左上を(x,y)=(0,0)として、横軸にx座標、縦軸にy座標となっており、

ピクセル単位でボタンの押下位置のx,y座標それぞれのパラメーターとして飛ぶようです。

まとめ

image_submit_tagで:nameのオプションを使う場合には、パラメータには十分注意しないといけないようです。

しかし、image_submit_tagも、1枚のボタン画像のなかで、押下位置により動作を変えたいといった場合には、使えるのかもしれません。

【Rails】barbyでバーコードを作成してPNGで表示する【バーコード】

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

「barby」を利用すると数字をバーコードにできたり、英数字をQRコードにして表示できるようになります。

今回はでデータURIスキームで画像表示する方法を紹介します。

この方法を使えば画像データを保存しなくて済みます。

 Githubとwikiのリンク

Github

https://github.com/toretore/barby

wiki

https://github.com/toretore/barby/wiki

 1.Gemfileに「 gem ‘barby’」と記述してbundle install

	$ vim Gemfile
	=================
	gem ‘barby’
	=================
	$ bundle install
	

 2.バーコード作成用のclassを作成

	class Barcode
	  require 'barby'
	  require 'barby/barcode/ean_13'
	  require 'barby/barcode/ean_8'
	  require 'barby/outputter/png_outputter'
	
	  def initialize(number, type = :ean_13)
	    @number = number.to_s
	    @type = type
	  end
	
	  # PNG形式でデータURIスキームを生成
	  def to_png_image
	    'data:image/png;base64, ' + Base64.encode64(Barby::PngOutputter.new(barcode(@type, @number)).to_png)
	  end
	
	  private
	
	  def barcode(type, data)
	    case type
	    when :ean_13 # 13桁の場合
	      Barby::EAN13.new(data)
	    when :ean_8 # 8桁の場合
	      Barby::EAN8.new(data)
	    end
	  end
	end
	

 3.controllerでの記述

	def index
	   @barcode = Barcode.new(491234567890)
	end
	

 4.viewでの記述

	<%= image_tag @barcode.to_png_image %>
	

他にもQRコードの作成やテーブルタグでの出力方法等もあったりします。詳しくは公式wikiをご確認ください。

[Ruby on Rails] enumerizeの使い方

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Railsのgem、enumerizeの使い方のご紹介。

 列挙型とは?

列挙型とは、定数のリストを定義することができる型のことです。

使い道は様々ですが、ActiveRecordのvalueと組み合わせる方法が便利です。

 定数を利用する場合

例えば、DBのユーザテーブルに「性別」というカラムがあったとします。

性別には当然「男」と「女」の2パターンがあります。

この性別のフォームを作る場合どうしたらいいでしょうか?

まず、modelに定数を宣言する方法があります。

app/models/user.rb

class User < ActiveRecord::Base
  SEX = { male => '男', famale => '女' }.freeze
end

app/views/users/_form.html.haml

.form-inputs
  = f.input :sex, collection: User::SEX.invert, include_blank: false

値が少ない場合は上記でも問題なさそうですが、値が増えた場合やI18nで多言語化をしている場合、modelの記述が膨れ上がります。

 enumerizeを利用する場合

そこで列挙型の出番です。

Rails4.1からはActiveSupportでenumが使えるようになったのですが、I18nには非対応のようです。

ですので、gemのenumerizeが便利です。ActiveRecordとI18nに対応しています。

【GitHub/enumerize】

https://github.com/brainspec/enumerize

※インストール方法は上記リンク先を参照ください。

app/models/user.rb

class User < ActiveRecord::Base
  extend Enumerize

  enumerize :sex, in: [:male, :female]
end

※extendで上手く読み込まれない場合、includeしてみてください。

多言語化する場合は「enumerize」というネームスペースを使います。

config/locales/models/ja.yml

ja:
  enumerize:
    user:
      sex:
        male: 男性
        female: 女性

app/views/users/_form.html.haml

.form-inputs
  = f.input :sex, collection: User.sex.options, include_blank: false

valueも以下のように取得できます。

@user.sex_text
#=> 男性

 最後に

選択肢が多いカラム定義の際に特に便利かと思います。

他にも色々なメソッドが使えるようになっていますので、ぜひGitHubのページをご覧ください!

最近人気な記事