ridgepole task
Railsなどでridgepoleを採用した時に使うridgepole用のtaskです。以下をridgepole.rake
として保存すれば使えます。
namespace :ridgepole do desc 'Apply database schema' task apply: :environment do ridgepole('--apply', "--file #{schema_file}") Rake::Task['db:schema:dump'].invoke end desc 'Export database schema' task export: :environment do ridgepole('--export', "--output #{schema_file}") end private def schema_file Rails.root.join('config/ridgepole/Schemafile') end def config_file Rails.root.join('config/database.yml') end def ridgepole(*options) command = ['bundle exec ridgepole', "--config #{config_file}"] system [command + options].join(' ') end end
Webpack4を使ってReact.js + Redux + Sassの環境構築
Webpack4を使って、React.js + Redux + Sassの環境構築を行います。
install yarn
npmではなく、Yarnを使用します。yarn add xxxx
と実行することで、package.json
への追記とライブラリのインストールを同時にやってくれます。
$ brew install yarn
プロジェクト作成
はじめにプロジェクト用のフォルダを作成し、そこに移動し、以下のコマンドを実行します。
$ yarn init
いくつか対話で聞かれますが、すべてEnterを押して進めてOKです。すると、package.jsonというファイルが出来上がります。
install packages
yarnを使って必要となるパッケージをインストールします。今回はReact.js, Redux, Sass, Webpack周りをインストールします。
以下のコマンドを実行してください。
yarn add babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-2 prop-types react react-dom redux react-redux extract-text-webpack-plugin@next node-sass style-loader css-loader sass-loader webpack webpack-dev-server webpack-cli
※2018/03/5時点で、extract-text-webpack-pluginがデフォルトだと3.x系が入ってしまうので、4.x系が入るように@nextというオプションを指定しています。
package.json
以下を追記してください。これでyarn run xxxx(build, watch, start)
でコマンドを実行することができます。
"scripts": { "build": "./node_modules/.bin/webpack --mode production --optimize-minimize", "watch": "./node_modules/.bin/webpack --mode development -w", "start": "./node_modules/.bin/webpack-dev-server" }
webpack.config.js
webpackを動かすための設定ファイルを作成します。以下の内容でwebpack.cconfig.js
を作成してください。今回の出力先はdist
というフォルダにします。
const path = require('path'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = [ { entry: { index: './src/index.js' }, output: { path: path.resolve('dist/js'), publicPath: '/dist/js', filename: '[name].js' }, module: { rules: [ { exclude: /node_modules/, loader: 'babel-loader', query: { presets: ['react', 'es2015', 'stage-2'] } } ] }, resolve: { extensions: ['.js', '.jsx'] } }, { entry: { style: "./src/stylesheets/style.scss", }, output: { path: path.resolve('dist/css'), publicPath: '/dist/css', filename: '[name].css' }, module: { rules: [ { test: /\.scss$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: ['css-loader', 'sass-loader'] }) } ] }, plugins: [ new ExtractTextPlugin('[name].css'), ] } ];
Project Folder
React.js + Reduxでアプリケーションを作成する場合には、以下のようなフォルダ構成にします。
src |_ components |_ containers |_ constants |_ reducers |_ auth.js |_ counter.js |_ stylesheets |_ actions |_ auth.js |_ counter.js
参考
RailsでRoutesを分割する
config/routes.rb
class ActionDispatch::Routing::Mapper def draw(routes_name) instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb"))) end end Rails.application.routes.draw do root to: 'pages#home' draw :common draw :admin end
config/routes/xxx.rb
Rails.application.routes.draw do namespace :admin do root to: 'pages#home' end end
これでapp/config/routes以下にファイルを配置していけばOK。
sorceryを使ってEmail + Passwordログインを実装
Sorceyを用いたログイン機能実装を解説します。
Railsのログイン機能でよく知らているdeviseよりも機能が少なく、簡単に扱えるのでこちらを採用しております。
実装の詳細に関してはSorceryのGitHubのリポジトリのwikiに書かれています。 今回の実装はwikiのSimple Password Authenticationを参考にしていきます。
Gemの導入
以下をGemfileに追加し、bundle install
を実行してください。
gem 'sorcery'
必要ファイルの生成
sorceryにはgenerateコマンドが備わっています。以下のコマンドを実行すると、必要ファイルが生成されます。
bundle exec rails g sorcery:install create config/initializers/sorcery.rb generate model User --skip-migration invoke active_record create app/models/user.rb invoke test_unit create test/models/user_test.rb create test/fixtures/users.yml insert app/models/user.rb insert app/models/user.rb
migrationファイルの作成
先程のコマンドだと、migrationファイルが生成されていません。以下のコマンドを実行し、migrationファイルを作成しましょう。
※何か問われた場合は、すべて「Y」と入力し進めてください。
$ bundle exec rails g model User name:string email:string
生成されたmigrationファイルを以下のように編集してください。編集が終わったら、bundle exec rake db:migrate
を実行し適用させましょう。
class CreateUsers < ActiveRecord::Migration[5.2] def change create_table :users do |t| t.string :name, null: false t.string :email, null: false t.string :crypted_password t.string :salt t.timestamps null: false end end end
User modelの編集
User modelに対して、sorceryを使用するための設定およびValidationを追加します。app/models/user.rb
を以下のように編集してください。
class User < ApplicationRecord authenticates_with_sorcery! validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] } validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] } validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] } validates :email, uniqueness: true end
Routingの設定
ログイン周りのsession管理を扱うControllerをSessionsController
、ユーザーの登録等を行うControllerをUsersController
とし、以下のようにRouting(config/routes.rb
)を編集します。
Rails.application.routes.draw do root to: 'pages#home' get 'login', to: 'sessions#new', as: :login post 'login', to: 'sessions#create' post 'logout', to: 'sessions#destroy', as: :logout resources :users, only: [:new, :create] end
Controllerの作成
Sorceryの実装に関わる、以下の4つのControllerを実装します。
- ApplicationController
- ベースの設定に関する処理
- SessionsController
- セッションの操作に関する処理
- UsersController
- ユーザーの操作に関する処理
- PagesController
- リダイレクト先として仮で用意(こちらは適宜Routingとともに自分で変更して問題ありません)
ApplicationController
ApplicationControllerにSorceryを使用するための設定を記述します。以下のようにapp/controllers/application_controller.rb
を編集してください。
class ApplicationController < ActionController::Base # 全ページでログインを必須とする before_action :require_login private # ログインをしていない場合の処理 def not_authenticated redirect_to login_path, alert: 'ログインしてください。' end end
SessionsController
ログイン・ログアウト周りの処理を行う、SessionsControllerを作成します。以下の内容にてapp/controllers/sessions_controller.rb
を作成してください。
※以下のようにskip_before_action :require_login
を指定することによって、ログインを必須ではなくすることができます。
class SessionsController < ApplicationController skip_before_action :require_login, only: [:new, :create] def new end def create user = login(params[:email], params[:password]) if user.present? redirect_to root_path, notice: 'ログインしました。' else redirect_to login_path, alert: 'ログインに失敗しました。' end end def destroy logout redirect_to login_path, notice: 'ログアウトしました。' end end
UsersController
ユーザー登録を行うためのUsersControllerを作成します。以下の内容にてapp/controllers/users_controller.rb
を作成してください。
class UsersController < ApplicationController skip_before_action :require_login def new @user = User.new end def create @user = User.new(user_params) if @user.save login(params[:user][:email], params[:user][:password]) redirect_to root_path, notice: 'ユーザー登録しました。' else render :new end end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end end
PagesController
リダイレクト先の仮ページとしてPagesControllerを用意します。以下の内容にてapp/controllers/pages_controller.rb
を作成してください。
class PagesController < ApplicationController skip_before_action :require_login def home end end
Viewの作成
ここまでで、Routing、Model、Controllerの作成が終わりました。最後に以下のViewファイルを作成します。
※Viewにbootstrap3.xを使用しているため、htmlのclassにはその要素が適用されております。
├── layouts │ └── application.html.erb ├── pages │ └── home.html.erb ├── sessions │ └── new.html.erb └── users └── new.html.erb
pages#home
ログイン・ログアウト及びユーザー登録ページへの導線を配置します。
<div class="container"> <h1>ホーム</h1> <ul> <li><%= link_to 'ログイン', login_path %></li> <li><%= link_to 'ユーザー新規登録', new_user_path %></li> <% if current_user %> <li><%= link_to 'ログアウト', logout_path, method: :post %></li> <% end %> </ul> </div>
sessions#new
ログインページのViewとなります。
<div class="container"> <div class="row"> <div class="col-md-4"></div> <div class="col-md-4"> <%= form_tag login_path, method: :post do %> <div class="form-group"> <label>メールアドレス</label> <%= text_field_tag 'email', nil, class: 'form-control' %> </div> <div class="form-group"> <label>パスワード</label> <%= password_field_tag 'password', nil, class: 'form-control' %> </div> <div class="form-group text-center"> <%= submit_tag 'ログインする', class: 'btn btn-primary' %> </div> <% end %> </div> </div> </div>
users#new
ユーザー登録ページのViewとなります。
<div class="container"> <div class="row"> <div class="col-md-4"></div> <div class="col-md-4"> <% if @user.errors.any? %> <div id="error_explanation"> <h2><%= @user.errors.count %>件のエラーがあります。</h2> <ul> <% @user.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <%= form_for @user, url: { action: 'create' } do |f| %> <div class="form-group"> <%= f.label :name %> <%= f.text_field :name, class: 'form-control', required: true %> </div> <div class="form-group"> <%= f.label :email %> <%= f.email_field :email, class: 'form-control', required: true %> </div> <div class="form-group"> <%= f.label :password %> <%= f.password_field :password, class: 'form-control', required: true %> </div> <div class="form-group"> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation, class: 'form-control', required: true %> </div> <div class="form-group text-center"> <%= f.submit '登録する', class: 'btn btn-primary' %> </div> <% end %> </div> </div> </div>
ここまで実装を行うと、ユーザー登録 -> ログイン -> ログアウトの一連の流れを確認することができます。
sorceryでFacebookログインを実装する
個人的に最近deviceではなくsorceryを使うことが多くなりました。deviceと比較してsorceryの良い点として、必要な機能だけを使えるという所です。
今回はsorceryを使ってFacebookログインを実装する方法を紹介します。
初期設定
# Gemfile gem 'sorcery' # bundle install ~ bundle install --path vendor/bundle # sorceryの初期ファイルのインストール ~ bundle exec rails g sorcery:install # Facebookログインを実装するためのファイル等インストール ~ bundle exec rails g sorcery:install external --only-submodules # Authentication Model作成 ~ bundle exec rails g model Authentication --migration=false
設定ファイル
# config/routes.rb post 'logout', to: 'users/sessions#destroy', as: 'logout' get 'oauth/callback', to: 'oauth#callback' get 'oauth/:provider', to: 'oauth#oauth', as: 'auth_at_provider'
# config/initializers/sorcery.rbの該当部分に以下を追記 Rails.application.config.sorcery.configure do |config| config.external_providers = [:facebook] config.facebook.key = Settings.facebook[:key] config.facebook.secret = Settings.facebook[:secret] config.facebook.callback_url = Settings.facebook[:callback_url] config.facebook.display = "page" # 以下3つは取得したい項目と合わせて変更する config.facebook.user_info_mapping = { email: "email", name: "name", gender: "gender" } config.facebook.user_info_path = "me?fields=email,name,gender" config.facebook.access_permissions = ["public_profile", "email"] config.user_config do |user| user.authentications_class = Authentication end end
Migration
# db/migrate/xxxxxxxxxxx_sorcery_core.rb class SorceryCore < ActiveRecord::Migration def change create_table :users do |t| t.string :email, null: false t.timestamps end add_index :users, :email, unique: true end end
# db/migrate/xxxxxxxxxxx_sorcery_external.rb class SorceryExternal < ActiveRecord::Migration def change create_table :authentications do |t| t.integer :user_id, null: false t.string :provider, null: false t.string :uid, null: false t.timestamps end add_index :authentications, [:provider, :uid], unique: true, name: 'ui_authentications_01' end end
Model
# app/model/user.rb class User < ApplicationRecord authenticates_with_sorcery! do |config| config.authentications_class = Authentication end has_many :authentications, dependent: :destroy accepts_nested_attributes_for :authentications validates :email, presence: true, uniqueness: true end
# app/model/authentication.rb class Authentication < ApplicationRecord belongs_to :user validates :user_id, presence: true validates :provider, uniqueness: { scope: :uid } end
Controller
# app/controllers/app_controller.rb class ApplicationController < ActionController::Base protect_from_forgery with: :exception before_action :require_login # もしログインを必要としない場合は以下を該当のControllerに記述する # skip_before_action :require_login private def not_authenticated redirect_to login_path, alert: "ログインが必要です" end end
# app/controllers/users/sessions_controller.rb class Users::SessionsController < ApplicationController def destroy logout redirect_to root_path, notice: 'ログアウトしました' end end
# app/controllers/oauth_controller.rb class OauthController < ApplicationController skip_before_action :require_login def oauth login_at(params[:provider]) end def callback provider = params[:provider] if @user = login_from(provider) redirect_to root_path, notice: 'ログインに成功しました' else begin @user = create_from(provider) reset_session auto_login(@user) redirect_to root_path, notice: 'ログインに成功しました' rescue redirect_to root_path, alert: 'ログインに失敗しました' end end end private def auth_params params.permit(:code, :provider) end end
View
# ログイン配置したいところに以下のコードを記述 <%= link_to 'Login with Facebook', auth_at_provider_path(provider: :facebook) %>