action_controllerの拡張gemをRSpecでTDD ٩( ‘ω’ )و
action_controllerの拡張gemを作成するときにrspecでテストする
ユーザ認証のサンプルgemをTDDで作っていきましょう
まずは、gemのひな形を作る
$ bundle gem user_auth
bundlerが1.9系なら以下のファイルが作成されてるはず
├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin │ ├── console │ └── setup ├── lib │ ├── user_auth │ │ └── version.rb │ └── user_auth.rb ├── spec │ ├── spec_helper.rb │ └── user_auth_spec.rb └── user_auth.gemspec
さて、ここから本題です
ゴールはこんな感じで、認証に失敗したら例外みたいなー
class ApplicationControlller < ActionController::Base before_action :authenticate_user! end
TDDなのでテストから書いてくが、その前に開発時に必要なgemをインストール
# Gemfile source 'https://rubygems.org' gemspec # この3つを追加 gem 'rails' gem 'rspec-rails' gem 'sqlite3'
ざっとテスト書いていきまーす
# spec/user_authenticate_spec.rb require 'spec_helper' RSpec.describe UserAuth::UserAuthenticate do describe ApplicationController, type: :controller do controller do def index render text: 'success!' end end context 'Authentication failure' do example do expect { get :index }.to raise_error end end context 'Authentication success' do let(:user) { User.create(email: 'user@example.com') } example do session[:user_id] = user.id get :index expect(response.body).to eq 'success!' end end end end
テスト実行すると落ちます!
uninitialized constant UserAuth::UserAuthenticate (NameError)
UserAuthenticateがないよーって言ってるので作りましょう
# lib/user_auth/user_authenticate.rb module UserAuth module UserAuthenticate end end # spec/spec_helper.rb $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'user_auth' # 追加 require 'user_auth/user_authenticate'
テストを実行!
uninitialized constant ApplicationController (NameError)
ApplicationControllerがないって言ってるので、ダミーのコントローラーを作ります
# spec/support/action_controller.rb require 'action_controller/base' class ApplicationController < ActionController::Base end # spec/spec_helper.rb $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'user_auth' # 追加 require 'support/action_controller'
テストを実行!
'method_missing': undefined method 'controller'
RSpecのcontrollerメソッドがないっていってる。
これはrspec-railsのメソッドなので、spec_helperに追加
# spec/spec_helper.rb $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'user_auth' require 'support/action_controller' # 追加 require 'rspec/rails'
テストを実行!
undefined method 'application' for Rails:Module
applicationがないって言ってるのでダミーのRails::Applicationを作ります
# spec/support/application.rb require 'action_dispatch' module Rails class App def env_config; {} end def routes return @routes if defined? @routes @routes = ActionDispatch::Routing::RouteSet.new @routes.draw do resources :posts end @routes end end def self.application @app ||= App.new end end # spec/spec_helper.rb $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'user_auth' # action controllerより前に追加 require 'support/application' require 'support/action_controller' require 'rspec/rails'
テストを実行!
uninitialized constant User
Userがないって言ってるのでダミーのUserモデルを作ります
# spec/support/active_record.rb require 'active_record' ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') class User < ActiveRecord::Base; end class CreateAllTables < ActiveRecord::Migration def self.up create_table :users do |t| t.string :email end end end # spec/spec_helper.rb $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'user_auth' require 'user_auth/user_authenticate' require 'support/application' require 'support/action_controller' require 'rspec/rails' # 追加 require 'support/active_record' RSpec.configure do |config| config.before :all do CreateAllTables.up unless ActiveRecord::Base.connection.table_exists? 'users' end config.before :each do User.delete_all end end
テストを実行!
For instance, 'include Rails.application.routes.url_helpers'.
そのまま、ダミーのApplicationControllerにincludeしてあげましょう
# spec/support/action_controller.rb require 'action_controller' class ApplicationController < ActionController::Base include Rails.application.routes.url_helpers end
テストを実行!
expected Exception but nothing was raised
キタ——(゚∀゚)——!!
ダミーのApplicationControllerにauthenticate_user!メソッドを追加しましょう
# spec/support/action_controller.rb require 'action_controller' class ApplicationController < ActionController::Base include Rails.application.routes.url_helpers before_action :authenticate_user! end
テストを実行!
undefined method 'authenticate_user!'
authenticate_user!メソッドを実装します
# lib/user_auth/user_authenticate.rb module UserAuth module UserAuthenticate def authenticate_user! end end end # lib/railtie.rb require 'rails/railtie' require 'active_support' module UserAuth class Railtie < Rails::Railtie ActiveSupport.on_load :action_controller do require 'user_auth/user_authenticate' send :include, UserAuth::UserAuthenticate end end end # lib/user_auth.rb require "user_auth/version" # 追加 require 'user_auth/railtie' ...
テストを実行!
expected Exception but nothing was raised
authenticate_user!メソッドでユーザを取得する
# lib/user_auth/user_authenticate.rb module UserAuth module UserAuthenticate def authenticate_user! User.find(session[:user_id]) end end end
テストを実行!
おーるぐりーんヾ( ̄∇ ̄=ノ
どうですか?
TDDなら次に何をするかが明確になりますよね?
テストを書いて、エラーがでたら原因を調べて解消していく
テストが失敗(エラーじゃないよ)したらロジックを書く!
そしてグリーンになればオッケーって感じです
サンプルコードは雑ですがね...
Happy Hacking٩( ‘ω’ )و
Rails - ActiveDecoratorのコード読んでみたヘ(^o^)ノ
いつもお世話になります。ActiveDecoratorさま。
@izumin5210さんの「ActiveDecorator読んでみたら超勉強になった」を読んで、そういやコードあんま見てなかったので読んでみた
なるほど編
alias_method_chain 既存のメソッドを拡張するときにちょー便利♪
コードよむ編
# lib/active_decorator/monkey/abstract_controller/rendering.rb module AbstractController module Rendering # to_aメソッドのエイリアス↓ def view_assigns_with_decorator # 既存のto_aメソッドをコール hash = view_assigns_without_decorator # メソッドを拡張 hash.values.each do |v| ActiveDecorator::Decorator.instance.decorate v end hash end # :view_assigns => :view_assigns_with_decorator # :view_assigns_without_decorator => :view_assigns alias_method_chain :view_assigns, :decorator end end
view_assignsは、Controllerで取得したインスタンスなんかが格納されたHashを返しますよー
こんな感じ↓
postsはActiveRecord::Relation
です(Post.allしたやつ)
testはインスタンス変数に文字列をいれてみた
このHashをActiveDecorator::Decorator
へ
# lib/active_decorator/decorator.rb module ActiveDecorator class Decorator include Singleton def initialize @@decorators = {} end def decorate(obj) [...] end [...] end end
singletonをMix-inしてるので、ActiveDecorator::Decorator.instance.decorate
で呼び出してますよ
decorateメソッド
if obj.is_a?(Array) # *2でArrayが引数となるのでここ # Arrayの要素(ModelClass)を引数として自身を呼び出してる *3 obj.each do |r| decorate r end elsif defined?(ActiveRecord) && obj.is_a?(ActiveRecord::Relation) && !obj.respond_to?(:to_a_with_decorator) # ActiveRecord::Relationならクラスを再オープンしてメソッド定義してる *1 class << obj def to_a_with_decorator to_a_without_decorator.tap do |arr| # Arrayを引数として自身を呼び出してる *2 ActiveDecorator::Decorator.instance.decorate arr end end alias_method_chain :to_a, :decorator end else # *3で引数がmodelクラスとなるのでここ(ActiveRecord::Relation、Array以外もここ) # decoratorのクラス取得してる d = decorator_for obj.class return obj unless d # PostDecoratorモジュールに属してなければextendしてる obj.extend d unless obj.is_a? d end
decorator_forメソッド
private def decorator_for(model_class) return @@decorators[model_class] if @@decorators.has_key? model_class decorator_name = "#{model_class.name}Decorator" d = decorator_name.constantize unless Class === d d.send :include, ActiveDecorator::Helpers @@decorators[model_class] = d else @@decorators[model_class] = nil end rescue NameError @@decorators[model_class] = nil end
ViewContextをごっそり
# lib/active_decorator/view_context.rb module Filter extend ActiveSupport::Concern included do before_filter do |controller| ActiveDecorator::ViewContext.current = controller.view_context end end end
helperの拡張
# lib/active_decorator/helpers.rb module Helpers def method_missing(method, *args, &block) super #TODO need to make sure who raised the error? rescue NoMethodError, NameError => original_error begin # helperでmethod_missingならActiveDecorator::ViewContextから(なければoriginal_errorの例外発生 ActiveDecorator::ViewContext.current.send method, *args, &block rescue NoMethodError, NameError raise original_error end end end
ViewContextをごっそりとってるから、flashとかもdecoratorで使えるよー
ActiveDecorator::ViewContext.current.flash => #<ActionDispatch::Flash::FlashHash:0x007fda40e27d78 @discard=#<Set: {}>, @flashes={"error"=>"test error"}, @now=nil> ActiveDecorator::ViewContext.current.flash[:error] => "test error" # app/decorators/post_decorator.rb module PostDecorator def error_flash flash[:error] end end
ActiveDecoratorだと読みやすくて勉強にも良いかと思います♪
Happy Hacking٩( ‘ω’ )و
d(゚Д゚ )☆スペシャルサンクス☆( ゚Д゚)b
Railsでpostした時間を○○minutes agoみたいに表示するヘ(^o^)ノ
今日、Twitterで@netwillnetさんと@chiastoliteさんのツイートをみてやってみた
@netwillnet time_ago_in_wordsすかね
— Hiroyuki Morita (@chiastolite) 2014, 11月 17
Railsではtime_ago_in_wordsヘルパーが用意されています
でも、この子はリアルタイムで更新されない(当たり前
クライアントサイドで、リアルタイムに経過時間をとってきたいですよね?
これを使ってみましょう
jsファイルを... ダウンロードせずにgemを探します
rails-timeago - GitHubを使ってみましょう
使い方はとっても簡単♪
# Gemfile gem 'rails-timeago', '~> 2.0' # app/assets/javascripts/application.js //= require rails-timeago # app/views/posts/index.html.haml - @posts.each do |post| %tr %td= timeago_tag post.created_at, nojs: true, limit: 10.days.ago
しばらく何もせずに待っていると...
クライアントサイドで経過時間が更新されていると思いますヘ(^o^)ノ
これでタイムラインの経過時間がリアルタイムに更新されますねー
Happy Hacking٩( ‘ω’ )و
Gretelで簡単パンくずリストヘ(^o^)ノ
Railsでパンくずリストを表示するのにGretelをつかってみた
基本的な使い方ー
# config/breadcrumbs.rb crumb :root do link 'Home', root_path end crumb :posts do link 'Posts', posts_path end crumb :post do |post| link post.title, post parent :posts end # views/layouts/application.html.erb <%= breadcrumbs separator: " › " %> # views/posts/index.html.erb <% breadcrumb :posts %>
これでHome > Posts > ほげほげ
みたく表示される
うーん、素敵 ☆ミ
同一ページでパンくず使いたいとき
# config/breadcrumbs.rb [...] crumb :comments do |post| link 'Comments', comments_path(post) end # views/posts/show.html.erb <% breadcrumb @post %> <% with_breadcrumb :comments @post do %> <%= breadcrumbs separator: " › " %> <% end %>
オプションも豊富! Gretel - Options
bootstrapやfoundation5もstyleオプションで指定すれば使えます。
Controllerと分けて管理できるのでさらに良いのですヘ(^o^)ノ
Happy Hacking٩( ‘ω’ )و
RansackでDateTime型の検索してみるヘ(^o^)ノ
Ransackはとっても便利ですよねー
さらっとしか使えてないので、ちょっと調べてみました
eq
は完全一致、cont
は中間一致
などなどとっても簡単に検索機能を実装できます
そこでDateTime型のフィールドを検索したい場合、時分秒までは検索しないですよね?
やりたいことはDateTime型のフィールドをDate型で検索したいので
# in the model : ransacker :created_at do Arel::Nodes::SqlLiteral.new "date(items.created_at)" end
SQLを確認してみる
SELECT "projects".* FROM "projects" WHERE ((date(projects.created_at) >= '2014-11-03' AND date(projects.created_at) <= '2014-11-03'))
これでyyyy-mm-ddの日付型で検索できますねー
文字列 yyyy/mm/ddで検索したい場合はこう
# in the model ransacker :created_at do Arel::Nodes::SqlLiteral.new "strftime('%Y/%m/%d', projects.created_at)" end
SQLを確認してみる
SELECT "projects".* FROM "projects" WHERE ((strftime('%Y/%m/%d', projects.created_at) >= '2014/11/03' AND strftime('%Y/%m/%d', projects.created_at) <= '2014/11/03'))
できたーヘ(^o^)ノ
入力された文字列を日付型として扱いたい場合は、以下の設定ファイルを追加してあげよう
Ransack.configure do |config| config.add_predicate 'date_lteq', arel_predicate: 'lteq', formatter: proc { |v| v.to_date }, validator: proc { |v| v.present? }, type: :string end
これでcreated_at_date_lteq
とすれば、yyyy/mm/ddと入力されても日付型に変化してくれます
注意) このコードはSQLite用ですm(__)m
Arel::Nodes::SqlLiteral.new
=> Arel.sql
でいけると思います
もちろんDate関数も変わってきますのでご注意を
と色々やってきたとこで、このQiita記事をみると... 検索用のgem「ransack」を使ってみる
日付型のFrom - Toはこれで良いみたいですなー
Ransack.configure do |config| config.add_predicate 'date_lteq', arel_predicate: 'lteq', formatter: proc { |v| v.end_of_day }, # ここでend_of_dayメソッドコールしてる validator: proc { |v| v.present? } end
なるほど...そやね( ̄▽ ̄;)
すごい遠回り感がハンパないけど、いいやw
Happy Hacking٩( ‘ω’ )و