- 投稿日:2019-11-29T23:48:44+09:00
Twitterでのコード投稿の見栄えどうにかならんのと思った話
たぶん、長い投稿
きっかけ(こんな呟きを見かけた
ソースコードをツイートするときに
— えるは個人えんじにゃー(喪中) (@ellnore_pad_267) November 2, 2019<br>source code<br>
ってやってマークダウンみたいに引用文にして欲しい。
ここはもうURLとかハッシュタグとかも全部エスケープして欲しい。出来たもの
作成の過程で収穫物
- Rails5.2での追加分(Active Record Storage含む
- Twitter Login方法と仕組み、そのたTwitterあれこれ
- JSの基礎(getElementByIdやsetAttribute、文字カウントなど
- AWS S3の使い方
- XSS対策
未だ残る改修すべき箇所
- 検索結果画面のリダイレクトエラー(多分route.rbの書き順番由来
- js辺りのエラー(動いてるけど、consoleではjs/mapのルーティングが何とか
- スマホで
タグ等を打つの面倒なので、なにか投稿補助ボタンでも- 本来の目的をよく考えたら、マークダウンの方は不要なのでは。
作成要件
- マークダウン投稿、シンタックスハイライト
- gem: redcarpet, rouge(結局syntax-hightlightだけは反映されないまま
- 投稿から画像生成
- AWS S3にog:image用の画像を保存
作成の流れ:予定
- rails new codr, git init, heroku create、Active Storage
- AWS S3あれこれ
- twitter登録、ログイン機能作成
開発環境
- vm : Linux Ubuntu (virtualbox + vagrant)
- Ruby 2.5.1p57
- Rails 5.2.3
- Postgresql
実作業: アプリ作成、諸準備
rails new codr -d postgresql # DB設定等は割愛Gem
今回は公開にまで至る予定なので、railsやdeviseの日本語化等も。が、想定ユーザはエンジニアだしと思い、殆ど英語になった。
Gemfilegem 'mini_racer' # uncomment gem 'rails-i18n' # japanize # authetication gem 'devise' # login gem 'omniauth' # SNS login gem 'omniauth-twitter' # twitter login gem 'devise-i18n' # japanize devise gem 'devise-i18n-views' gem 'redcarpet' # for markdown gem 'rouge' # for syntax highlight gem 'meta-tags' gem 'aws-sdk-s3' # for aws s3kpumuk/meta-tags:Search Engine Optimization (SEO) for Ruby on Rails applications.は割愛。
gitignore => rails.credentials.yml
当初は.
gitignore
とgem 'dotenv'等を使っていた。が、作成途中でRails5.2からのrails.credentials.ymlを知り、利用した。rails.credentials.ymlは暗号化されており、なお、復号化には
/config/master.key`を利用。irb# editor setting EDITOR="vi" bin/rails credentials:edit # edit credentials.yml rails credentials:edit # show credential.yml rails credentials.yml:show # herokuにmaster.keyを環境変数として指定 heroku config:set ENV_VAR="環境変数" --app "アプリ名" # 追加した変数を使用するには Rails.application.credentials.dig(:twitter, :API_Key)rails gあれこれ
# devise # install devise rails g devise:install rails g devise User name:String # Add Admin column to User rails g migration AddAdminToUsers # add setting at /db/migrate/20191103141531_add_admin_to_users.rb add_column :users, :admin, :boolean, default: false # add views and controllers to modify devise rails g devise:controllers users rails g devise:views users # japanize # add at /config/application.rb config.i18n.default_locale = :ja => create /config/locale/devise.view.ja.yml# scaffold post rails g scaffold Post user:references name:string content:text date:datetimeActive Record Associations関連付け
/app/model/user.rbhas_many :posts/app/model/post.rbbelongs_to :user投稿関連
マークダウン投稿
基本:
Redcarpet::Markdown.new(renderer, extensions = {}).render(@post.content)
オプションやXSS対策等を追加したく、helperメソッドを作成した。app/helpers/posts_helper.rbModule PostsHelper require 'rouge/plugins/redcarpet' class RougeRedcarpetRenderer < Redcarpet::Render::HTML include Rouge::Plugins::Redcarpet def header(text, level) # #や##等がh2、h3となるようにした。 level += 1 "<h#{level}>#{text}</h#{level}>" end end def markdown(text) render_options = { filter_html: true, # do not allow any user-inputted HTML in the output. hard_wrap: true, } extensions = { autolink: true, # <>で囲まれていない時は、リンクとして認識しない fenced_code_blocks: true, # ```\n ```内をコード部分と見做す lax_spacing: true, no_intra_emphasis: true, strikethrough: true, superscript: true, tables: false, # テーブルを認識しない highlight: true, disable_indented_code_blocks: true, space_after_headers: false # #の後にスペースが無くても、h1等とする。 } renderer = RougeRedcarpetRenderer.new(render_options) Redcarpet::Markdown.new(renderer, extensions).render(text).html_safe end endhtml_safe => sanitize
html_safeではXSS対策としては駄目と知った。名前詐欺である。
sanitizeヘルパーを使用した。ホワイトリスト方式。要参照app/views/posts/index.html.erb# sanitize(html, options = {}) <div id="capture" class="content"> <%= sanitize(markdown(@post.content), tags: %w(div img h1 h2 h3 h4 h5 strong em a p pre code ), attributes: %w(class href)) %> </div>投稿内容のデータ化、AWSへの画像保存
最初はTwitterAPIを利用して、投稿から作成、DBに直接保存した画像でTwitter投稿しようとした。だが、Herokuでは画像が保持されない事、TwitterAPIの変更などいろいろ面倒なことが発生したので、最終的には画像をAWS S3に保存し、og:imageに添付する形を取った。
- Webアプリ内で通常投稿
- showページ表示(同時にhtml2canvasでBase64としてデータ取得、hidden_fieldに格納
- Tweetボタン押す(Postされ、postモデル内でbase64をデコード
- Active Storageを通して、AWS S3に保存
Active Storage
Rail5.2からの機能で、今までのcarrievaveやpaperclip等を使わずに、クラウドストレージ等へのアップロードが容易になる。今回はAWS S3を使った。
irb# set up rails active_storage:install # 今回は画像が紐づくPostテーブルが既にあるので、不要 # rails g resource comment content:text rails db:migrateapp/models/post.rbclass Post < ApplicationRecord # 今回は1つの投稿につき、1枚の画像なので。複数なら => has_many_attached :prtscs has_one_attached :prtsc endapp/config/enviroments/# ファイル保存先変更 # development.rb config.active_storage.service = :local # production.rb config.active_storage.service = :amazon
rails credentials:edit
でAWSアクセスキーとシークレットキーを追加。config/credentials.yml.encaws: access_key_id: secret_access_key:config/storage.ymltest: service: Disk root: <%= Rails.root.join("tmp/storage") %> local: service: Disk root: <%= Rails.root.join("storage") %> amazon: service: S3 access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> region: ap-northeast-1 bucket: codr0Gemfile# gemが必要 gem 'aws-sdk-s3', require: false # 今回は不要だったので、入れず。 gem 'mini_magick'html2canvas
参考:htmlを画像化する方法(html2canvasの使い方)
jsはProgateレベルだったので、DOM操作は初めてで、なんか楽しかったぞ。
- Tweetボタン押下時に、画像をPostするためのフォーム、hidden_fieldを用意
html2canvas.js
をapp/assets/javascripts
ディレクトリ配下に保存。- html上に置くscriptコードを改修
app/views/posts/show.html.erb<%= form_with(model: @post, local: true) do |form| %> <%= form.hidden_field :id, value: @post.id %> <%= form.hidden_field :prtsc, value: "" %> # idはpost_prtscになる。 <%= form.submit "Post", class:"btn btn-outline-dark", id:"tweet", value:"tweet" %> <% end %>app/views/layouts/application.html.erb<script type="text/javascript"> html2canvas(document.querySelector("#capture"),{scale:1, width:600}).then(canvas => { var base64 = canvas.toDataURL('image/jpeg', 1.0); document.getElementById('post_prtsc').setAttribute('value', base64); }); </script>Base64デコード
大学で画像処理していたとはいえ、 Base64とは?Blobとは?となり、良い機会だった。
app/models/post.rbattr_accessor :img def parse_base64(img) if img.present? # data:image/jpeg;base64,/9j/4AAQSkZJRgABA・・・から/9j/4AA以降を選択取得 content = img.split(',')[1] # 今回は、ユーザによる画像アップロード投稿ではなく、拡張子が決まっている filename = Time.zone.now.to_s + '.jpg' decoded_data = Base64.decode64(content) # String.IO.newにより、アプリ内に一時ファイルを作成しなくて済む prtsc.attach(io: StringIO.new(decoded_data), filename: filename) end endあとはposts_controllerで、paramsから受け取ったBase64データを上の
parse_base64(img)
で変換し、保存すれば完了。AWS S3
AWS上での登録、設定、バケット作成等は割愛。
Tweet button
公式で生成されるTweetボタンのURLを利用し、押下時にwindow.openでTweet投稿ページを開くようにした。rubyonrailsで用意した変数をjsに渡す
gem 'gon'
も考えたが、見送った。app/views/layouts/application.html.erb<script> var base = 'https://twitter.com/intent/tweet?url='; var pageUrl = 'https://codr0.herokuapp.com/posts/' + document.getElementById('post_id').value; var option = '&button_hashtag=Codr0&ref_src=twsrc%5Etfw'; var href = base + pageUrl + option; var twit = document.getElementById('tweet'); twit.addEventListener('click', function() { window.open( href ); }); </script>og:imageに画像添付
なお、headのmeta情報セットには、
gem 'meta-tags'
を使用。参照 : kpumuk/meta-tagsservice_url()とurl_for()
基本的にはどちらも、ActiveStorageに保存したデータのUrlを取得するメソッドの様だ。
どちらもセキュリティの為にリンクの有効期限が短いみたいだが、違いが分からなかった。今回はTweetボタン押下し、Tweetした際にog:imageとして表示されればいい。app/views/posts/show.html.erb# 画像がActive StorageでAWS S3に保存されて入れば <% if @post.prtsc.attached? %> <% set_meta_tags og:{image: @post.prtsc.service_url} %> <% end %>Twitterログイン
TwitterDeveloperAccountが必要。割愛。
なお、omniauthは脆弱性が見つかっており、githubの方でもアラートが来るのだが、パッチが無いのだが。クックパッドの人が対処してくれたので、感謝したい。
app/models/user.rb# 参考ページと同じ基礎的な所は割愛する。 class User < ApplicationRecord def self.from_omniauth(auth) find_or_create_by!(provider: auth['provider'], uid: auth['uid']) do |user| # 一部割愛 user.username = auth['info']['nickname'] # SNS登録時は、ダミーメールを登録 user.email = User.dummy_email(auth) end end # SNS登録(providerが存在する)時は、パスワード要求をしない def password_required? super && provider.blank? end def self.new_with_session(params, session) if session['devise.user_attributes'] new(session['devise.user_attributes']) do |user| user.attributes = params end else super end end private def self.dummy_email(auth) "#{auth.uid}-#{auth.provider}@example.com" end endTwitterのニックネームが取得できるようになったので、元からあるUserのnameテーブルは削除した。
css
今回はBootstrapを部分的に使用した。cssの優先順位など収穫があった。
改修(加筆
メディアクエリ
想定ユーザは殆どスマホなのに、PCで作成し、CSSをPCの見た目でやってた。折角SCSSでやってるので、変数を利用した。
app/assets/stylesheets/scaffold.scss# ディスプレイサイズが680pxまでなら。 $tab: 680px; @mixin tab { @media (max-width: ($tab)) { @content; } } // .box { // @include tab { // background-color: blue; // }; // }最後に
gist等がコードスクショをog:imageで表示してくれたら全て済むんじゃと思った。
因みにもう1段階先のWebアプリを考えてあるけど、たぶんjsの知識が足りないので、今は無理。
転職活動中の無職です。働きたい。。。。
- 投稿日:2019-11-29T22:03:53+09:00
ABC085C - Otoshidama
問題
回答
N,Y = gets.chomp.split.map(&:to_i) x = -1 y = -1 z = -1 N.step( 0, -1 ) do |i| if 10000 * i > Y next else ( N - i ).step( 0, -1 ) do |j| if 10000 * i + 5000 * j + 1000 * ( N - i - j ) == Y x = i y = j z = N - i - j break end end if x != -1 || y != -1 || z != -1 break end end end printf( '%d %d %d', x, y, z )結果
感想
5000円、10000円と1000円の差分である4000円と9000円で割り切れるかで確認する方法があって処理は早かったけど、流石にコード読むのが難しくなるしなあ・・・
- 投稿日:2019-11-29T21:19:42+09:00
HamlをRuby on Railsアプリケーションに導入する方法
Hamlとは
Hamlとは、HTMLを簡潔かつ簡単に記述できるマークアップ言語。
Railsで使用する場合の手順
1. Hamlを導入
Gemfileの一番下にコードを記述すると、すべての環境でhamlが使用できる。
記述後は、bundle installを忘れずに 。Gemfilegem 'haml-rails'2. erbファイルをHamlに変換
rails newにより、erbファイルがすでに作成されているので
拡張子がerbのファイルをhamlに変換する。一括変換する方法
ターミナルから以下のコマンドを実行。
$ rails haml:erb2hamlここで、
Would you like to delete the original .erb files? (This is not recommended unless you are under version control.) (y/n)と聞かれたら、状況に応じてy/nを選択する。
元のerbファイルを削除して構わなければ yを選択しよう。
- 投稿日:2019-11-29T19:41:00+09:00
rails-tutorial第2章
scaffold
MVCアーキテクチャではmodel,view,controlerが組み合わさって一つのリソースと考える。scaffoldを使うとリソース単位で自動生成してくれる。
scaffoldをしてサーバーを立ち上げると、その時点からuserリソースを扱える。/users/newとかできる。migration file
これはrailsとDBが全く別物だから、migration fileに書いて〜を保存するスペース作ってくれない?とお願いをしなければいけない。なので、rails generate scaffold User name:string email:stringなどの指示を出した後はrails db:migrateをしないとDBにリソースを保存することができない。ちなみに、rails db:migrateをせずにrails sをすると、 migration pending(DBに何かお願いすることあるんじゃないの?)というエラーを出してくれる。rails db:migrateはべきとうせいである。つまり何度実行しても追加したmigration fileがなければ何も起こらない。
resources
これはルーティングのパッケージ版。本来 /users => user#indexなど基本のルーティングがパッケージングされてる。
actionのデフォルトview
例えばindexアクションを呼び出した時、renderなどのviewを呼び出す指示が書いてないことがほとんど。その場合は、アクション名からapp/views/users/index.html.erbが呼び出される仕組みになっている。
後半戦
bundle exec
このコマンドはgem fileを読み込んだ上でexec以下のコマンドを実行するというもの。 ex)bundle exec rails --version
テーブルの関連付け
class User < ApplicationRecord has_many :microposts endclass Micropost < ApplicationRecord belongs_to :user validates :content, length: { maximum: 140 } endこれによって、User.first.micropostsなどが実行できる。
そもそもなんで上記のコマンドでテーブルが結合される?
これはrailsのデフォルトによるもの。
micropostsテーブルにはuser_idというカラムがある。
model名_idというカラムがあり、結合すると、model名(ここではusersテーブル)のidカラムと自動的に結合してくれるから。validation
class Micropost < ApplicationRecord validates :content, length: { maximum: 140 } endこれによって投稿の文字数を140文字に制限することができる。
heroku注意点
上記のようにテーブルを作った状態でherokuにアップしようとすると、エラーになってしまう。
それはmigrationfileが開発用には適用されたが、本番環境では適応されていないから。開発環境用のDBと本番環境用のDBが違うことから起きる。そのため、 heroku run rails db:migrate としてあげる。
つまり、migrationfileを一つでも作ったら、git push heroku masterをした後に上記のコマンドを実行しなければいけないということ。
また、DBは違えど、Active RecordによってSQLが適切に書かれるのでそこを気にする必要はない。
- 投稿日:2019-11-29T19:13:26+09:00
【Rails】Docker+Rails環境でpryの`edit`コマンドが使えなかったので、使えるようにした
はじめに
DockerとRailsを使った開発環境でpryの
edit
コマンドが効かなかったので対処してみたときの記録です。※Docker環境下での話です。ローカルでは普通に動作しましたので、割愛します。
この記事が役に立つ方
- あれ?なんか
edit
コマンド効かなくね?と思っている方この記事のメリット
- Docker環境下でpryの
edit
コマンドが使えるようになる環境
- macOS Catalina 10.15.1
- zsh: 5.7.1
- Ruby: 2.6.5
- Rails: 5.2.3
- Docker: 19.03.5
- docker-compose: 1.24.1
pryの
edit
コマンドとは?以下、公式のWikiです。
Editor integration · pry/pry Wiki · GitHubThe edit command is used to invoke your default editor. This command will load your file once you have finished editing it (unless you pass the -n or --no-reload flag to edit).
pryの画面でそのままデフォルトのエディタを開き、ファイルの編集が出来るコマンドです。
これまでpryの画面上からファイルをいじったりはしていなかったのですが、デバッグ効率が上がるかなと思ったので導入します。
【事前準備】
こちらのドキュメントに従ってDocker環境下でgem pry-railsを使うための準備は済ませておきました。
Using pry-rails with Docker · GitHub
上記ドキュメントの流れは以下の通りです。
docker-compose.ymlapp: tty: true stdin_open: true上記設定を追加。
↓Gemfilegroup: :development gem 'pry-rails' endgemを追加。
↓docker-compose buildビルド。
↓docker-compose up起動。
↓docker psRailsアプリの
CONTAINER ID
かNAMES
を確認
↓docker attach `CONTAINER ID`か`NAMES`↓
binding.pry ※好きなところでこれで普通にpryでデバッグ可能な状態になります。
とりあえず
edit
コマンド使ってみようとする※Docker環境下です。
とりあえず
edit
コマンドを叩いてみます。[1] pry(main)> edit Error: Please set Pry.config.editor or export $VISUAL or $EDITORするとエラー発生。
Pry.config.editor
か、環境変数$VISUAL
か$EDITOR
を設定してねという内容。先程のWikiの、Setting the default editorのくだりに設定方法が記載されていたので、その通りに進めてみます。
1.
~/.pryrc
を作成・設定pryの設定ファイルは
.pryrc
です。
今回はなかったので作成しました。以下設定方法についてです。
Editor integration · pry/pry Wiki · GitHub今回エディタはvimを使いたかったので、以下のように設定。
~/.pryrcPry.config.editor = proc { |file, line| "vim #{file}:#{line}" }該当のファイルと行に飛んでくれる仕様に例にならってみましたが、うまくいきません。
ローカルの設定ファイルは読み込まれないんですね。
2.Railsアプリのディレクトリ内に
.pryrc
を配置する。下記記事を参照し、Railsアプリのルートディレクトリに
.pryrc
を配置してみることにしました。docker-compose上のRailsのデバッグを行う - My External Storage
./.pryrcPry.config.editor = proc { |file, line| "vim #{file}:#{line}" }※設定は先程と同様です。
もう一回試します。
[1] pry(main)> edit Error: `vim ファイル名:行数` gave exit status: 127別のエラー発生。
惜しい!コマンド自体は正しそうに見えますが、127
なるエラーが。何これ?と思って調べると、
「コマンドが見つからない、もしくはタイポ」
らしいと判明。...ということは、そもそも
vim
がない?3.
vim
をインストールするなければいくらコマンドが正しくても、どうしようもないですよね。
調べると、こちらの記事を発見しました。
Docker — docker コンテナの中で vim が使えない場合 - QiitaDockerfileapt-get install vim上記を追記。
↓docker-compose build↓
docker-compose up↓
rails c
などでpry起動。[1] pry(main)> edit #=>vim起動無事起動!
おわりに
最後まで読んで頂きありがとうございました
vimがないという環境もあるのかとびっくりしましたが、ローカルとDocker環境の境目を意識するいい経験になりました
これでデバッグ効率が上がりそうです
参考にさせて頂いたサイト(いつもありがとうございます)
- 投稿日:2019-11-29T18:25:57+09:00
インスタのような感じのアプリを作るスパイラル仕様
1.文字投稿サイト
ヘッダーのパーシャルを使う。
文字投稿はRESTfulな入力フォームを使う。2.ユーザー登録機能
ヘッダーにifでログイン状態のときに表示を変える。
投稿に対して名前が付与される。この時に関連付けが利用される。
postテーブルの関連付けからuser.nameをひっぱる。postにはuser_idを追加簡略化のために名前とメールアドレスだけで認証を行う。
パスワードはあとでok2.5投稿に○○さんの投稿と名前をつける
ログインしていない投稿にはifでゲストさんにしておいた。3.画像機能
画像が投稿で追加される。
取得できればそんなに難しくない。3.5 いいね機能
そんなに難しくはないと思う。4.関連付けでフォロー
できないことはないと思う。これ以降
細かい機能を取り付け
・投稿、ユーザーの検索
・投稿への返信機能
・ユーザーへのメッセージ機能
・フォロワー通知
・バリデーション
・デザイン
・Restfulな書き方、美コード化
・各機能等のテスト方法考案
・フォロー機能
・いいね機能
・基本的なログイン(パスワード仕様)
・発展的ログイン(クッキー)
・sns紐付けログイン
・投稿詳細ページ
・ユーザー詳細ページ
・アカウント有効化メール
・パスワード再設定機能(メールなし・あり
・投稿の削除・編集
・ユーザーの削除・編集
- 投稿日:2019-11-29T15:30:54+09:00
【Ruby on Rails】開発環境構築(MacOS)
MacOSの状態を確認
Catalina以降の場合とMojave以前の場合で環境構築の設定が異なります。
OSがMojave以前の場合の環境構築
❶Command Line Toolsを用意
Command Line ToolsはWebアプリケーション開発に必要なソフトウェアをダウンロードするために必要な機能です。
$ xcode-select --install❷Homebrewを用意
$ cd #ホームディレクトリに移動 $ pwd #ホームディレクトリにいるかどうか確認 $ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" #コマンドを実行※処理に時間がかかる可能性のある操作です。
「
Press RETURN to continue or any other key to abort
」と表示されたら、「続けるにはエンターキーを、やめるにはそれ以外の入力をしてください
」という意味ですので、エンターキーを入力します。「
Password:
」と表示されたら、PCを開く際に求められるパスワードを入力します。ターミナル上で文字は表示されませんが、間違いなく入力はされています。パスワードを入力し終わったらエンターキーを押します。$ brew -v #Homebrewがインストールされているか確認 Homebrew 1.8.0$ brew update #Homebrewを最新状態にする$ sudo chown -R `whoami`:admin /usr/local/bin #Homebrewの権限を変更❸新しいバージョンのRubyをインストール
$ brew install rbenv ruby-build #Homebrewを用いてrbenvとruby-buildをインストール$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile #PCにおけるどこのディレクトリからも使用できるようにする$ source ~/.bash_profile #bash_profileの変更を反映$ brew install readline #readlineをインストール$ brew link readline --force #どこからも使用できるように# rbenvを利用してRubyをインストール $ RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline)" $ rbenv install 2.5.1$ rbenv global 2.5.1 #利用するRubyのバージョンを指定$ rbenv rehash #rbenvを読み込んで変更を反映$ ruby -v #Rubyのバージョンを確認先ほどインストールした2.5.1であることが表示されれば完了です。
❹MySQLを用意
MySQLは、Webアプリケーションにおけるデータを蓄積する場所のこと。
# MySQLのインストール $ brew install mysql@5.6 $ brew reinstall https://raw.githubusercontent.com/Homebrew/homebrew-core/f171f1c74/Formula/mysql@5.6.rb※処理に時間がかかる可能性のある操作です。
# MySQLの自動起動設定 $ mkdir ~/Library/LaunchAgents $ ln -sfv /usr/local/opt/mysql\@5.6/*.plist ~/Library/LaunchAgents $ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql\@5.6.plist# mysqlのコマンドをどこからでも実行できるようにする $ echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.bash_profile $ source ~/.bash_profile# mysqlのコマンドが打てるか確認する $ which mysql# 以下のように表示されれば成功 /usr/local/opt/mysql@5.6/bin/mysql❺Railsを用意
Railsというフレームワークを用いて、Rubyでアプリケーションを作成します。
$ gem install bundler #bundlerをインストール$ gem install rails --version='5.2.3' #Railsをインストール※処理に時間がかかる可能性のある操作です。
$ rbenv rehash #rbenvを再読み込みしておく$ rails -v #Railsが導入できたか確認Mojave以前の場合は、これにてWebアプリケーション開発のための環境構築は完了です。
OSがCatalina以前の場合の環境構築
# zshをデフォルトに設定 $ chsh -s /bin/zsh# ログインシェルを表示 $ echo $SHELL# 以下のように表示されれば成功 /bin/zshもし
echo $SHELL
コマンドで、/bin/zsh
と表示されなかった方は、一度ターミナルを閉じて再度開いた上でもう一度echo $SHELL
コマンドを実行します。(“$
”が“%
”になっている可能性もあります)❶Command Line Toolsを用意
$ xcode-select --install #Command Line Toolsをインストール❸Homebrewを用意
$ cd #ホームディレクトリに移動 $ pwd #ホームディレクトリにいるかどうか確認 $ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" #コマンドを実行※処理に時間がかかる可能性のある操作です。
「
Press RETURN to continue or any other key to abort
」と表示されたら、「続けるにはエンターキーを、やめるにはそれ以外の入力をしてください
」という意味ですので、エンターキーを入力します。「
Password:
」と表示されたら、PCを開く際に求められるパスワードを入力します。ターミナル上で文字は表示されませんが、間違いなく入力はされています。パスワードを入力し終わったらエンターキーを押します。$ brew -v #Homebrewがインストールされているか確認$ brew -v Homebrew 2.1.13$ brew update #Homebrewをアップデート$ sudo chown -R `whoami`:admin /usr/local/bin #Homebrewの権限を変更❸新しいバージョンのRubyをインストール
$ brew install rbenv ruby-build #Homebrewを用いてrbenvとruby-buildをインストール$ echo 'eval "$(rbenv init -)"' >> ~/.zshrc #PCにおけるどこのディレクトリからも使用できるようにする$ source ~/.zshrc #zshrcの変更を反映$ brew install readline #readlineをインストール$ brew link readline --force #どこからも使用できるように# rbenvを利用してRubyをインストール $ RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline)" $ rbenv install 2.5.1※処理に時間がかかる可能性のある操作です。
$ rbenv global 2.5.1 #利用するRubyのバージョンを指定$ rbenv rehash #rbenvを読み込んで変更を反映$ ruby -v #Rubyのバージョンを確認❹MySQLを用意
# MySQLのインストール $ brew install mysql@5.6 $ brew reinstall https://raw.githubusercontent.com/Homebrew/homebrew-core/f171f1c74/Formula/mysql@5.6.rb※処理に時間がかかる可能性のある操作です。
# MySQLの自動起動設定 $ mkdir ~/Library/LaunchAgents $ ln -sfv /usr/local/opt/mysql\@5.6/*.plist ~/Library/LaunchAgents $ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql\@5.6.plist# mysqlのコマンドをどこからでも実行できるようにする $ echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.zshrc $ source ~/.zshrc# mysqlのコマンドが打てるか確認する $ which mysql# 以下のように表示されれば成功 /usr/local/opt/mysql@5.6/bin/mysql❺Railsを用意
$ gem install bundler #bundlerをインストール$ gem install rails --version='5.2.3' #Railsをインストール$ rbenv rehash #rbenvを再読み込みしておく$ rails -v #Railsが導入できたか確認Catalina以降の場合は、これにてWebアプリケーション開発のための環境構築は完了です。
- 投稿日:2019-11-29T15:07:32+09:00
#Rails ( or #Ruby + ActiveSupport ) : Time.zone.now VS Time.current what is difference? when specify Time.zone then used ActiveSupport::TimeWithZone class else use Time class
require 'active_support/core_ext' # No Time.zone setting Time.use_zone(nil) { Time.current } # => 2019-11-29 07:38:04 +0900 Time.use_zone(nil) { Time.current.class } # => Time # with Time.zone setting # Time.current class changed Time to ActiveSupport::TimeWithZone Time.use_zone('UTC') { Time.current } # => Thu, 28 Nov 2019 22:38:20 UTC +00:00 Time.use_zone('UTC') { Time.current.class } # => ActiveSupport::TimeWithZone Time.use_zone('UTC') { Time.zone.now } # => Thu, 28 Nov 2019 22:38:26 UTC +00:00 Time.use_zone('UTC') { Time.zone.now.class } # => ActiveSupport::TimeWithZone require 'active_support/testing/time_helpers' include ActiveSupport::Testing::TimeHelpers # Freeze time Time.use_zone('UTC') { travel_to Time.parse('2020-01-01 00:00') } Time.use_zone('UTC') { Time.current == Time.zone.now } # => true # NOTE # Time.parse class not changed to ActiveSupport::TimeWithZone Time.parse('2020-01-01 00:00') # => 2020-01-01 00:00:00 +0900 Time.parse('2020-01-01 00:00').class # => Time Time.use_zone('UTC') { Time.zone.parse('2020-01-01 00:00') } # => Wed, 01 Jan 2020 00:00:00 UTC +00:00 Time.use_zone('UTC') { Time.zone.parse('2020-01-01 00:00').class } # => ActiveSupport::TimeWithZoneOriginal by Github issue
- 投稿日:2019-11-29T15:07:31+09:00
#Rails ( or #Ruby + ActiveSupport ) + travel_to : freeze to localtime beggining of year and ref UTC date and time and timestamp
# REQUIRE : on Ruby require 'active_support/testing/time_helpers' # => true require 'active_support/core_ext' # => true include ActiveSupport::Testing::TimeHelpers # => Object # Travel to JST beggining of Year travel_to Time.use_zone('Tokyo') { Time.parse('2020-01-01 00:00') } # => nil # JST Time.use_zone('Tokyo') { Time.current } # => Wed, 01 Jan 2020 00:00:00 JST +09:00 Time.use_zone('Tokyo') { Time.zone.now } # => Wed, 01 Jan 2020 00:00:00 JST +09:00 Time.use_zone('Tokyo') { Date.current } # => Wed, 01 Jan 2020 # UTC Time.use_zone('UTC') { Time.current } # => Tue, 31 Dec 2019 15:00:00 UTC +00:00 Time.use_zone('UTC') { Time.zone.now } # => Tue, 31 Dec 2019 15:00:00 UTC +00:00 Time.use_zone('UTC') { Date.current } # => Tue, 31 Dec 2019 # Unix Timestamp # are All same at any Timezone Time.use_zone('Tokyo') { Time.zone.now.to_i } # => 1577804400 Time.use_zone('UTC') { Time.zone.now.to_i } # => 1577804400 Time.now.to_i # => 1577804400 Time.current.to_i # => 1577804400 # UTC vs JST near date change line travel_to Time.use_zone('Tokyo') { Time.parse('2020-01-01 08:59') } Time.use_zone('UTC') { Date.current } # => Tue, 31 Dec 2019 travel_to Time.use_zone('Tokyo') { Time.parse('2020-01-01 09:00') } # => nil Time.use_zone('UTC') { Date.current } # => Wed, 01 Jan 2020Original by Github issue
- 投稿日:2019-11-29T15:07:31+09:00
#Rails ( or #Ruby + ActiveSupport ) の Time.zone.now と Time.current の違いは Time.zone 指定の有無であり、Time class と ActiveSupport::TimeWithZone class が切り替わることが判明
require 'active_support/core_ext' # No Time.zone setting Time.use_zone(nil) { Time.current } # => 2019-11-29 07:38:04 +0900 Time.use_zone(nil) { Time.current.class } # => Time # with Time.zone setting # Time.current class changed Time to ActiveSupport::TimeWithZone Time.use_zone('UTC') { Time.current } # => Thu, 28 Nov 2019 22:38:20 UTC +00:00 Time.use_zone('UTC') { Time.current.class } # => ActiveSupport::TimeWithZone Time.use_zone('UTC') { Time.zone.now } # => Thu, 28 Nov 2019 22:38:26 UTC +00:00 Time.use_zone('UTC') { Time.zone.now.class } # => ActiveSupport::TimeWithZone require 'active_support/testing/time_helpers' include ActiveSupport::Testing::TimeHelpers # Freeze time Time.use_zone('UTC') { travel_to Time.parse('2020-01-01 00:00') } Time.use_zone('UTC') { Time.current == Time.zone.now } # => true # NOTE # Time.parse class not changed to ActiveSupport::TimeWithZone Time.parse('2020-01-01 00:00') # => 2020-01-01 00:00:00 +0900 Time.parse('2020-01-01 00:00').class # => Time Time.use_zone('UTC') { Time.zone.parse('2020-01-01 00:00') } # => Wed, 01 Jan 2020 00:00:00 UTC +00:00 Time.use_zone('UTC') { Time.zone.parse('2020-01-01 00:00').class } # => ActiveSupport::TimeWithZoneOriginal by Github issue
- 投稿日:2019-11-29T15:06:56+09:00
初めてのRuby on Rails
はじめに
このQiitaは、プログラミング未経験の初心者がRailsチュートリアルに取り組み
学んだことを記録、振り返るために書いていきます。今までやったこと
Progateで
- HTML
- CSS
- Ruby
- Ruby on Rails5
- Git
- CommandLine
- SQL
- HTMLとCSSを学んだ後に一度架空のホームページの見た目だけ作成
PC環境
MacBook Pro Mid2012(古いです)
macOS Mojave ver 10.14.6
エディタ
- Visual Studio Code ver 1.40.2
最初のアプリケーション
「Hello,World」を表示するプログラム
1.ターミナルでcd..... ホームディレクトリに移動
2.cd environment.....作成したenvironmentディレクトリに移動
3.rails new.....railsアプリの骨組みを作成ターミナルで
$ rails _5.1.6_ new hello_appこれでrails 5.1.6を使ったhello_appというファイルが作られる
rails newとは
新規ファイルの作成
RsilsはRubyを使ったフレームワークなのである程度必要なファイルなどを自動作成してくれる。
$ rails new blog_app(作りたいプロジェクト名)これによりblog_appというファイルが作られ、必要なものが中に自動生成される。
rails newのオプション
rails newをする際に様々な条件を付けることができる
Railsのバージョンの指定
railsのバージョンを指定して作成できる
$ rails new _5.1.6_ new blog.app5.1.6の部分に好きなバージョンを入れることで、そのバージョンで作成できる。
データベースの指定
rails new時、データベースのデフォルトはsqliteになる。
rails new した後に変更することも可能だが、最初から使うデータベースが決まっているなら最初に設定しておける。$ rails new blog_app -database=mysqlこれはMySQLを使うときのコマンド
$rails new blog_app -database=postgresqlこれはPostgresqlを使うときのコマンド
そのほかにもrails new するときのオプションはたくさんある
また今度調べよう
Bundler
Bundlerは必要なgemをインストールするコマンド
$ budle※railsにおけるgemとはrubyのライブラリのこと
rubyに必要な機能の管理場所のようなもの通常rails newをすると最低限必要なgemは自動でbundlerしてくれる
チュートリアル通りgemの内容を変更し、bundlerを再実行する
gemはテキストエディタでhello_appディレクトリにあるgemfileで触れる
今回は下記の様にgemを書き換える
source 'https://rubygems.org' gem 'rails', '5.1.6' gem 'puma', '3.9.1' gem 'sass-rails', '5.0.6' gem 'uglifier', '3.2.0' gem 'coffee-rails', '4.2.2' gem 'jquery-rails', '4.3.1' gem 'turbolinks', '5.0.1' gem 'jbuilder', '2.6.4' group :development, :test do gem 'sqlite3', '1.3.13' gem 'byebug', '9.0.6', platform: :mri end group :development do gem 'web-console', '3.5.1' gem 'listen', '3.1.5' gem 'spring', '2.0.2' gem 'spring-watcher-listen', '2.0.1' end書き換えたらターミナルでbundle
発生したエラー
You have requested: spring = 2.0.2 The bundle currently has spring locked at 2.1.0. Try running `bundle update spring` If you are updating multiple gems in your Gemfile at once, try passing them all to `bundle update`あなたがリクエストしたのはspring = 2.0.2だけどspring = 2.1.0で設定してるから無理だよ!
変えるならbundle updateしてね!
的なことを言っているので素直にbundle updateを実行
エラーは治ったそして再度bundle
正常に作動して必要なgemをインストールした
Railsサーバーの立ち上げ
$ rails serverrailsはこのコマンドを入れるだけで、ローカルWebサーバーを立ち上げることができる
$ rails sでもいいらしい
rails s をするときは別ターミナルタブで行った方が他のコマンドも打てるので良い
入力したら
にアクセスしてみる
サーバーの立ち上げに成功していれば
Yay! You’r on Rails! と可愛らしいイラスト付きで表示が出る。
Railsのサーバーの立ち上げができた
立ち上げること自体は本当に簡単でたった数個の記述で出来た
数行の記述でブログが作れる!みたいな記事もあったので
このお手軽さがrailsが初学者に人気の理由なのかなRailsチュートリアル自体が最初は細かい部分が省かれている感じなので
rails全体を掴むために疑問点はメモしておき、
確認の1週目ということにして書いてある通りにさくさく進めてみよう
- 投稿日:2019-11-29T14:59:17+09:00
RubyからRustを呼び出すいくつかの方法のまとめ
この記事はAteam Hikkoshi Samurai Inc. & Ateam Connect Inc.(エイチーム引越し侍、エイチームコネクト) Advent Calendar 2019 1日目の記事です。
プログラムのパフォーマンス向上のためにRubyからCで書かれた処理を呼び出したいという場合があるとき、拡張ライブラリを書いたりFFIを使って処理を呼び出したりします。
今回はRustで書かれた処理をRubyで呼び出す方法を調べてみました。拡張ライブラリを書く方法
RubyにはCのためのAPIがあり、これを使うことで拡張ライブラリを作ることができます。RustにはCのプログラムとリンクさせるライブラリを作ることができるので、その機能を利用してRustでRubyの拡張ライブラリを作ることができます。
詳細は以下の記事をご参照ください。
RustだけでRuby native extensionを書くFFIで呼び出す方法
Cで拡張ライブラリを記述するよりも、より簡単な方法としてFFI(Foreign function interface)を利用する方法もあります。
FFIを使った場合にはC拡張を書くことなく、利用することができるので、より簡単に使うことができます(内部的にはC拡張の仕組みと同じものが動きます)。詳細は以下の記事をご参照ください。
RubyからRustの関数をつかう → はやいHelixを使う方法
Rustで簡単に拡張ライブラリを書くための方法としてhelixというgemが
あります。Qiita界隈ですとHelixといえばネームスペースはキーボードに占有されている気もしますが、RustとRubyのブリッジというこよで実在する橋が名前の由来のようです。
Getting Startedのページを見るといきなりRuby on Railsのプロジェクトから作り始めるなかなかに攻めた内容になっていますが、今回はシンプルにgemを作って、それを呼び出す方法を見てみます。
前章の参照記事で書かれているものとほぼ同じフィボナッチ数を求める関数を作成していきます。gemテンプレートの作成
まずはgemのプロジェクトの雛型を作ります。
$ bundle gem helix_fib Creating gem 'helix_fib'... MIT License enabled in config Code of conduct enabled in config create helix_fib/Gemfile ... $ cd helix_fibプロジェクトの作成が完了したら、helix_fib.gemspecに
以下の内容を追記します。spec.add_runtime_dependency "helix_runtime", "= 0.7.5"またspec.authorsなどTODOが記載されている項目は適当に記入します。
記入が終わったらbundle installを行って依存関係にあるhelix_runtimeをインストールします。bundle install --path=vendor/bundle次にRakeファイルを以下のように書き換えてbuild時にhelix_runtimeが使われるように修正します。
require 'bundler/setup' require 'helix_runtime/build_task' HelixRuntime::BuildTask.new do |t| end task :default => :buildrustテンプレートの作成
まず以下のコマンドを実行してrustのプロジェクトを作成します。
$ cargo init --libこのコマンドによってCargo.tomlとsrc/lib.rsが作られます。
他言語用のダイナミックなシステムライブラリを作成したいためにCargo.tomlに以下の記載を加えます。[lib] crate-type = ["cdylib"]またdependenciesにはhelixを加えます
[dependencies] helix = "0.7.5"次にRustのコードを書いていきます。src/lib.rsに以下の内容を記述します。
#[macro_use] extern crate helix; ruby! { class HelixFIB { struct { } def initialize(helix) { HelixFIB{helix} } def fib(&self, n: u32) -> u32 { if n <= 1 { n } else { self.fib(n - 1) + self.fib(n - 2) } } } }lib/helix_fib.rbの先頭に以下の2行を加えてビルドが通るように修正します。
require 'helix_runtime' require 'helix_fib/native'ビルドと動作確認
rakeコマンドを行ってrustのコードをビルドします。
$ bundle exec rake {} cargo rustc --release -- -C link-args=-Wl,-undefined,dynamic_lookup ... Finished release [optimized] target(s) in 0.08sビルドが完了したらbin/consoleで動作を確認します。
$ bin/console irb(main):001:0> helix_fib = HelixFIB.new => #<HelixFIB:0x00007fffc4a4afd0> irb(main):002:0> helix_fib.fib(40) => 102334155rustで書いたコードがrubyのクラスとして呼び出されて、関数が機能していることが確認できます。
gem化とrubyプロジェクトからの呼び出し
gemのパッケージ化をした際にinstall時にビルドされる必要があるためhelix_fib.gemspecに以下の内容を追記します。
spec.extensions = %w[extconf.rb]またextconf.rbには以下の内容を記述します。
execonf.rbabort "Rust compiler required (https://www.rust-lang.org/)" if `which rustc`.empty? File.open("Makefile", "wb") do |f| f.puts(<<EOD) all: \tbundle --deployment --path vendor/bundle \tbundle exec rake clean: install: \trm -r vendor/bundle target EOD endgemのbuildの際にディレクトリの内容をgit ls-filesで検索して依存性を解決するので、これまでの作業内容をgitに記録します。
git add .gem buildを行うとgemファイルが完成します。
$ gem build helix_fib Successfully built RubyGem Name: helix_fib Version: 0.1.0 File: helix_fib-0.1.0.gem
新しいディレクトリを作成して、bundle initして作成されたGemfileにhelix_runtimeとhelix_fibを追記します。
gem "helix_runtime" gem "helix_fib"vender/cacheのディレクトリを作成したあとに先ほど作成したhelix_fib-0.1.0.gemをコピーして、bundle install --path=vendor/bundleを行います。
helix_fibを呼び出す処理は以下のように記述します。sample.rbrequire 'helix_fib' helix_fib = HelixFIB.new puts helix_fib.fib(40)作成したファイルを実行すると以下のような結果が返ってきてRustで作成したファイルが実行されていることがわかります。
$ bundle exec ruby sample.rb 102334155Wasmを使う方法
Rustで書かれたコードをWasmにコンパイルする手法はよく知られているものだと思いますが、最近ではCloudfrare Workersや、Compute@Edgeなど、フロントエンドだけでなく、サーバーサイドよりの領域でも使われるようになっていました。そしてWASIというWASMをウェブブラウザー以外から呼び出すための仕様も策定されています。
今回はRustで書かれたコードをWasmにコンパイルして、それをRubyから呼び出す方法について記載しようと思います。
wasmerというスタンドアローンなWasmのランタイムとそれを呼び出すgemが存在するので、今回はこれを利用します。環境の準備
まずはrustupを使ってターゲットとするコンパイラにwasmを追加します。
rustup target add wasm32-unknown-unknownプロジェクトの作成とCargo.tomlの整備
cargoコマンドを使ってWasm用のプロジェクトを作成します。
$ cargo new --lib wasm_fibCargo.tomlに以下の内容を追記します。
Cargo.toml... [lib] crate-type =["cdylib"]プログラムの作成
src/lib.rsに以下の内容を記載します。多言語から呼び出すので、[no_mangle]を付けて関数のマングリングをなくします。
lib.rs#[no_mangle] pub extern fn fib(n: u32) -> u32 { if n <= 1 { n } else { fib(n - 1) + fib(n - 2) } }Wasmへのコンパイル
cargo buildでwasmへのコンパイルを行います。
$ cargo build --target wasm32-unknown-unknownコンパイルが完了するとtarget/wasm32-unknown-unknown/debugにwasm_fib.wasmが作成されます。
Rubyのプロジェクトの作成
Wasmを呼び出すためのRubyのプロジェクトを作成します。
新しいディレクトリを作成してbundle initを行って作成されたGemfileに以下の記述を追加して、bundle installを行います。gem "wasmer"Wasmを呼び出すプログラムの作成
前章で作成したwasm_fib.wasmをプロジェクトのディレクトリにコピーした後にsample.rbというファイルを作成し、以下の記述を行います。
sample.rbrequire "wasmer" bytes = IO.read "wasm_fib.wasm", mode: "rb" instance = Wasmer::Instance.new bytes puts instance.exports.fib 40wasmのバイト列をWasmer::Instance.newの引数として渡すことで、wasmがインスタンス化され、関数を呼べるようになります。
プログラムを実行して結果を確認します。$ bundle exec ruby sample.rb 102334155ベンチマーク
rubyのベンチマーク関数を使ってフィボナッチ数の関数に40を渡したときの結果を出力してみます。
ruby
$ ruby fib.rb user system total real fibonatti 11.750000 0.000000 11.750000 ( 11.748531) 102334155ffi
$ ruby rust-fib.rb user system total real fibonatti 0.593750 0.000000 0.593750 ( 0.588983) 102334155拡張ライブラリ
$ ruby rust-fib.rb user system total real fibonatti 1.218750 0.000000 1.218750 ( 1.232219) 102334155Helix
bundle exec ruby sample.rb user system total real fibonatti 0.625000 0.000000 0.625000 ( 0.627916) 102334155Wasm
$ bundle exec ruby sample.rb user system total real fibonatti 5.125000 0.000000 5.125000 ( 5.144103) 102334155結果としては、Ruby < Wasm < 拡張ライブラリ < Helix < FFI
の順番になりました。拡張ライブラリがHelixより遅いのはもっと検証が必要かもしれませんが、Wasmはランライムの呼び出しにコストがかかっているように感じました。
しかし、いずれの方法でもRubyに比べてパフォーマンスは向上しているようです。まとめ
Helixをつかうことによって、RubyのC APIの内容をあまり知らずとも、Rustで拡張ライブラリを書くことができるようになりました。
ruby!やclassというおよそRustとは思えないコードであったり、
多言語から呼び出す処理に#[no_mangle]がないなど、不思議な感じが
する部分もありますが、簡単に拡張ライブラリが書けるということには少なからずメリットがあると思います。
Wasmに関してはFFIと同じような感覚があります。Rustで記述されたものをRubyで呼び出すためにWasmにする必要はなさそうに思えますが、wasmerの他にもwasmtimeやlucetなど、いくつかランタイムが登場していているので、興味深い分野だと思いました。お知らせ
エイチームグループでは一緒に活躍してくれる優秀な人材を募集中です。
興味のある方はぜひともエイチームグループ採用ページ(Webエンジニア詳細ページよりお問い合わせ下さい。
あるいは、Qiita Jobsの引越し侍Webエンジニアチーム、自社サービスWebエンジニア/インフラからサービス改善まで!、求む!引越し侍大規模リプレイスにおけるフロントエンドのリードエンジニア募集!の記事をみて、興味が出てきた方は是非Qiita Jobsのチャットでメッセージをください。明日
明日はfujimkの記事です。
きっとこれがQiita初投稿かな。皆さんお楽しみに。
- 投稿日:2019-11-29T14:50:20+09:00
rubyのバージョンアップ
rubyのバージョンアップする時の方法
この記事は私の備忘録として記載していきます。
何か間違っている場合があれば、ご指摘していただけると助かります。現在の環境
ruby 2.6.3
rails 5.2.3目的
rubyのバージョンを2.6.5にしたいです。
環境にバージョンがあるか確認します。
$ rbenv install --listもし、指定したいバージョンがなければ
$ brew upgrade ruby-buildをしてください。
もう一度確認で
$ rbenv install --listrbenvでインストール
指定したいバージョンがあれば次に、rbenvでインストールをします。
$ rbenv install 2.6.5インストールされました!!
インストールしたバージョンを使用する時
$ rbenv versions system 2.4.1 2.5.1 2.5.3 * 2.6.3 (set by /Users/user_name/hoge/.ruby-version) 2.6.5
- 環境全体にrubyバージョンを反映したい時
$ rbenv global 2.6.5
- 特定のプロジェクトのみ反映したい時
$ rbenv local 2.6.5もう一度確認で
$ rbenv versions system 2.4.1 2.5.1 2.5.3 2.6.3 * 2.6.5(set by /Users/user_name/hoge/.ruby-version)反映されました。
rubyのバージョンごとにgemをインストールしなければいけないので
$ gem install bundlerインストールできました。
Gemfileに使用するバージョンを書き換えて$ bundle install --path=vendor/bundleできました。
rubyのバージョンを上げた後にrailsコマンドを使おうとすると、下記のようになってしまった。
ruby.rb$ rails --version rbenv: rails: command not found The `rails' command exists in these Ruby versions: 2.6.5新しくインストールしたrubyの中にrailsというgemが入っていないために起こっているようだ。
目的
新しくインストールしたruby環境にrailsをインストールをする
まず、gemをアップデートします$ gem update --system次にbundlerをインストールします
$ gem install bundlerその次にrailsをインストールします
$ gem install railsバージョンを確認します。
$ rails --version Rails 5.2.3バージョンが表示されたらrails コマンドが使えるようになっている
まとめ
初めてのQiita記事でした。記事一つ書くだけでも長時間かかってしまいました。
本日はバージョンのアップデートやバージョン指定など頻繁にあるので、備忘録としてここに書かせて頂きました。
どんどん記事を書いてアウトプットしていきたいと思います。この記事は、下記の記事を参考にさせて頂きました。
rbenvでrubyのバージョンを上げたときに The `rails' command exists in these Ruby versions: となる
とても、勉強になりました。ありがとうございます。
- 投稿日:2019-11-29T14:46:18+09:00
【Ruby on Rails】新規アプリケーション作成まとめ
Rubyとは
Ruby(プログラミンング言語)
Rubyはプログラミング言語の一つであり、Javaなどの他のプログラミング言語に比べて、少ないソースコードの記述量でプログラムを組めるなどの特徴があります。(Ruby公式サイト)
Ruby on Rails
Ruby on Rails
RubyのWebアプリケーションフレームワークの一つであり、Webアプリケーションフレームワークとは、Webアプリケーションを簡単に作れるようにする骨組みのことです。
フレームワークを使用することで、開発者はより少ない労力で開発を行うことが可能になります。Rails開発環境の構築
Rails開発ではデータベース(MySQL)やRails自体のインストールなど、まずは開発のできる環境を整える必要があります。(Rails開発環境構築)
- Homebrewのインストール・アップデート
- rbenv・ruby-buildのインストール
- Ruby ver.2.5.1のインストール
- MySQLのインストール
- MySQLのインストール
新規アプリケーションを作成
$ rails new アプリケーション名 # アプリケーションを新規作成 $ rails new アプリケーション名 -オプション名 # オプションを付けてアプリケーションを作成Gemの追加
Gem
RailsはGem(じぇむ)と呼ばれる便利なrubyライブラリをインストールして利用することで、より簡単にアプリケーション開発をすることができます。gemには開発を効率化してくれたり、会員登録サービスが簡単にできるなど便利なものがたくさんあります。Railsアプリケーションは全てをゼロから作るのではなくgemのライブラリも利用することでより効率的に開発することができます。
Gemfileに追加したら必ずターミナルでbundle installを実行します。
gem 'Gem名' # Gemの追加$ bundle install # Gemをインストールデータベースの作成
データベース
データベースとはたくさんの情報(データ)が入っている箱のようなものです。この中には様々な種類のデータが存在していて、データを格納したり、取り出すことができます。Railsでは運用環境ごとにひとつのデータベースを持っています。
運用環境
運用環境 概要 development 開発環境。通常、開発をする際に使用する環境。 production 本番環境。アプリケーションを実際にリリースする際に使用する環境。 test テスト環境。アプリケーションの動作をテストする際に使用する環境。 database.ymlファイルの内容に基づいてデータベースを新規作成します。
$ rake db:create # データベースの作成
- 投稿日:2019-11-29T14:35:59+09:00
#Rails ( or #Ruby + ActiveSupport ) + travel で 現地時刻 = JST で特定日付に時間を固定して、それぞれUTC JST timestamp で現在時刻・日付を得る例
# REQUIRE : on Ruby require 'active_support/testing/time_helpers' # => true require 'active_support/core_ext' # => true include ActiveSupport::Testing::TimeHelpers # => Object # Travel to JST beggining of Year travel_to Time.use_zone('Tokyo') { Time.parse('2020-01-01 00:00') } # => nil # JST Time.use_zone('Tokyo') { Time.now } # => 2020-01-01 00:00:00 +0900 Time.use_zone('Tokyo') { Time.current } # => Wed, 01 Jan 2020 00:00:00 JST +09:00 Time.use_zone('Tokyo') { Time.zone.now } # => Wed, 01 Jan 2020 00:00:00 JST +09:00 Time.use_zone('Tokyo') { Date.current } # => Wed, 01 Jan 2020 # UTC Time.use_zone('UTC') { Time.now } # => 2020-01-01 00:00:00 +0900 Time.use_zone('UTC') { Time.current } # => Tue, 31 Dec 2019 15:00:00 UTC +00:00 Time.use_zone('UTC') { Time.zone.now } # => Tue, 31 Dec 2019 15:00:00 UTC +00:00 Time.use_zone('UTC') { Date.current } # => Tue, 31 Dec 2019 # Unix Timestamp # are All same at any Timezone Time.use_zone('Tokyo') { Time.now.to_i } # => 1577804400 Time.use_zone('Tokyo') { Time.zone.now.to_i } # => 1577804400 Time.use_zone('UTC') { Time.now.to_i } # => 1577804400 Time.use_zone('UTC') { Time.zone.now.to_i } # => 1577804400 Time.now.to_i # => 1577804400 Time.current.to_i # => 1577804400 # Date.today # Not considered Timezone with Time.zone setting # its Ruby method refers TZ env Time.use_zone('Tokyo') { Date.today } # => Wed, 01 Jan 2020 Time.use_zone('UTC') { Date.today } # => Wed, 01 Jan 2020Original by Github issue
- 投稿日:2019-11-29T13:55:59+09:00
やさしい図解で学ぶ モデル マイグレーション その2 アソシエーション
その1ではテーブルの設計図となるマイグレーションファイルにて紹介しましたが今回は「モデルについて」と「アソシエーション (関連性)」をモデルファイルへ記述する方法をカンタンに学んでいきます。
⭐️モデルについて
まずモデルファイルとは
DBとのデータのやりとりをするために重要なファイルとイメージしてください。ファイルには
モデル(テーブル)間の関係性を記述したり、
DBにあるテーブルに直接データの保存、呼び出しを可能にする機能を基本的に備えています。上の図では
モデルファイルからDBへ「ActiveRecord」と書かれたパイプや橋のようなものがあり、userやtweetモデルからDB内の同じ名前のテーブルに伸びています。*ActiveRecordというのは厳密にいうとRailsに標準ライブラリとして採用されているO/Rマッパーのことです。専門用語を使わず簡単に表現すると
DBとアプリケーションとの 「 橋渡し or 通訳 」
のようなものだと思ってください。
もし、モデルがないと
Railsアプリケーションではプログラミング言語「Ruby」を。
DBでは「SQL」をそれぞれ使っているので、言語の違いからそのままではやりとりができません。とざっくりなイメージですが、ありがたいことにRailsではモデルファイル(ActiveRecord)が通訳のようにRuby→SQLに、SQL→Rubyにと言語を変換してデータベースとのやりとりを円滑に行ってくれているんです。
⭐️アソシエーション(テーブル間の関連性)
アソシエーションとは、つまり
テーブル同士の関連性のことで、テーブル間にみられる対1や対多などの関係性をモデルファイルに明示しなければいけません。中間テーブルを含む3つのテーブルがあります。
これらの関係性を各テーブルのモデルファイルに記述していきます。まず
今回のテーブル群のモデルファイルへの記述方法は以下の3パターンを使用します。
(そのほかにもhas_oneやhas_and_belongs_to_manyなどがあり、オプションも存在しますが今回はこちらの3つのみ)belongs_toやhas_oneの場合は単数、
has_manyなどは複数形で記述します。実際にuserのモデルファイルに記述するとこのようになります。
またそのほかのモデルファイルにもアソシエーションを記述し、
さらにER図のようにイメージして書くとこのような関係性になります。中間テーブルcourse_usersにはそれぞれ接続されているテーブルの主キー、つまりは、外部キーuser_idとcourse_idを持っていますのでこのような記述となります。
そして中間テーブルと接続しているテーブルのモデルにはそれぞれアソシエーションとして以下のように記述します。
今回はイメージを含めて基本的なもののみの紹介です。
そのほかにも便利なオプションや外部キー関連の記述もありますので徐々に学んでいきましょう。⭐️補足:belongs_to
はじめはbelongs_toというのがちょっとわかりづらいと思いますが、考え方としてはテーブル内に外部キーを持っていた場合、そのモデルファイルにはbelongs_toを書くと思ってください。
例えば、
usesテーブルとtweetsテーブルなるものがあり、関係性が図のようだとするとtweetsテーブルには外部キーであるuser_idを持っています。
モデルファイルには以下のように記入します。
- 投稿日:2019-11-29T13:55:59+09:00
やさしい図解で学ぶ モデル マイグレーション その2
その1ではテーブルの設計図となるマイグレーションファイルにて紹介しましたが今回は「モデルについて」と「アソシエーション (関連性)について」を基礎に学んでいきます。
⭐️モデルについて
まずモデルファイルとは
DBとのデータのやりとりをするために重要なファイルとイメージしてください。ファイルには
モデル(テーブル)間の関係性を記述したり、
DBにあるテーブルに直接データの保存、呼び出しを可能にする機能を備えています。上の図では
モデルからDBへ「ActiveRecord」と書かれたパイプや橋のようなものがあり、userやtweetモデルからDB内の同じ名前のテーブルに伸びています。*ActiveRecordというのはRailsに標準ライブラリとして採用されているO/Rマッパーのことです。
専門用語を使わず簡単に表現すると
DBとアプリケーションとの 「 橋渡し or 通訳 」
のようなものだと思ってください。
もし、モデルがないと
Railsではプログラミング言語「Ruby」を。
DBでは「SQL」をそれぞれ使っているので、言語の違いからそのままではやりとりができません。Railsではモデルファイル(ActiveRecord)が通訳のようにRuby→SQLに、SQL→Rubyにと言語を変換してデータベースとのやりとりを円滑に行ってくれているんです。
⭐️アソシエーション(テーブル間の関連性)
アソシエーションとは、つまり
テーブル同士の関連性のことで、テーブル間にみられる対1や対多などの関係性をモデルファイルに明示しなければいけません。中間テーブルを含む3つのテーブルがあります。
これらの関係性を各テーブルのモデルファイルに記述していきます。今回の記述方法は以下の3パターンを使用します。
(そのほかにもhas_oneやhas_and_belongs_to_manyなどがあり、オプションも存在しますが今回はこちらの3つのみ)belongs_toやhas_oneの場合は単数、
has_manyなどは複数形で記述します。実際にuserのモデルファイルに記述するとこのようになります。
またそのほかのモデルファイルにもアソシエーションを記述し、
さらにER図のようにイメージして書くとこのような関係性になります。中間テーブルcourse_usersにはそれぞれ接続されているテーブルの主キー、つまりは、外部キーuser_idとcourse_idを持っていますのでこのような記述となります。
has_many through
中間テーブルと接続しているテーブルのモデルにはそれぞれアソシエーションとして以下のように記述します。
中間テーブルを通ってその先のインスタンスを複数持っていますよという記述になります。
今回も基礎的な部分を図解を用いて記載致しましたので実際の開発ではモデルファイルにもう少し細かく書いていきます。
今回の記事でモデルに関して少しでもイメージが湧いていただけたのなら幸いです。
⭐️補足:belongs_to
初見ではbelongs_toというのがちょっとわかりづらいと思いますが、考え方としてはテーブル内に外部キーを持っていた場合、そのモデルファイルにはbelongs_toを書くと思ってください。
例えば、
usesテーブルとtweetsテーブルなるものがあり、関係性が図のようだとするとtweetsテーブルには外部キーであるuser_idを持っています。
モデルファイルには以下のように記入します。
- 投稿日:2019-11-29T12:59:16+09:00
selectタグで複数選択に変えたら、ストロングマラメーターによってパラメーターが排除されてしまった
webサイトを作成中にselect_tagを複数選択に変えたら、 入力した値が排除されてしまい少しハマってしまったので同じミスをしないためにまとめます。
エラーが発生した状況
上の写真のようにselect_tagを複数選択に変えたところ、これまで正常に登録ができていたにもかかわらず、登録できないエラーが発生した。
ログをみてみると、「Unpermitted parameter: :category_id」となっており、ストロングパラメータで弾かれ、paramsの値がnilになっていることが原因のようだった。
コンソールでも確認してみたところnilになっている
解決のために試したこと
ストロングパラメータを書いたコードをみて、paramsで指定した値の入力ミスがあるか確認。
しかし、タイポしているわけではなさそう。複数選択を解除すれば問題なく通ったため、複数選択が原因であることは間違えなさそう。
複数選択の時と一つしか選択できない時のparamsに入っている値を確認してみる。
・一つ選択の時
・複数選択の時(pryを使用してparamsの値を確認)
確認してみると、複数選択の場合、値が配列になっていることからストロングパラメーターにもそれを追記しなければいけないのではと思い実行したら、うまく作動できた!
params.require(:posting_thread_categories).permit(:category_id) # 上記から以下に変更 params.require(:posting_thread_categories).permit(category_id: [])
- 投稿日:2019-11-29T11:52:58+09:00
Railsチュートリアル学習メモ2
データ更新(rails cから)
[1] pry(main)> post = Post.find_by(id: 1) [2] pry(main)> post.content = "ああああああ" [3] pry(main)> post.saveデータの削除(rails cから)
[1] pry(main)> post = Post.find_by(id: 1) [2] pry(main)> post.destroy [3] pry(main)> post.savelink_toでmethodを指定する
link_to("文言", "パス", {method: "post"})modelにvalidationを設定
modes/postvalidates :content, {presence: true, length: {maximum: 100}} // presenceはカラムが存在するか確認するrenderメソッド
renderメソッドを使うと、redirect_toメソッドを使った場合と違い、そのアクション内で定義した@変数をビューでそのまま使える。
エラーメッセージはRails側で自動で保存される
@post.errors.full_messages
view側でのエラーメッセージ表示
test.html.erb<% @post.errors.full_messages.each do |message| %> <div class="form-error"> <%= message %> </div> <% end %>ん、railsってメッセージデフォルトで予測してくれて入っちゃってる感じ?
フラッシュメッセージ
flash[:notice] = "投稿を編集しました"
- 投稿日:2019-11-29T10:45:25+09:00
【Ruby】配列のnilチェック+部分一致検索 (備忘録)
Rubyの超初心者(自分)向けの備忘録
やりたいこと
- Rubyハッシュ(連想配列)のnilチェック
- 配列の部分一致検索
1. Rubyのハッシュ(連想配列)のnilチェック
- 最初はif文でnilチェックをやろうと思ったけど、以下のように
dig()
を使う方が簡単だった
(Ruby2.3で導入されたらしい)params.dig(:q, :name)2. 配列の部分一致検索
search_text = '検索したい文字' array.select { |e| e =~ %r{^.*#{search_text}.*} }
#{}
で囲うとで変数を入れられる%r{}
で正規表現オブジェクトを作成した場合は、パターン内に「/」が含まれていてもエスケープは不要らしい。便利!
「もっとこうした方が楽だよ!」と言った知見があれば、コメントお待ちしてます
参考サイト
- 投稿日:2019-11-29T10:45:25+09:00
【Ruby】配列のnilチェック+部分一致検索
やりたいこと
- Rubyハッシュ(連想配列)のnilチェック
- 配列の部分一致検索
1. Rubyのハッシュ(連想配列)のnilチェック
- 最初はif文でnilチェックをやろうと思ったけど、Ruby2.3で導入された
dig()
を使う方が簡単だったparams.dig(:q, :name)2. 配列の部分一致検索
search_text = '検索したい文字' array.select { |e| e =~ %r{^.*#{search_text}.*} }
#{}
で囲うとで変数を入れられる%r{}
で正規表現オブジェクトを作成した場合、パターン内に「/」が含まれていてもエスケープは不要らしい。便利!
「もっとこうした方が楽だよ!」と言った知見があれば、コメントお待ちしてます
参考サイト
- 投稿日:2019-11-29T04:20:20+09:00
Ruby on Rails と React Native で作る web & モバイルアプリ [webアプリ編]
イントロダクション
目的
なんか web アプリケーションを作って運用していたら、どうやらモバイルアプリ需要が出てきたことが分かってバタバタ後追いでモバイルアプリ対応することってあるじゃないですか。今回は演習式でそのシミュレーションをします。まず、Ruby on Rails で web アプリケーションを作った後に、API を生やして React Native で web アプリケーションと同じような仕様のモバイルアプリケーションを作ります。
今回作るアプリケーションの完成品は以下のリポジトリに置いていますので、React Native 側の演習のみを行いたい方は Ruby on Rails アプリケーションは以下より clone してデプロイし、モバイルアプリ編 へ進んでください。
フレームワーク リポジトリ Ruby on Rails (web) https://github.com/ogihara-ryo/zone-web React Native (mobile) https://github.com/ogihara-ryo/zone-mobile いきなり言い訳なんですが、Advent Calendar の1枠としてやるには明らかにやりすぎな記事になってしまいました。お詫び申し上げます。Ruby on Rails と React Native のカレンダーで1枠ずつ頂くのではなく1人 Advent Calendar をやれば良かったなぁと12月に入ってから今更少し後悔しています。ただ、私は深く反省しているので、てへぺろ1つで許されることは目に見えています。張り切っていきましょう。
今回作るシステム
スーパーウルトラシンプルなサンプルアプリケーションを作ることにしましょう。今日やるべきタスクを雑に登録してひたすら消化するためだけのタスク管理システムを作りましょう。大人の都合で認証機能を設けた方が記事的に映えるのでログイン必須のシステムが良いですね。
/users/:id/tasks
を叩けば誰でもそのユーザーの今日のタスク状況が見られるようにしましょう。ユースケースとしては、まず各人は朝に今日中に片付けたいタスクを雑に列挙して(まあ朝起きられないおまえらは昼かもしれませんが)、各タスクが終わるたびにチェックを付けていき、プロジェクトマネージャーだか上司だかは URL を共有してもらっているので各人のタスク状況を web から確認できる、みたいな感じにしますか。
それ Google スプレッドシートでできるよ。web アプリケーションでできること
- サインアップ
- ログイン
- タスクの CRUD
- 他人のタスクも閲覧のみ可能
モバイルアプリケーションでできること
- ログイン (サインアップは web でやれ)
- 自身のタスクの CRUD (他人のタスクは web で見ろ)
想定読者
記事を書いた動機は、最近 Ruby on Rails で作った web アプリを React Native アプリ対応しようと思ったら参考日本語記事が少なくて苦労したためですが、この記事としては Ruby on Rails の心得が少しだけある初学者レベルを想定して書いています。Ruby on Rails 自体が初めての方も、写経で雑に理解しながら進めていくことはできるようには少しだけ配慮していますが、あまりにも初歩的で詳細すぎる説明は避けるようにも配慮しています。本記事(webアプリ編)だけでも、ちょっとした Ruby on Rails アプリケーションを作ることができるので、初学者の方は挑戦してみてほしいなぁ...という下心があります。
もし、この記事を進めていく上で躓いた場合や理解できないことがあった場合は @OgiharaRyo までご連絡頂くか、現在300人ほど参加している技術質問 slack コミュ二ティを運営していますので、こちらで質問して頂ければと思います。(slack 招待リンク)
環境
2019年12月初旬執筆時点の最新版を使用します。
% ruby -v ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin19] % rails -v Rails 6.0.1 % psql -V psql (PostgreSQL) 12.1謝辞
以下のツイートで記事のテストプレイヤーを募集したところ、にしこさんとnonoさんにお声がけ頂き、記事中の不備や手順の漏れを確認及び指摘して頂きました。ありがとうございました。
[ゆるぼ]
— Ryo (@OgiharaRyo) December 6, 2019
Ruby on Rails と React Native でそれぞれサンプルアプリケーションを写経で作る記事を書いているのですが、その Ruby on Rails 編を明日明後日でテストプレイしてくださる方はいらっしゃいませんか? (続)Ruby on Rails アプリケーションの開発
rails new
オプション欲張りセットの
rails new
を実行します。本記事では web アプリケーションを Heroku で公開するため、データベースに PostgreSQL を指定しています。その他のたっぷりあるオプションは今回の記事に関係のないものを全て排除しています。ちなみにアプリケーション名にzone
と入っているのは、集中して没頭している意識状態になることを「ゾーンに入る」と表現することがあるので、そこから拝借しました。日次でタスクを可視化して消化するだけで、結構集中状態に入りやすくなる気がしませんか?まあそんなことは良いので早くrails new
してください。Ruby on Rails アプリケーション開発の始まりです!% rails new zone-web --database=postgresql --skip-action-mailer --skip-action-text --skip-active-storage --skip-action-cable --skip-javascript --skip-turbolinks --skip-testデータベースを作ってから一応起動を確認しておきましょう。
% cd zone-web % rails db:create % rails s
環境にもよりますが、大人の都合で以下よりあなたの開発サーバーが localhost:3000 であることを前提にリンクを記述していきますので異なる方は適時読み替えてください。ということで、localhost:3000 にアクセスしてご機嫌な表示がされたら ok です。
モデリング
とりあえず
User
モデルとTask
モデルを作成します。User
列 型 説明 name string ユーザーの名前を保存します。 account_id string 今回はメール認証等は行わないので、とりあえず任意のアカウントID(文字列)とパスワードのペアでログインできるようにします。ここをメールアドレスにしてメール認証のプロセスを挟んでも良いでしょう。 password_digest string ハッシュ化されたパスワードを保存します。このパスワードは復号できません。つまり、ユーザーがどんなパスワードを入力したのかはシステムが後から知ることはできません。 Task
列 型 説明 user_id string どのユーザーのタスクであるのかを保存します。 name string タスクの名前を入力します。見積もり時間も詳細も不要です。ただ1行完結なタスク内容が書ければ良いのです。日次でタスクを表示するので作成日も必要ですが、 rails g(enerate)
で生成されたマイグレーションファイルはデフォルトでcreated_at
という列にレコードの作成日時が入るようになるのでこれを利用します。ちなみにこれはあまり良い方法ではないので実務ではcreated_at
を業務上の都合と結び付けないようにしましょう。scaffold 等は行わずに Controller と View は温もりのあるお手製で作ります。モデルとマイグレーションファイルは冷たく Generator に頼ります。
% rails g model User name:string account_id:string password_digest:string % rails g model Task user:belongs_to name:string finished:boolean % rails db:migratemigrate した結果はこのようになっているはずです。
db/schema.rbcreate_table "tasks", force: :cascade do |t| t.bigint "user_id", null: false t.string "name" t.boolean "finished" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["user_id"], name: "index_tasks_on_user_id" end create_table "users", force: :cascade do |t| t.string "name" t.string "account_id" t.string "password_digest" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end add_foreign_key "tasks", "users"
Task
モデルには generator がbelongs_to :user
を追加してくれていますが、User
側がTask
を複数持つことはモデルが知らないので一文追加しておきましょう。app/models/user.rbclass User < ApplicationRecord + has_many :tasks end
サインアップ
とりあえず
User
を作る機能を実装しなければ始まらないので、サインアップ機能を提供しましょう。サインアップフォーム表示
ルーティング
/signup
という URL にアクセスされたらUsersController
のnew
アクションが呼ばれるようにします。また、サインアップの submit で/signup
という URL に POST された時にUsersController
のcreate
アクションが呼ばれるようにします。config/routes.rbRails.application.routes.draw do + get 'signup', to: 'users#new' + post 'signup', to: 'users#create' end
Controller
新しく
app/controllers/users_controller.rb
を作成し、以下のコードを仮置きします。今回はnew
アクションでフォームが表示されることだけを目的とするので、create
アクションはまだ書きません。app/controllers/users_controller.rbclass UsersController < ApplicationController def new @user = User.new end endパスワードハッシュ化の準備
Gemfile でデフォルトでコメントアウトされている
bcrypt
をコメントインします。Gemfile- # gem 'bcrypt', '~> 3.1.7' + gem 'bcrypt', '~> 3.1.7'
bundle install
します。ちなみにbundle
だけ叩くとbundle install
されます。% bundle
User
モデルでhas_secure_password
をコールします。今インストールしたbcrypt
の力で、これをコールしておくことでモデルの属性にpassword
とpassword_confirmation(確認入力用)
が追加され、合致してsave
した時に自動でpassword_digest
にハッシュ化されたパスワードがセットされます。app/models/user.rbclass User < ApplicationRecord + has_secure_password end
View
新しく
app/views/users/new.html.erb
を作成し、ユーザー情報の入力フォームをマークアップしていきましょう。app/views/users/new.html.erb<%= form_with model: @user, url: signup_path, local: true do |f| %> <p> <%= f.label :name, 'お名前' %> <%= f.text_field :name %> </p> <p> <%= f.label :account_id, 'アカウントID' %> <%= f.text_field :account_id %> </p> <p> <%= f.label :password, 'パスワード' %> <%= f.password_field :password %> </p> <p> <%= f.label :password_confirmation, 'パスワード(確認)' %> <%= f.password_field :password_confirmation %> </p> <p><%= f.submit '送信' %></p> <% end %>これで localhost:3000/signup にアクセスするとフォームが表示されます。
サインアップ処理実装
今の状態で submit すると(送信ボタンを押すと) 「
create
アクションが見つからないんだが?!」というエラーで怒られます。初学者のあなたは一度 submit してみてエラー画面を目に焼き付けておきましょう。エラーメッセージを目に焼き付けた数が経験値になります。さて、早速 Controller にcreate
アクションを実装していきましょう。app/controllers/users_controller.rbclass UsersController < ApplicationController def new @user = User.new end + + def create + @user = User.new(user_params) + render :new unless @user.save + end + + private + + def user_params + params.require(:user).permit(:name, :account_id, :password, :password_confirmation) + end end
user_params
メソッドの中身は Strong Parameters といい、ブラウザーから POST されるはずのパラメーターのキーを個別に許可しています。これは、id
等といったサインアップフォームからは設定してほしくない属性(フォームにフィールドを置いていない属性)を特殊な方法で POST されてもデータベースに保存しないための防御機構です。ここで許可した属性以外は、users
テーブルにセットしないようにしているわけですね。続いて、作成後に「ご登録ありがとうございます。」とのテキストを表示できるようにします。新しく
app/views/users/create.html.erb
を作ってマークアップします。app/views/users/create.html.erb<p>ご登録ありがとうございます。</p>さて、実装できたら以下のような情報を入力してテストアカウントを作成してみましょう。
項目 入力する情報 お名前 テストアカウント アカウントID test パスワード password パスワード(確認) password 送信すると、先ほどの
create.html.erb
にマークアップしたテキストが表示されるはずです。上手く実装できていれば、
users
テーブルに1レコード作成されているはずです。rails console
から見てみましょう。% rails c Running via Spring preloader in process 48857 Loading development environment (Rails 6.0.1) irb(main):001:0>一番最近作られた
User
を参照します。このように先ほど作ったユーザーが表示されれば ok です。irb(main):001:0> User.last User Load (5.6ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]] => #<User id: 1, name: "テストアカウント", account_id: "test", password_digest: [FILTERED], created_at: "2019-12-01 11:58:46", updated_at: "2019-12-01 11:58:46">「ご登録ありがとうございます。」との表示を確認したのに
User
の情報が表示されずnil
が返ってくる場合はコードを見直してみてください。Strong Parameters あたりが怪しいと思います。異常処理
今のサインアップ機構には以下の問題点があります。
- 名前やアカウントIDやパスワードが空でも登録できてしまう
- パスワードとパスワード(確認)が違っている場合にユーザーに何も通知されない
- 同一のアカウントIDを登録できてしまう
同一のアカウントIDを登録できてしまうと、ログインする時に入力されたアカウントIDでどのユーザーで認証すれか良いか分からなくなるので、アカウントIDは既に登録されている値を使えなくする必要があります。これから、それらの問題を解決していきます。
サーバーサイドバリデーション
まず、Model 側でバリデーションを設定してレコードを作成する時に値の有効性を検証することにしましょう。パスワードを必須であるかや、パスワードとパスワード(確認)が一致しているかは、
has_secure_password
が検証してくれるので、残りの名前とアカウントIDに関するバリデーションを定義していきます。app/models/user.rbclass User < ApplicationRecord has_secure_password + + validates :name, presence: true + validates :account_id, presence: true, uniqueness: true end
これで、名前が必須となり、アカウントIDが必須かつ重複不可となりました。
rails console
で試してみましょう。名前を空にして、アカウントIDを先ほど画面から作ったtest
にして、パスワードとパスワード(確認)に別の文字列を入れてみます。irb(main):001:0> User.create!(name: '', account_id: 'test', password: 'password', password_confirmation: 'invalid') (0.1ms) BEGIN User Exists? (0.2ms) SELECT 1 AS one FROM "users" WHERE "users"."account_id" = $1 LIMIT $2 [["account_id", "test"], ["LIMIT", 1]] (0.3ms) ROLLBACK Traceback (most recent call last): 1: from (irb):2 ActiveRecord::RecordInvalid (Validation failed: Password confirmation doesn't match Password, Name can't be blank, Account has already been taken)今回重要なのは最終行です。
ActiveRecord::RecordInvalid (Validation failed: Password confirmation doesn't match Password, Name can't be blank, Account has already been taken)
と書かれていますね。つまり、パスワード(確認)がパスワードと合っていないよ、名前は空にできないよ、アカウントが重複しているよ、と言われています。問題なさそうですね。ちなみに勿論、データベースには保存されていません。不安であれば再び
User.last
で確かめてみましょう。ちなみに、エラーになる条件は他にもパターンがあります。アカウントIDやパスワードが空の場合です。勿論このままそういったパターンの検証を続けても良いですが、実際の開発ではこういった挙動確認はテストコードを書いて行うことが多いです。ただし、テストの話をすると私がうるさくなるので、本記事ではノーテストでいきます。「ノーテストで開発するのはノーヘルでバイクに乗るようなものです。つまり気持ちいいってことです。」みたいなことを誰か偉い人が言っていたような気がします。わかる。エラーメッセージの通知
さて、サーバーからバリデーションエラーで弾かれたことをユーザーに通知してやる必要があります。エラーメッセージは、
@user.errors.full_messages
で配列で取り出すことができます。View のform_with
ブロックの一番上にエラーメッセージ表示用のマークアップを行いましょう。app/views/users/new.html.erb<%= form_with model: @user, url: signup_path, local: true do |f| %> + <% if @user.errors.any? %> + <ul> + <% @user.errors.full_messages.each do |message| %> + <li><%= message %></li> + <% end %> + </ul> + <% end %> ... <% end %>
これで、
@user
インスタンスがエラー情報を持っている場合のみバリデーションエラーのリストを表示することができます。早速動かしてみましょう。先ほどrails c
で試してみたことをブラウザーから試してみましょう。以下のスクリーンショットのように、名前を空にして、アカウントIDを先ほど画面から作ったtest
にして、パスワードとパスワード(確認)に別の文字列を入れてみます。この状態で送信ボタンを押して submit すると以下のようにエラーメッセージが表示されます。先ほど
rails c
で確認したエラーメッセージと同様ですね。ちなみにこのメッセージを日本語にすることもできるのですが、少しコードが散らかって今回作りたいシステムの目的から脱線するので今回は英語のままにしておきます。変なところで投げっぱなし、それがこの記事の雑さです。また、上記2つのスクリーンショットを見比べてみると、ラベルと入力フィールドの間の改行状態が変わっていることが分かります。これは Ruby on Rails (
f.text_field
)が気を利かせて生成される HTML にエラーであることが分かるような手を加えてくれているのですが、ここで詳しくは解説しません。気になる方は、ブラウザーのデベロッパーツールから生成された HTML のソースコードを読んでみてください。変なところで投げっぱなし、それがこの記事の雑さです。クライアントバリデーション
さて、ついでにクライアント(ブラウザー)側でも簡易的なバリデーションを設定しておきましょう。今回はとても簡単な「フィールドを必須にする」という実装だけを行います。なんと、HTML5 の
input
要素はrequired
属性を与えるだけでそのフィールドが必須であることをブラウザーに指示してくれます。Ruby のフォームビルダーのメソッド(f.text_field
等)でrequired
属性を指定するにはrequired: true
というオプションを与えてやるだけで ok です。app/views/users/new.html.erb<p> <%= f.label :name, 'お名前' %> - <%= f.text_field :name %> + <%= f.text_field :name, required: true %> </p> <p> <%= f.label :account_id, 'アカウントID' %> - <%= f.text_field :account_id %> + <%= f.text_field :account_id, required: true %> </p> <p> <%= f.label :password, 'パスワード' %> - <%= f.password_field :password %> + <%= f.password_field :password, required: true %> </p> <p> <%= f.label :password_confirmation, 'パスワード(確認)' %> - <%= f.password_field :password_confirmation, required: true %> + <%= f.password_field :password_confirmation, required: true %> </p>この状態でフィールドを空にして submit しようとすると、ブラウザーから以下のスクリーンショットのように警告が表示されてキャンセルされると思います。このスクリーンショットは macOS の Chrome です。
「どの道サーバーで弾かれてエラーメッセージも表示されるんだからクライアント側のバリデーションは冗長では?」と思う方もいらっしゃるかもしれませんが、サーバーに問い合わせなくても入力段階で弾けるものは弾いてしまっておいた方が、ユーザーの手間を煩わせずに済むことが多いため、UX の観点からクライアント側でもバリデーションを行うことが多いです。変なところでこだわる、それがこの記事の雑さです。
スタイリング
無事にサインアップの機能を作ることができましたが、フォームがあまりにも簡素で気分が上がらないので少しだけ見た目に気を使うことにしましょう。ただ、この記事では「CSS 絶対に書きたくないでござる!」という気持ちと、「CSS フレームワークに依存した class 付けは絶対にしたくないでござる!」という気持ちと、「CSS フレームワークの導入は CDN だけで終わらせたいでござる!」という気持ちが強いので、CSS を書かなくても class を付けなくても良くて CDN で読み込むだけでそれっぽい見た目になる方法を探します。そこで mini.css を利用します。記事執筆時点で、ここまでに書いたサインアップフォームを一切触らなくても CDN へのリンクを書くだけでスタイルが当たるものを探し回ったところ mini.css が一番それっぽくなったような気がしました。ということで、早速 CDN から mini.css を読み込みます。たった1行書くだけで CSS を丸ごと配信してもらえるなんて至れり尽くせりですね。また、mini.css の ドキュメント(Getting started) を見ると、「viewport はちゃんと指定しておけよ」と書いてあるので従います。各ページ共通のレイアウト(
head
要素等) はapp/views/layouts/application.html.erb
に書かれているので、ここに viewport の指定と CDN へのリンクを設定します。app/views/layouts/application.html.erb<head> <title>ZoneWeb</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> + <meta name="viewport" content="width=device-width, initial-scale=1"> + + <link rel="stylesheet" href="https://cdn.rawgit.com/Chalarangelo/mini.css/v3.0.1/dist/mini-default.min.css"> <%= stylesheet_link_tag 'application', media: 'all' %> </head>
この状態でサインアップフォームにアクセスすると、それっぽいデザインになっています。
required
属性が付いたinput
要素のフィールドが未入力だと赤枠で囲まれるのが素敵ですね。まあいろいろと、もう少し何とかしたい感はありますが、一切の class 付けもカスタマイズも行わずにただ CSS を読み込むだけで、ここまでスタイリングしてもらえれば充分です。今回はスタイリングのことは考えずに進めたいので、一旦はこのまま進もうと思います。
認証
次は、サインアップしたユーザーのアカウントIDとパスワードでログインできるようにします。この章では以下のような仕様で実装していきます。
- ログインしたらトップページへ遷移
- ログインしていない状態でトップページへアクセスされたらログインページへリダイレクト
トップページ
まず、ログインした時に表示するためのトップページを作ります。トップページはタスクの一覧をいきなり表示するので、
TasksController
のindex
アクションにルーティングします。config/routes.rbRails.application.routes.draw do + root 'tasks#index' get 'signup', to: 'users#new' post 'signup', to: 'users#create' end
app/controllers/tasks_controller.rb
を作成し、TasksController
のindex
アクションを仮実装します。今は、ログイン後にページが表示されれば何でも良いので空っぽのアクションで ok です。app/controllers/tasks_controller.rbclass TasksController < ApplicationController def index; end end続いて
app/views/tasks/index.html.erb
を作成し、View を仮実装します。テンションの上がりそうなテキストを置いておきましょう。app/views/tasks/index.html.erb<p>Welcome!!</p>これで、トップページ(localhost:3000)にアクセスするとテンションが上がります。
ログインフォーム実装
サインアップページやトップページを作った時と同様に、サクサクとルーティングと Controller と View を実装していきましょう。少しずつ慣れてきましたか?
config/routes.rbRails.application.routes.draw do root 'tasks#index' get 'signup', to: 'users#new' post 'signup', to: 'users#create' + + get 'login', to: 'sessions#new' + post 'login', to: 'sessions#create' end
app/controllers/sessions_controller.rbclass SessionsController < ApplicationController def new; end def create; end endapp/views/sessions/new.html.erb<%= form_with scope: :session, url: login_path, local: true do |f| %> <p> <%= f.label :account_id, 'アカウントID' %> <%= f.text_field :account_id, required: true %> </p> <p> <%= f.label :password, 'パスワード' %> <%= f.password_field :password, required: true %> </p> <p><%= f.submit 'ログイン' %></p> <% end %> <p><%= link_to 'サインアップはこちら', signup_path %></p>サクサクと実装できましたか?これでログインページ(localhost:3000/login)にアクセスするとフォームが表示されます。
あとは、サインアップ完了画面にログインリンクを追加しておきましょう。
app/views/users/create.html.erb<p>ご登録ありがとうございます。</p> + <p><%= link_to 'ログインページ', login_path %>からログインしてご利用を開始してください。</p>
認証
さて、それではログイン処理を実装していきましょう。いきなり難易度が跳ね上がりますが、あれだったらコピペして後は見なかったことにして切り抜けてください。まずは認証のコア部分を書いていきます。今回は超シンプルな認証にするので
devise
等の Gem は使わずに remember me も実装せず、大したセキュリティも意識せずにサクッとお手製で作っていきます。認証系を手書きするのは地獄だと思われるかもしれませんが、これだけです。app/helpers/sessions_helper.rbmodule SessionsHelper def login(user) session[:user_id] = user.id end def current_user @current_user ||= User.find_by(id: session[:user_id]) end def logged_in? current_user.present? end end
login
メソッドはただの代入のように見えますが、これはUser
の ID をクライアントの Cookie に暗号化して詰めています。この Cookie の中身を復号した値は代入時と同じようにsession
メソッドから呼び出せます。つまり、session[:user_id]
にUser
の ID を詰めておけば、次回以降のリクエストでユーザーに Cookie が残っていればsession[:user_id]
を参照することで読み出すことができます。
current_user
メソッドは、上述のsession[:user_id]
を使ってUser
のインスタンスを取得して返します。User.find_by(id: session[:user_id])
で Cookie に入っているユーザーの ID でデータベースを照会している訳ですね。||=
という演算子の使い方は見慣れない方もいるでしょう。これは、演算子の短絡評価の特性を使って左辺が偽の場合のみ代入が実行するための文法です。論理和演算(||
)における短絡評価とは、「左辺が偽なら右辺が真であろうが偽であろうがどうせ偽になるので右辺をそもそも評価する必要ないよね」という評価法です。左辺が真であって初めて右辺を気にするということですね。ここで||=
を使う目的は、同じリクエスト内で複数回current_user
メソッドがコールされた時のデータベースアクセスを初回以外スキップすることです。つまり初回でデータベースからUser
のインスタンスを引っ張ってきたら、次回以降はインスタンス変数を参照することでデータベースにアクセスするコストを防いでいるということです。
logged_in?
メソッドは、見ての通りcurrent_user
でUser
のインスタンスを引っ張れたかどうかを確認するメソッドです。find_by
は見つからなかった時にnil
を返すので、session[:user_id]
が空の場合や存在しないユーザーの ID 等が入っている場合は、logged_in?
メソッドがfalse
を返すということです。さて、ログイン機構を作ったところで、ログインフォームから POST されてきた時の処理を実装しましょう。
SessionsController
のcreate
アクションを実装していきます。app/controllers/sessions_controller.rbdef create user = User.find_by(account_id: params[:session][:account_id]) if user&.authenticate(params[:session][:password]) login user redirect_to root_path else flash.now[:alert] = 'アカウントID、またはパスワードが間違っています。' render :new end endユーザーから POST された値(ログインフォームに入力されたアカウントIDとパスワード)は、
params[:session]
に入っているので、まずUser.find_by(account_id: params[:session][:account_id])
でそのアカウントIDのユーザーを引っ張り、次行のif user&.authenticate(params[:session][:password])
でパスワードが正しいかを確認しています。authenticate
メソッドはUser
モデルでコールしたhas_secure_password
メソッドによって提供されていて、引数に与えたパスワードが正しいかどうかを返してくれます。&.
という演算子も見慣れない方がいるかもしれません。これは Safe Navigation Operator という演算子で、レシーバーが nil であればNoMethodError
を投げずにnil
を返してくれます。昔の Ruby を知っている方はuser.try!(:authenticate, params[:session][:password])
と同じだと考えると良いかもしれません。つまり、ユーザーが存在しないアカウントIDを POST してきてfind_by
でUser
のインスタンスが引っ張れずにnil
を返してきた時に、else
に入るために利用しています。if user.present? && user.authenticate(params[:session][:password])
という上述の演算子の短絡評価を使った書き方の方が明示的で好みな方もいるかもしれませんね。余談ですが、&
はひとりぼっちで体育座りしている様子に見えるため&.
は通称「ぼっちオペレーター」と呼ばれていたりします。そんなこんなで認証できた場合は
if
が真となるので、先ほどSessionsHelper
に定義したlogin
メソッドをコールして Cookie にUser
の ID を詰めてトップページにリダイレクトしています。else
に入った場合は、POST されたアカウントIDが存在していなかったか、アカウントIDは存在していたけどパスワードが間違っていたかのいずれかなので、エラーメッセージをflash
に詰めてnew.html.erb
を再描画します。詰めたエラーメッセージを表示する処理は後ほど書きましょう。その前に、今のままだとSessionsHelper
のlogin
メソッドがSessionsController
からは見えていない問題を解決します。SessionsHelper
のcurrent_user
はあちこちで使うことになるので全ての Controller が継承しているApplicationController
で Mix-in します。app/controllers/application_controller.rbclass ApplicationController < ActionController::Base + include SessionsHelper end
これで、
ApplicationController
を継承している全ての Controller でSessionsHelper
のcurrent_user
やlogged_in?
を利用できるようになりました。Helper を Controller に Mix-in するのって気持ち悪いですが、現状スッキリとした方法が思い浮かばないのでこのままいきます。そして、ログインページに、ログインに失敗した時のエラーメッセージを表示します。先ほど Controller の
flash.now[:alert]
で詰めたエラーメッセージが表示されます。if flash[:alert].present?
しても良いですが、まあflash[:alert]
の中身がnil
ならどうせ何も出ないので雑に一行置いておきましょう。app/views/sessions/new.html.erb<%= form_with scope: :session, url: login_path, local: true do |f| %> + <%= flash[:alert] %> <p> <%= f.label :account_id, 'アカウントID' %> <%= f.text_field :account_id, required: true %> </p> ... <% end %>
最後に、ログインに成功してトップページにリダイレクトされた後、本当にログインに成功しているのかを確かめるために「Welcome!!」 の文字列を「Welcome [ユーザーの名前(
User#name
)]!!」に変更しましょう。app/views/tasks/index.html.erb- <p>Welcome!!</p> + <p>Welcome <%= current_user.name %>!!</p>これでログイン処理周りの実装は完了です。早速動かしてみましょう!まずは、適当なアカウントIDやパスワードを入力してログインに失敗した場合にエラーメッセージが表示されるかを確認しましょう。
次に、正しいアカウントIDとパスワードでログインしてみましょう。先ほど作ったテストアカウント(アカウントID:
test
, パスワード:password
) でログインしてみましょう。いろいろあってテストアカウントがない場合はサインアップページ(localhost:3000/signup)から作りましょう。ログインに成功してユーザー名が表示されれば ok です。
ログアウト
ログインできるようになったのでログアウトも実装しましょう。サクサクといきましょう。まずはルーティングを追加します。
/login
に対する DELETE リクエストをSessionsController
のdestroy
アクションにルーティングします。config/routes.rbget 'login', to: 'sessions#new' post 'login', to: 'sessions#create' + delete 'logout', to: 'sessions#destroy'
SessionsController
のdestroy
を実装していきます。この後追加するSessionsHelper
のlogout
メソッドをコールしてログインページにリダイレクトします。app/controllers/sessions_controller.rbdef create ... end + + def destroy + logout + redirect_to login_path + end end
そして、実際のログアウト処理です。Cookie から
User
の ID 情報を削除して、インスタンス変数もnil
で初期化するだけです。app/helpers/sessions_helper.rbdef logged_in? current_user.present? end + + def logout + session.delete(:user_id) + @current_user = nil + end end
最後にログアウトのリンクを置きます。まあとりあえずトップページにでも置いておきましょう。
app/views/tasks/index.html.erb<p>Welcome <%= current_user.name %>!!</p> + + <%= link_to 'ログアウト', logout_path, method: :delete %>
最後と言ったな、あれは嘘だ。まだ大きな問題があります。上記のコードでは
link_to
にmethod
オプションを与えることで、生成されるa
要素にdata-method="delete"
属性を追加して GET ではなく DELETE でリクエストを送ることを期待しています。しかし、現状はそんなことはできずに GET リクエストで/logout
にリクエストされてルーティングエラーになります。「えっ?よくやってるけど」と思う方もいらっしゃるかもしれませんが、これは jquery-ujs が提供している機能です。今回は--skip-javascript
でrails new
したため jquery-ujs なんて高級なものは入っていません。よし、CDN から読み込みましょう。(雑)app/views/layouts/application.html.erb<link rel="stylesheet" href="https://cdn.rawgit.com/Chalarangelo/mini.css/v3.0.1/dist/mini-default.min.css"> <%= stylesheet_link_tag 'application', media: 'all' %> + + <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-ujs/1.2.2/rails.js"></script> </head>
ちなみに
data-method
属性を持ったa
要素で任意のリクエストを投げるためのコードはこれです。なんと、a
要素が押された瞬間にdata-method
に従ったメソッドでリクエストを投げられるようなform
要素を錬金して強制submit
しています。最初からform
を組み立てておいても良いならlink_to
ではなくbutton_to
を使う方法もあるのですがデザインがいい感じにならなかったので jquery-ujs を読み込んじゃうことにしました、てへぺろ。まあ今回はしばらく使い古した web アプリケーションのモバイルアプリ化が目的なので、ちょっと jQuery ぐらい置いてたり、設計が歪だったりした方がリアルじゃないですか。ちなみにrails-ujs
の話がタイムリーなことに別の Advent Calendar の記事で行われていたので、詳しく知りたい方はこちらの記事をどうぞ。さて、ということで、ログアウト処理が実装できました。ログインしてトップページからログアウトリンクを踏むとログインページに戻されるためです。ブラウザーのデベロッパーツールでログアウト前とログアウト後で Cookie がどのような動きをするかを確認してみても良いかもしれませんね。
ログイン必須化
ログインとログアウトの処理は実装しましたが、もう1つだけ問題があります。それは、ログアウトした状態であってもトップページにアクセスできてしまうことです。ログインしていない状態で localhost:3000 にアクセスしてみましょう。このように「
current_user
がnil
だからname
なんかにはアクセスできませんぞ!!」というエラーが表示されます。ログアウト状態では、
session[:user_id]
が空なのでcurrent_user
はnil
を返すのでしたね、覚えていますか?回避方法としては先ほどの Safe Navigation Operator (通称ぼっちオペレーター)を使ってcurrent_user&.name
にする手もありますが、そもそも今回の要件としては、トップページはログインユーザー以外アクセスできてはならないということなので、未ログインのユーザーがトップページにアクセスしてきたらログインページにリダイレクトするようにしましょう。app/controllers/tasks_controller.rbclass TasksController < ApplicationController + before_action :login_required def index; end + + private + + def login_required + redirect_to login_path unless logged_in? + end end
before_action
にシンボルで渡したメソッドは、この Controller のアクションがコールされる前に実行されます。ここでは、unless logged_in?
の時、つまり未ログインの時にログインページにリダイレクトしています。では、ログアウトした状態でトップページにアクセスしてみましょう。ログインページにリダイレクトされており、ログインしたらトップページが表示されれば ok です。あとは肝心のタスク管理ができるようになれば web アプリケーションとしては完成です。タスクの CRUD
CRUD とは、データの Create, Read, Update, Delete の頭文字を取ったものです。つまり、タスクを作ったり読み出したり更新したり消したりといったコードをこれから書いていきます。今回は Delete は実装しないので、CRU かもしれませんね。
一覧ページと作成フォーム
タスクの一覧ページはトップページとユーザーIDを指定したページの2種類用意します。つまり、localhost:3000 と localhost:3000/users/:id/tasks の2種類です。
:id
の部分にはそのUser
のid
属性が入ります。また、タスクの一覧ページに正しくタスクが表示されているかを確認するために、一気にタスク作成フォームまで作ってしまうのでcreate
アクションも実装します。今回はindex
ページにタスク追加フォームを配置するのでnew
の専用フォームはありません。index
とcreate
を同時に実装していくのは難しそうに感じるかもしれませんが、大丈夫、あなたは写経するだけです。config/routes.rbget 'login', to: 'sessions#new' post 'login', to: 'sessions#create' delete 'logout', to: 'sessions#destroy' + + resources :users, only: [] do + resources :tasks, only: :index + end + resources :tasks, only: :create
それでは、タスクの一覧を表示するための Controller 処理を書いていきます。今回少し癖があるのは、
/users/:id/tasks
の URL でアクセスされた場合は:id
で指定されたUser
のタスクを取得しますが、そうでない場合はログイン中のUser(current_user)
のタスクを取得する必要があるということです。app/controllers/tasks_controller.rbclass TasksController < ApplicationController before_action :login_required + before_action :set_user - def index; end + def index + @tasks = @user.tasks.where(created_at: Time.zone.today.all_day) + end + + def create + @task = Task.create(task_params.merge(user: current_user)) + redirect_to Rails.application.routes.recognize_path(request.referer) + end private def login_required redirect_to login_path unless logged_in? end + + def set_user + @user = (params[:user_id].present? ? User.find(params[:user_id]) : current_user) + end + + def task_params + params.require(:task).permit(:name, :finished) + end endまずは
before_action
で今回タスクを表示するUser
のインスタンスを@user
に代入しています。もし、params[:user_id]
が存在しているのであれば、/users/:id/tasks
からのリクエストなのでUser.find(params[:user_id])
で対象のUser
のインスタンスを取得します。params[:id]
が存在していなければトップページからのリクエストなのでcurrent_user
を取得します。index
アクションでは、この@user
が持っている今日付けのタスクを全て取得しています。create
アクションでは、飛んできたパラメーターにログインユーザーのuser_id
をセットしてtasks
テーブルのレコードを作成し、元いたページ(トップページ or/users/:id/tasks
)にリダイレクトしています。次に View を書いていきましょう。Welcome メッセージとログアウトリンクだけであったページを、タスク一覧とタスク作成フォームの表示ページとしてマークアップしていきます。
app/views/tasks/index.html.erb<p><%= @user.name %></p> <% if @user == current_user %> <%= form_with model: Task.new, local: true do |f| %> <%= f.text_field :name, required: true %> <%= f.submit '作成' %> <% end %> <% end %> <ul> <% @tasks.each do |task| %> <li> <%= check_box_tag "finished#{task.id}", true, task.finished, data: { id: task.id }, disabled: (@user != current_user) %> <%= label_tag "finished#{task.id}", task.name %> </li> <% end %> </ul> <%= link_to 'ログアウト', logout_path, method: :delete %> <% if @user == current_user %> <script> $(document).ready(function(){ $('#task_name').focus(); }); </script> <% end %> <style> li { list-style: none; } :checked + label { color: gray; text-decoration: line-through; } </style>今回のアプリケーションでは、タスクのチェックボックスにチェックを入れた状態を完了状態とします。ただし、人のタスクを勝手に完了にするわけにはいかないので、
disabled: (@user != current_user)
で今見ているページが自分のページでない時に限りチェックボックスを非活性にしています。チェックボックスをチェックした時にサーバーにリクエストを送ってfinished
を変更する処理は後ほど書いていきます。今はただチェックボックスを表示するだけのコードです。check_box_tag
の各種引数は覚えるかググるかしかないので、まあ適当に察してください。フォームヘルパーの引数順は何百回ググっても覚えられませんね。まあ、都度ググれば良いのです。チェックボックスの活性状態と同様に、タスクの新規作成フォームもif @user == current_user
で今見ているページが自分のページである場合のみ表示しています。今回の View には純粋な HTML の文書構造だけではなく、
script
要素とstyle
要素を使っています。script
要素では、JavaScript でページを表示した時にタスク名の入力フォームにフォーカスが当たるようにしています。こうすることで、タスク名を打って return キーを叩く操作を繰り返すだけで次々にタスクを作っていくことができて快適になります。ここでも jQuery を利用しています。style
要素では軽くデザインを当てていて、li
要素のlist-style: none;
で箇条書きの・
を非表示にして、チェックされたチェックボックスの後続のlabel
要素のテキストカラーをグレーにして取り消し線を付けています。まあどちらも触ってみれば効果が分かるでしょう。ということで動作確認です。少し複雑な仕様で作り込んだのでテストも複雑です。テストコードを書いた方が早くテストできるのでストレスは溜まりますが、一生懸命ブラウザーで手動確認していきましょう。準備として、別のユーザーをサインアップフォームから作成しておいてください。サインアップを何度か繰り返していて、もう今のデータベースの状態がよく分からなくなってしまった方は、一度データベースをリセットしてゼロから作っていきましょう。
% rails db:migrate:reset % rails c irb(main):001:0> User.create(name: 'テストアカウント', account_id: 'test', password: 'password', password_confirmation: 'password') irb(main):002:0> User.create(name: 'テストアカウント2', account_id: 'test2', password: 'password', password_confirmation: 'password') irb(main):003:0> exit2ユーザーをシステムに作成できたらログインとログアウトを繰り返しながら双方のタスクをいくつか作ってみて、以下を確認してみましょう。
- トップページ(localhost:3000)
- 自分のタスクが表示されること
- タスクの作成が行えること
- チェックボックスをチェックできること
- 自分のタスク一覧ページ(localhost:3000/users/1/tasks)
- 自分のタスクが表示されること
- タスクの作成が行えること
- チェックボックスをチェックできること
- 他人のタスク一覧ページ(localhost:3000/users/2/tasks)
- 他人のタスクが表示されること
- タスク作成フォームが表示されていないこと
- チェックボックスをチェックできないこと
タスクの更新
チェックボックスをチェックすることで、
tasks
テーブルのfinished
列を更新します。今回はチェックボックスの状態変更のイベントを拾って Ajax で非同期にサーバーにステータス更新リクエストを投げることにします。まずはルーティングにupdate
アクションを追加します。config/routes.rbresources :users, only: [] do resources :tasks, only: :index end - resources :tasks, only: :create + resources :tasks, only: %i[create update]次に
TasksController
のcreate
アクションの下にupdate
アクションを追加しましょう。上述のルーティングによって/tasks/:id
という URL でリクエストが送られてくるので、params[:id]
のTask
を更新します。ただし、更新しようとしているタスクがログインしているユーザーのタスクであった場合のみ更新を行い、ログインしているユーザーのタスクでなかった場合はログインページにリダイレクトしています。勝手に他人のタスクを弄られないようにする配慮です。app/controllers/tasks_controllerdef update task = Task.find(params[:id]) redirect_to login_path if task.user != current_user task.update(task_params) endそして、View の
script
要素内にチェックボックスの状態が変化した時に上記の Controller までリクエストを投げる処理を追加します。app/views/tasks/index.html.erb<script> $(document).ready(function(){ $('#task_name').focus(); }); + + $('input[type="checkbox"]').change(function() { + $.ajax('/tasks/' + $(this).data('id'), + { + type: 'patch', + data: { + task: { + finished: $(this).prop('checked') + } + }, + dataType: 'json' + } + ) + }); </script>
チェックボックスを触る度にページをリロードされてはストレスなので、Ajax で非同期にリクエストを投げることでページ遷移を挟まずにリクエストを投げています。やや複雑に見えるコードですが、落ち着いて1行ずつ読んでみれば意味は分かるはずです。
$('input[type="checkbox"]').change
はチェックボックスが変更された場合イベントを拾おうとしています。$.ajax('/tasks/' + $(this).data('id')
では Ajax でリクエストを送る URL を組み立てています。変更されたチェックボックスである$(this)
のdata-id
属性を使って/tasks/1
といった URL を作っています。data-id
属性についてピンと来なければcheck_box_tag
にどんなパラメーターを与えたかと、結果としてどのような HTML が生成されているかをブラウザーからソースコードを見て確認してみましょう。type: 'patch'
は PATCH リクエストを送るためです。PATCH がピンと来なければrails routes
コマンドを実行してconfig/routes.rb
によってどのような URL とメソッドがアプリケーションに定義されているかを確認してみましょう。data: { task: { finished: $(this).prop('checked') } }
はサーバーに送るパラメーターです。動作確認する時にチェックボックスを操作した時にrails s
に出るログを確認してチェックを付けた時と外した時でそれぞれどんなパラメーターが飛んできているかを確認してみましょう。ということで、早速動かしてみましょう。チェック状態がページをリロードしても変わらなければ ok です。なんと、これで web アプリケーションとしては完成です。日次のタスクをひたすら登録して消化していくには充分な機能ができました。勿論、まだまだ改善の余地はあるのでスタイリングをするなり、ユーザーのお気に入り機能を付けるなり、他人のタスクを見るだけならログイン必須ではなくしたり、いろいろ取り組んでみると力が付くかもしれませんね。とりあえず、本記事で作りたかった web アプリケーションはこれで完成です。お疲れ様でした。
モバイルアプリケーション用 API 実装
ここからは、モバイルアプリケーションからこの web アプリケーションを扱えるように API を実装していきます。外部からでもタスクの情報を取得したり、タスクの完了状態を変更したりできるようにするためのインターフェースを提供するということですね。早速やっていきましょう。
認証
今作った web アプリケーションでは、タスクを見るにも作るにも更新するにもアカウントIDとパスワードによるログイン認証が必要でした。モバイルアプリケーションも同様で、正しいアカウントIDとパスワードを入力したモバイルアプリケーションにのみタスクの操作を許してあげる必要があります。web アプリケーションは、モバイルアプリケーションが正しいアカウントIDとパスワードを送ってきた場合は API トークンを返します。web アプリケーションは、モバイルアプリケーションがパラメーターに乗せてきた API トークンでログインユーザーを判別します。何を言っているのか分からないと思いますので、実装を始めてしまいましょう。作っていくうちに何となく分かってくるはずです。
APIトークンの発行
まずは
users
テーブルにapi_token
の列を追加します。% rails g migration AddApiTokenToUsers api_token:string % rails db:migrateそして、
users
テーブルにレコードを追加する時に自動でAPIトークンを埋めるようにします。何と1行追加するだけでbcrypt
が勝手にやってくれます。app/models/user.rbclass User < ApplicationRecord has_many :tasks has_secure_password + has_secure_token :api_token validates :name, presence: true validates :account_id, presence: true, uniqueness: true end
これで新規の
User
に関しては自動でapi_token
がセットされるようになりましたが、既に存在するusers
テーブルのレコードのapi_token
は空のままなのでrails console
からワンライナーで一気に発行してしまいましょう。ちゃんと発行されたか不安であればUser.first.api_token
等で確認してみましょう。これで API トークンの発行準備は完了です。% rails c irb(main):001:0> User.find_each { |user| user.regenerate_api_token }ログイン
次に、ログイン用のルーティングを追加します。API に関する URL は必ず
/api
で始めることにしましょう。また、API 用の Controller はApi
という名前空間で括ることにしましょう。つまり、ログイン API は URL が/api/login
で Controller はapp/controllers/api/login_controller.rb
にあるApi::LoginController
クラスとなります。config/routes.rbresources :tasks, only: %i[create update] + + namespace :api do + get 'login', to: 'login#show' + end
次に Controller を実装していきます。
app/controllers/api/login_controller.rbclass Api::LoginController < ApplicationController def show @user = User.find_by(account_id: params[:account_id]) if @user&.authenticate(params[:password]) render status: :ok, json: { api_token: @user.api_token } else render status: :unauthorized, json: {} end end endそれでは、localhost:3000/api/login?account_id=test&password=password にアクセスしてみましょう。このような API トークンが表示されれば ok です。
また、パラメーターに誤ったアカウントIDかパスワードを指定してみましょう。この時は API トークンは返ってきません。
タスク CRUD API
認証と一覧の取得
自身のタスクの一覧を返したり、更新させたりという機能を書いていくわけですが、それを要求してきたユーザーが上記の API トークンをちゃんと持っているかを確認する必要があります。ルールとして、リクエストのパラメーターに必ず
api_token
を与えてもらうことにします。api_token
のパラメーターが渡されていない場合、あるいは誤っている場合のリクエストは無視します。app/controllers/api/tasks_controller.rbclass Api::TasksController < ApplicationController before_action :authenticate_by_token def index @tasks = @user.tasks.where(created_at: Time.zone.today.all_day) render json: @tasks.map { |task| { id: task.id, name: task.name, finished: task.finished } } end private def authenticate_by_token @user = User.find_by(api_token: params[:api_token]) render status: :unauthorized, json: 'Invalid API token' if @user.blank? end end
before_action
では、トークンの認証を行い失敗した場合にInvalid API token
という文字列とステータスコード 401 を返します。正しい API トークンを与えた場合はタスクの一覧の json が返ってきます。これが
@tasks.map { |task| { name: task.name, finished: task.finished } }
の結果です。モバイルアプリケーションは、この情報を元にタスクの一覧画面を組み立てるわけですね。作成と更新
続いて作成と更新の API を一気に書いていきます。やることは web アプリケーションとほとんど同じなのでサクサクといきましょう。まずはルーティングに
create
とupdate
アクションを追加します。config/routes.rbnamespace :api do get 'login', to: 'login#show' - resources :tasks, only: :index + resources :tasks, only: %i[index create update] endそして、Controller に web の時と同様に
create
とupdate
アクションを実装していきます。app/controllers/api/tasks_controller.rbclass Api::TasksController < ApplicationController + skip_forgery_protection before_action :authenticate_by_token def index @tasks = @user.tasks.where(created_at: Time.zone.today.all_day) render json: @tasks.map { |task| { id: task.id, name: task.name, finished: task.finished } } end + + def create + task = Task.create(task_params.merge(user: @user)) + render json: { id: task.id, name: task.name, finished: task.finished }, status: :created + end + + def update + task = Task.find(params[:id]) + (task.user == @user) ? task.update(task_params) : render(status: :unauthorized) + end private def authenticate_by_token @user = User.find_by(api_token: params[:api_token]) render status: :unauthorized, json: 'Invalid API token' if @user.blank? end + + def task_params + params.require(:task).permit(:name, :finished) + end end
create
アクションとupdate
アクションの中身はほとんど前回と同じなので問題ないでしょう。問題があるとすれば先頭にしれっと追加されたskip_forgery_protection
という一行です。これは話すと長くなるのですが、Ruby on Rails はデフォルトで CSRF(Cross-Site Request Forgeries) 保護が行われています。外部の攻撃者から不正なリクエストを受けても弾けるように、POST や PATCH リクエストは認証トークンを添えて送らなければリクエストを処理しません。認証トークンで認証できない場合は Controller のbefore_action
時点で弾かれるのでcreate
やupdate
といったアクションに入ってくることはありません。skip_forgery_protection
はこれをskip_before_action
するためのものです。この後curl
で API を叩いてみるので興味のある方はskip_forgery_protection
をコメントアウトした状態で API を叩いてrails server
のログを確認してみると良いでしょう。Can't verify CSRF token authenticity.
というエラーが表示されているはずです。そして作成や更新は行われずにデータベースに変化は起こっていないはずです。ただ、この Controller に関しては、authenticate_by_token
メソッドで自前のセキュリティゆるゆるのトークン認証を実装しているので、一旦は CSRF 保護をスキップします。それでは、動作を確認してみましょう。まずは作成です。ターミナルから
curl
コマンドを実行してサーバーに POST リクエストを送ってみます。api_token
はご自身のものに差し替えてください。先ほどログイン API で GET した API トークンです。% curl -X POST -d 'task[name]=by API&api_token=8YKPhsDNphD91j4EaQxyf6JF' localhost:3000/api/tasks実行したらブラウザーのタスク一覧画面か、一覧 API で返ってくる json を確認してみましょう。新しいタスクが追加されていれば ok です。
POST による作成が問題なければ、次は更新の PATCH リクエストを送ってみましょう。今作ったタスクを完了状態にしてみましょう。API トークンを差し替えるのは勿論のこと、URL の末尾の
Task
の ID も今作ったTask
の ID に差し替えてください。rails console
でTask.last.id
を叩けば最後に作ったTask
の ID が取れます。% curl -X PATCH -d 'task[finished]=true&api_token=8YKPhsDNphD91j4EaQxyf6JF' localhost:3000/api/tasks/7再びブラウザーのタスク一覧画面か、一覧 API で返ってくる json を確認してみましょう。完了状態になっている、あるいは
finished
がtrue
になっていれば ok です。ここまで実装できれば、モバイルアプリケーションからタスク管理するための準備は整いました。お疲れ様でした。
デプロイ
最後にこの web アプリケーションをどこかのサーバーに置く必要があります。本記事では無料かつクレジットカードの登録も必要がない Heroku にデプロイする手順を雑に書きますが、お好きなサーバーに上げてもらって大丈夫です。どこにも上げたくなくてお手軽にやりたければ、ngrok で
localhost:3000
をポートフォワーディングする手もあります。本記事では「web アプリケーションを作ったった!!」という気分になりたいので、とりあえず Heroku の無料枠にデプロイします。まずは Heroku にサインアップしてインストールガイドに従って heroku-cli をインストールします。Mac であれば現状は以下です。
% brew tap heroku/brew && brew install herokuHeroku にログインします。下記のコマンドを実行するとブラウザーからのログインが求められます。
% heroku loginHeroku では Git のリポジトリを push する形でデプロイするので、もしここまで Git 管理していなかった人は雑に commit してください。
% git commit -am "Create task management application"Heroku にアプリを作ります。このタイミングで URL が割り振られます。
% heroku create Creating app... done, ⬢ limitless-earth-31665 https://limitless-earth-31665.herokuapp.com/ | https://git.heroku.com/limitless-earth-31665.git
お好みで名前を変更してください。ドメインになります。勿論人と被ると弾かれます。
zone-web.herokuapp.com
は頂いた。% heroku rename zone-web Renaming limitless-earth-31665 to zone-web... done https://zone-web.herokuapp.com/ | https://git.heroku.com/zone-web.git
heroku create
した段階で、git remote
にheroku
が自動登録されているので、ここに push するだけでデプロイできます。3分ぐらい時間がかかると思うので、お茶でも飲みながら休憩しましょう。% git push heroku master無事にデプロイできたら、Heroku 上で
rails db:migrate
を実行してもらいます。heroku run
を頭に付けるだけでコマンドを走らせられるのは素晴らしいですね。% heroku run rails db:migrateこれで準備は完了です。
heroku create
かheroku rename
の時に割り振られた URL へアクセスしてみましょう。ログインページにリダイレクトされれば、ちゃんとアプリケーションが動いています。サインアップして自由にアプリケーションを操作してみましょう。これで、web アプリ編の演習は終了です。お疲れ様でした。モバイルアプリ編 でお会いしましょう。
終わりに
今回の演習では、最小限の認証付きタスク管理アプリケーションを開発し、モバイルアプリケーションに向けた API の実装を行いました。もし完走した方がいらっしゃいましたら、こっそり私(@OgiharaRyo)まで教えて頂けると嬉しいです。冒頭でも言い訳しましたが、Advent Calendar でやるには本当に本当に長い演習になってしまいました。重ねてお詫び申し上げます。
Ruby on Rails Advent Calendar 17日目は bake0937 さんです。
- 投稿日:2019-11-29T04:02:47+09:00
#Rails ( #Ruby + ActiveSupport ) + Time.use_zone + travel_to で現地時刻=日本時刻=JSTを固定して UTC 変換結果を見て遊ぶ例
- タイムゾーンの9時間の扱いを一生かかっても覚えられる気がしない
- 足し算と引き算を間違える確率が65%
- Rubyで遊んでいたらだんだん分かってきた気がするけれど
- タイムゾーンに慣れ親しむだけで1週間かかって、3日で忘れる気がした
require 'active_support/core_ext' # 日本時間の年始の時間 jst_beggining_of_year = Time.use_zone('Tokyo') { Time.local(2020, 01, 01, 00, 00, 00) } # travel するために必要 require 'active_support/testing/time_helpers' include ActiveSupport::Testing::TimeHelpers # 日本時間の年始に時間を固定する travel_to jst_beggining_of_year # 今日の日付は 2020-01-01 Date.current # => Wed, 01 Jan 2020 # 現在時刻は 2020-01-01 00:00:00 で JST のタイムゾーンも持っている Time.now # => 2020-01-01 00:00:00 +0900 # こちらも同じく Time.current # => 2020-01-01 00:00:00 +0900 # Time.nowはRubyのclassだがちゃんとtravelできている Time.now.class # => Time # Time.currentはRails=ActiveSupportのclassみたいだがtravelできている # Time.zone= を指定していない場合は Ruby の Time.now と同じように働く気がしたが合ってるかな Time.current.class # => ActiveSupport::TimeWithZone # Time.zone.now メソッドでは、Time.zoneの指定をしていないので何も得られない # Time.currentとは扱いが違うみたいだ Time.zone.now # NoMethodError: undefined method `now' for nil:NilClass # Time.zone 指定を日本時間にする Time.zone = 'Tokyo' # => "Tokyo" # 現在時刻は 2020-01-01 00:00:00 で JST のタイムゾーンや曜日も持っている Time.zone.now # => Wed, 01 Jan 2020 00:00:00 JST +09:00 # Time.zone.now は ActiveSupport の class を持っている Time.zone.now.class # => ActiveSupport::TimeWithZone # 今日の日付は 2020-01-01だ # こちらも Ruby ではなく Rails = ActiveSupport のもの Date.current # => Wed, 01 Jan 2020 # Time.zone 指定を日本時間にする Time.zone = 'UTC' # => "UTC" # UTCでいうと今日の日付はまだ、前年の年末だ Date.current # => Tue, 31 Dec 2019 # 日本時間の年始の0時は、UTCでいうと9時間遅い、年末の15時だ Time.zone.now # => Tue, 31 Dec 2019 15:00:00 UTC +00:00Original by Github issue
- 投稿日:2019-11-29T03:15:24+09:00
#Ruby と #Rails の Time や Time.zone の扱いががマジで分からないし混沌としてるのでちょっとだけ整理したい。
Try at
この時間に試しました
- JST 2019-11-28 hour 19
- UTC 2019-11-28 hour 10
For Ruby
require ‘active_support/core_ext’
No Time zone setting
タイムゾーン指定がないとメソッドが使えなかったり
Time.zone # nil Time.zone.now # NoMethodError: undefined method `now’ for nil:NilClassTimezone指定
Time.zone は ActiveSupportのインスタンスになる
タイムゾーンを切り替えるにはTime.zone=
メソッドを実行Railsであればapplication.rb の configに書くだろうが
タイムゾーン指定自体は
Tokyo
でもAsia/Tokyo
でも同じようにはからってくれるっぽい
ちなみにTime.zone = ‘JST’
じゃ動かないとか信じられるかいTime.zone = 'UTC' => "UTC" Time.zone => #<ActiveSupport::TimeZone:0x00007f8610941ef0 @name="UTC", @tzinfo=#<TZInfo::DataTimezone: Etc/UTC>, @utc_offset=nil> => #<ActiveSupport::TimeZone:0x00007f86108d1a38 @name="Asia/Tokyo", @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>, @utc_offset=nil> Time.zone = 'Tokyo' => "Tokyo" Time.zone Time.zone = 'Asia/Tokyo' => "Asia/Tokyo" Time.zone => #<ActiveSupport::TimeZone:0x00007f86108d1a38 @name="Asia/Tokyo", @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>, @utc_offset=nil> Time.zone = 'JST' # ArgumentError: Invalid Timezone: JSTUTC
UTCをtimezone指定した場合
Time.zone = ‘UTC’ # => “UTC” Time.zone # => #<ActiveSupport::TimeZone:0x00007fe40e15ed08 @name=“UTC”, @tzinfo=#<TZInfo::DataTimezone: Etc/UTC>, @utc_offset=nil> Time.zone.now # => Thu, 28 Nov 2019 10:00:31 UTC +00:00 Time.zone.parse(‘2020-01-01 00:00:00’) # => Wed, 01 Jan 2020 00:00:00 UTC +00:00JST
JSTをtimezone指定した場合
Time.zone = ‘Tokyo’ # => “Tokyo” Time.zone # => #<ActiveSupport::TimeZone:0x00007fe40d2721a0 @name=“Tokyo”, @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>, @utc_offset=nil> Time.zone.now # => Thu, 28 Nov 2019 19:00:47 JST +09:00 Time.zone.parse(‘2020-01-01 00:00:00’) # => Wed, 01 Jan 2020 00:00:00 JST +09:00Time系
こちらはRuby系なので
Time.zone
を参照しない、というよりTime.zone
なんていうメソッドもクラスもない
Rails = ActiveSupport の Time.zone 系メソッドとは分けて考えることRuby で最初から出来るかと思いきや、 一部のメソッドは requireが必要という罠 -> Time.parse とか
require active support でも動くようになるので、まさかのactivesupportのメソッドかと思って驚いたが違ったタイムゾーンを変えるには ruby実行時の環境変数を変える
TZ=UTC ruby ... TZ=Asia/Tokyo ruby ...環境変数指定もしくはシステムのタイムゾーンによって結果が変わる
Rubyだけの処理でもタイムゾーン情報を持っているようだTime.now # => 2019-11-28 10:19:37 +0000 Time.now # => 2019-11-28 19:19:43 +0900 # Time.parse / Time.zone.parse Time.parse(‘2020-01-01 00:00:00’) # NoMethodError: undefined method `parse’ for Time:Class Time.parse(‘2020-01-01 00:00:00’) # => 2020-01-01 00:00:00 +0900 Time.zone = ‘UTC’ # => “UTC” Time.zone.parse(‘2020-01-01 00:00:00’) # => Wed, 01 Jan 2020 00:00:00 UTC +00:00RubyっぽいのにRails? current系
nowじゃなくてcurrentっていう名前のメソッドはRubyじゃなくてRails/AcviveSupportのものだ
Time.current # NoMethodError: undefined method `current’ for Time:Class require ‘active_support/core_ext’ Time.current # => 2019-11-28 19:21:56 +0900 なのにclassはTimeという奇特なやつ Time.current.class # => Timeちなみに
Date.today
は Ruby のものだがDate.current
Date.yesterday
Date.tomorrow
は Railsのものだという大いなる罠もあるOriginal by Github issue
- 投稿日:2019-11-29T02:58:55+09:00
Railsでのjoinsの結果のidについて
Railsでテーブル結合してデータを取得した時、idを参照元の主キーにしたいのに参照先の主キーになってしまっていた。
結論としてHoge.joins(:fuga).where(fuga_name:"hoge").select("fuga.*,hoge.*")このようにselectで最後に指定したテーブルの主キーが結合したデータの主キーとなる。
- 投稿日:2019-11-29T02:27:23+09:00
金井の戯言【NIT-Numazu Advent Calendar 2019】
書くことないんですけど
なんかプロコン同好会で参加して欲しいとか言われたから参加したけどさあ
どうすんのこれマジで
現時点で書くこと無いし、何も強い人じゃないから既に劣等感しか無いんですけど
技術的なこと書ける人、強くていいですね!^ ^…ぼやいててもしょうがないので取り敢えず自己紹介書きます。
とってつけたような自己紹介
名前 : 金井 一真
趣味 : ぷろぐらみんぐ、原付で小旅行、ピアノ
使用言語 : C / C++ / Ruby / HTML / CSS /
コメント : 最近基本情報受かりました、おら基本情報技術者様だぞ道を開けろ
関連リンク : ついたー ぎとはぶ適当に書いてたら方針が決まった
ぼく結構何事も浅く広くな人間なので(信条は狭く深くなのに!)、色んなことをちまちま並べて書いていくことにする
一記事で二度美味しいって言え!!!!!!!!!!!!!!!
それじゃいきます
vimのはなし
今これ書いてるのもvimです。vimを弄り回す人たち凄いですね、これmarkdownなのにリアルタイムで変更が見れるプラグイン使って書いてるんですよ。
あ、URL貼っとこ。
https://github.com/suan/vim-instant-markdown
というわけで個人的にすこポイントを幾つか挙げていく。キーボードから手が離れない
これ90%くらいを占めてます。それくらいキーボードから手が離れないのは重要。
いちいちマウス触るのめんどくさいんすよね。それだけ。
自分とマウスは一体だから問題ないですよって人は別にいいんじゃないですかね?わからん。なんかかっこいい
逆にこれがかっこいいと思わん人とは感性が合わないんやろなって思います。
画面分割とか、ひたすらキーボードカタカタやって作業してるのオシャレじゃないですかね?そんなことない?強めのオタクたちのプラグインがたくさんある
多分これを一番推すべきなんでしょうね。
ぼくはまだvimの素の機能すら完全に使いこなせていないので、あんまたくさん入れてないんですよ。今はmarkdown周りだけかな?
vimスーパー完璧人間になってからあれこれいれると作業効率がとんでもないことになると思います。そう信じてます。とりあえずこんなもんですかね。実際vimmerになってから作業効率は間違いなく上がってるし、そこそこ使いやすいエディタだと思います。端末からのアクセスが早いのも良し、軽いのも良し。かっこいい。いいことずくめ。
ただ惜しむらくは、日本語入力との相性が悪いことですかね…
編集モードと入力モードを行ったりきたりするエディタなので、編から入に切り替えようってときに日本語のままの状態になってると、
こうなる。
(なんか伝わりにくいスクショですね、vim触ったことある人は言わんとすることわかるはず)
これ急いでるときにやられるとほんま迷惑で、編集モードに入ったら自動で英語入力に戻すシステムだかプラグインだか入れなきゃいけない。あるのかな?探してみるか。
といった感じでvimの話は以上。みんなもvim使ってね!bot開発のはなし
ぼくの趣味みたいになってる奴です。ついったのbotと、でぃすこのbotがあります。
これなんでハマってるかというと、自分のした改造がレスポンスとしてわかりやすく現れる、ってのと、色んな人に遊んでもらえるってところかなって思います。
↑ これは僕のbotをつっつくたべさん
要は、わかりやすく評価されるから嬉しいよねって話です。
他にもいいところが有って(活かせてはいない)、色んなAPIとか.jsonファイルとかの扱いが上手になります。外部のAPIを使うとbotの完成度が上がるので良いです。ぼくのbotでは天気予報が出来たりします。すごい!すごいって言え!
ただ、あんまりdiscordの方のファイル構造がうまくいってなくて困ってます。ファイル構造云々は多分rubyの理解度の問題なんだよな、精進します。
みなさんのdiscordにもかめせんにんを入れて遊んでみてね!
ついったーのbotの方は、僕のツイを分解してマルコフ連鎖で繋げてツイートする、なんてことをしている奴です。あの、案外面白いです。僕のことをフォローしている人は是非セットでフォローしていただきたい。
というわけで以上、bot開発のはなしでした。みんなもbot開発、やってみよう!Josephineのはなし
かっこつけて英語使いたかっただけです。ジョセフィーヌのはなしです。
ジョセフィーヌって誰だよ、何だよ、って方もいらっしゃると思うのでご紹介します。
この亀のぬいぐるみの名前です。かわいい。
この子は、八景島シーパラダイスで開催された「鎮守府第三次瑞雲祭り」にいった際にお土産で買ってきました。
会場内を歩き回って疲れたときに亀を見て癒されたのが妙に印象に残っており、「せっかくだから」と言って買いました。
名前の由来は…父に強引に命名されました
これマジ?(由来とか)ないじゃん…
ちなみに父に命名される前は「亀五郎」と呼んでました。なんで五郎なんだ。
ちなみに、ついったのFFの絵を描く人にお願いして、擬人化を描いて貰ったりしました。
かわいい。
僕の顔みたいな存在なので、ぜひ覚えてあげてください、ジョセフィーヌのはなしでした。もう十分だろ
書くことがないとぼやきから始まった記事でしたが、なんだかんだそこそこ書けたような気がします。埋めるだけならついったーらんどで鍛えた文章力が火を噴きます。噴くな。
記事作成を完走した感想(激うまギャグ)ですが、専門的な内容が結局書けなかったのが悔しい、是れに尽きます。色々なことを齧っている割には深く学習は出来ていないので、今の僕はなんとも中途半端なエンジニアなのかなと。まぁでも、ひとまずやることは応用情報の勉強でしょうし、専門化はまだまだ先のお話となりそうです。
というわけで、この記事もそろそろ終わりです。如何だったでしょうか?もしちょっとでも面白いと思っていただけたのであれば、僕のついったーの方をフォローしていただけると泣かない程度に喜びます。
御拝読頂きまして、ありがとうございました。お疲れやまでした。
- 投稿日:2019-11-29T01:20:51+09:00
email_fieldの入力フォームに初期値を反映させる方法
bootstrapを使用しながらemail_fieldの入力フォームに初期値を反映させる方法。
ポートフォリオを採用側に見てもらう時少しでも手間を省くため、ワンクリックで済むようにと初期値設定を考えました。
text_fieldは大量に検索ヒットしたけどemail_fieldとpassword_fieldは出てこなくて苦戦したので、自分用メモとして。new.html.erb<%= form_with(url: login_path, scope: :session, local: true) do |f| %> <div class="form-group"> <%= f.label :email, 'メールアドレス' %> <%= f.email_field :email, class: 'form-control', placeholder: 'hoge@gmail.com' %> </div> <div class="form-group"> <%= f.label :password, 'パスワード' %> <%= f.password_field :password, class: 'form-control', value: 'hoge' %> </div> <%= f.submit 'ログイン', class: 'btn btn-primary btn-block' %> <% end %>email_fieldの方には placeholder: 'hoge@gmail.com'
password_fieldの方には value: 'hoge'下記のURLのchapter 7−4を参考にしてください。
email_fieldとpassword_field初期値設定
- 投稿日:2019-11-29T01:00:16+09:00
#Ruby で #Rails と同じ Time.zone.now を使う
- activesupportをinstall & require する
- Time.zone を指定する
- あとは Rails と同じようにする
gem install activesupportrequire 'active_support/core_ext' # => true Time.zone # => nil Time.zone = 'Tokyo' # => "Tokyo" Time.zone # => #<ActiveSupport::TimeZone:0x00007fded1c02c10 @name="Tokyo", @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>, @utc_offset=nil> Time.zone.now # => Thu, 28 Nov 2019 17:41:36 JST +09:00Original by Github issue
- 投稿日:2019-11-29T00:27:12+09:00
#Ruby / #Rails で日付を UTC あるいは local時刻 = JSTなどで time に変換するにはするには引数を指定せよ
- 投稿日:2019-11-29T00:24:15+09:00
AndroidのlogcatをTSV形式に加工するrubyスクリプト
Androidのlogcatを解析する際にExcelやgoogleスプレッドシートに貼り付けて、
フィルタ機能を使って一部のログのみ抽出して表示したいので作ってみました。変換スクリプト
locat2tsv.rb#!/usr/bin/env ruby while line = ARGF.gets (date, time, pid_package, level_tag, message) = line.split(" ",5) (pid, package) = pid_package.split("/") (level, tag) = level_tag.split("/") data = ["#{date} #{time}", pid, package, level, tag, message] puts data.join("\t") end変換結果をgoogleスプレッドシートに貼り付けた例
使い方
cat env-logcat.log | ./locat2tsv.rb | pbcopy./locat2tsv.rb env-logcat.log | pbcopy変換後のTSVのカラムは次の通りです
1. 日時
2. pid
3. パッケージ名
4. ログ出力レベル
5. tag
6. ログメッセージ