- 投稿日:2020-06-26T22:32:58+09:00
curlコマンドにより作成したapiでデータを取得する
環境
cloud9(api取得側)
local(api提供側)
ruby 2.6.3
Rails 6.0.2.1結論
controller def index uri = `curl -v -X GET "http://stage-smartyoyaku-env.ap-northeast- 1.elasticbeanstalk.com/api/v1/tasks" \ -H "Authorization: Bearer ここにTokenを書く"` @test = JSON.parse(uri) end*取得できているかブラウザで確認します
viewfile <%= @test %>それまでのエラー内容
cloud9のターミナルでcurlコマンドを入力すると。。
curl: (7) Failed to connect to localhost port 3000: Connection refused調べて見るとサーバーが立ち上がってない場合かポート番号が間違っているかのようです。
ブラウザでlocalhost:3000にアクセスしましたが問題なし。port番号もあっていましたが、、もう一度整理
・cloud9側のアプリでcurlコマンドを叩いている。(取得する側)
・local環境の3000portでapiを提供しているアプリを立ち上げている。(提供する側)←ここ重要ん?cloud9(インターネット上)からlocalには接続できないんじゃ、、
$ lsof -i:3000 $ cloud9上で3000portが使われているか確認→表示されないつまりapiを提供する側はherokuなどでネットにurlを公開し、そこからデータを取ってくる必要があります。
注意点
ターミナルでcurlコマンドを打つ場合とcontrollerに書く場合は微妙に書き方が違う。
controllerではcurlコマンドを``で囲む。ターミナルの場合 $ curl -v -X GET "http://stage-smartyoyaku-env.ap-northeast-1.elasticbeanstalk.com/api/v1/tasks" \ -H "Authorization: Bearer ここにTokenを書く"controllerの場合 def index uri = `curl -v -X GET "http://stage-smartyoyaku-env.ap-northeast- 1.elasticbeanstalk.com/api/v1/tasks" \ -H "Authorization: Bearer ここにTokenを書く"` end
- 投稿日:2020-06-26T22:17:14+09:00
Rails gem rooを使って、エクセルファイルからデータ登録
目的
ユーザーが複数のデータをアプリに投稿したいとき、エクセルファイルを選択するとデータベースに保存できるようにしたい。
実装
アプリ立ち上げ
rails new sample_app -d postgresql rails db:create
rails newで新規アプリを作成、データベースも作成する。
コントローラー作成
rails g controller users index
indexアクションを持つUserコントローラーを作成。
ルーターとビューも一緒に作成される。モデル作成
rails g model user name:string email:string rails db:migrate
usersテーブルには、string型のnameとemailのカラムを作成。
ビュー
app/views/users/index.html.erb<h1>ユーザー一覧</h1> <table> <thead> <tr> <th>ID</th> <th>名前</th> <th>Eメール</th> </tr> </thead> <tbody> <% @users.each do |user| %> <tr> <td><%= user.id %></td> <td><%= user.name %></td> <td><%= user.email %></td> </tr> <% end %> </tbody> </table> <%= form_with url: import_users_path, local: true do |form| %> <%= form.file_field :file %> <%= form.submit %> <% end %>投稿フォーム生成。ファイル自体を保存するわけではなく、params[:file]を送ったアクション:importにてUserを登録できるようにする。
ルーター
config/routes.rbRails.application.routes.draw do resources :users do collection do post :import end end endUserにPostメソッドの:importアクション用のルーティングを追加する。
コントローラー
app/controllers/users_controller.rbclass UsersController < ApplicationController def index @users = User.all end def import User.import(params[:file]) redirect_to users_url end endindexアクションはscaffoldを使ったときと同じで全レコードを取得し、@usersに代入。
importアクションは、params[:file]を受け取って、usersテーブルのレコードを作成するimportメソッドを使う。モデルには、importメソッドの定義を書く。モデル
app/models/user.rbclass User < ApplicationRecord def self.import(file) xlsx = Roo::Excelx.new(file.tempfile) xlsx.each_row_streaming(offset: 1) do |row| user = self.new(id: row[0].value, name: row[1].value, email: row[2].value) next if self.pluck(:id).include?(user.id) user.save end end endUserモデルのモデルメソッドなので、def self.import の形で定義。params[:file].tempfileに送信したファイルのパスが入る。Roo::Excelx.new(file.tempfile)でファイルを読み取り、変数xlsxに代入。each_row_streamingメソッドで読み込んだファイルの行毎に処理を実行。
offsetオプションによって、処理を飛ばす行数を指定できる(一行目の項目欄を飛ばす)。
next if self.pluck(:id).include?(user.id)で同じidがusersテーブルに存在している場合は処理をスキップしている。gem rooを使えるように準備をする。
gem rooを使う準備
Gemfilegem "roo" #追記
ターミナルでbundle installする。また、config/application.rbにrequire "roo"を追記
config/application.rb# 省略 require 'rails/all' require "roo" # 省略これで、OK。
確認
サーバーを立ち上げ "http://localhost:3000/users" にアクセスして画面を確認する。
ファイルを選択を押して、次のようなエクセルファイルからデータを登録できる。
登録後、次のように画面が変わっていることを確認する。
参考記事
https://qiita.com/seitarooodayo/items/c9d6955a12ca0b1fd1d4
https://qiita.com/guri3/items/f20487516311b2a3db37
本当に参考になりました!ありがとうございます!後書き
応用して、社員表を一気に登録する機能をアプリに作ってみようと思います。
- 投稿日:2020-06-26T22:17:14+09:00
Rails gem rooを使って、エクセルファイルからデータ保存
目的
ユーザーが複数のデータをアプリに投稿したいとき、エクセルファイルを選択するとデータベースに保存できるようにしたい。
実装
アプリ立ち上げ
rails new sample_app -d postgresql rails db:create
rails newで新規アプリを作成、データベースも作成する。
コントローラー作成
rails g controller users index
indexアクションを持つUserコントローラーを作成。
ルーターとビューも一緒に作成される。モデル作成
rails g model user name:string email:string rails db:migrate
usersテーブルには、string型のnameとemailのカラムを作成。
ビュー
app/views/users/index.html.erb<h1>ユーザー一覧</h1> <table> <thead> <tr> <th>ID</th> <th>名前</th> <th>Eメール</th> </tr> </thead> <tbody> <% @users.each do |user| %> <tr> <td><%= user.id %></td> <td><%= user.name %></td> <td><%= user.email %></td> </tr> <% end %> </tbody> </table> <%= form_with url: import_users_path, local: true do |form| %> <%= form.file_field :file %> <%= form.submit %> <% end %>投稿フォーム生成。ファイル自体を保存するわけではなく、params[:file]を送ったアクション:importにてUserを登録できるようにする。
ルーター
config/routes.rbRails.application.routes.draw do resources :users do collection do post :import end end endUserにPostメソッドの:importアクション用のルーティングを追加する。
コントローラー
app/controllers/users_controller.rbclass UsersController < ApplicationController def index @users = User.all end def import User.import(params[:file]) redirect_to users_url end endindexアクションはscaffoldを使ったときと同じで全レコードを取得し、@usersに代入。
importアクションは、params[:file]を受け取って、usersテーブルのレコードを作成するimportメソッドを使う。モデルには、importメソッドの定義を書く。モデル
app/models/user.rbclass User < ApplicationRecord def self.import(file) xlsx = Roo::Excelx.new(file.tempfile) xlsx.each_row_streaming(offset: 1) do |row| user = self.new(id: row[0].value, name: row[1].value, email: row[2].value) next if self.pluck(:id).include?(user.id) user.save end end endUserモデルのモデルメソッドなので、def self.import の形で定義。params[:file].tempfileに送信したファイルのパスが入る。Roo::Excelx.new(file.tempfile)でファイルを読み取り、変数xlsxに代入。each_row_streamingメソッドで読み込んだファイルの行毎に処理を実行。
offsetオプションによって、処理を飛ばす行数を指定できる(一行目の項目欄を飛ばす)。
next if self.pluck(:id).include?(user.id)で同じidがusersテーブルに存在している場合は処理をスキップしている。gem rooを使えるように準備をする。
gem rooを使う準備
Gemfilegem "roo" #追記
ターミナルでbundle installする。また、config/application.rbにrequire "roo"を追記
config/application.rb# 省略 require 'rails/all' require "roo" # 省略これで、OK。
確認
サーバーを立ち上げ "http://localhost:3000/users" にアクセスして画面を確認する。
ファイルを選択を押して、次のようなエクセルファイルからデータを登録できる。
登録後、次のように画面が変わっていることを確認する。
参考記事
https://qiita.com/seitarooodayo/items/c9d6955a12ca0b1fd1d4
https://qiita.com/guri3/items/f20487516311b2a3db37
本当に参考になりました!ありがとうございます!後書き
応用して、社員表を一気に登録する機能をアプリに作ってみようと思います。
- 投稿日:2020-06-26T22:12:09+09:00
Ruby on Rails5 速習実践ガイド5.2対応 Chapter2
Chaptrt2 Railsアプリケーションをのぞいてみよう
Windows10の環境構築
実習でHyperVマネージャーにより、仮想環境はすでにあり。
Ubuntu 18.04.3をインストール
rbenv 1.1.2-30-gc879cb0 Gitを使ってGitHubからをインストール。
Ruby 2.5.1 インストール
Rails インストール
Node.js のインストールJavaScriptを用いる場合、効率よく配信するためにJavaScriptを圧縮する機能が備わっているが、そのためにJavaScriptランタイムが必要となるためにインストール。
PostgreSQL をインストールしセットアップ
PostgreSQLを起動。
sudo service postgresql start
sudo su postgres -c 'createuser -s {Ubuntuのログインユーザ}'
を打つと、以下のエラーが出た。
psql: FATAL: role “postgres” does not exist
{}無しでUbuntuのログインユーザー名を記載したらすんなり通りました。scaffoldを使ってscaffold_appという名前で「ユーザー管理アプリケーション」をRailsのコマンドで自動生成させる。
rails new scaffold_app -d postgresql
rails new
コマンドはRailsアプリケーションのひな型を生成。
アプリケーションの名前をscaffold_app
と指定。
scaffold_appディレクトリと関連ファイルが生成されて、ログ出力される。
この後 bundle installが自動的に実行されて、Railsの実行に必要なgemがインストールされる。
データベースの作成。config/databese.ymlというファイルの定義をもとに。
bin/rails s db:create
「rails」ではなく「bin/rails」で、アプリケーションのルートディレクトリ直下のbinディレクトリにあるrailsというスクリプトを呼び出している。Gemfileどおりのgemを利用できる環境上でrailsコマンドを実行できる。
railsサーバー起動
bin/rails s
railsは標準のHTTPサーバとしてPumaを採用。
WEBブラウザでサーバーの起動を確認
アドレスバーにhttp://localhost:3000/を入力。ユーザー管理機能のひな型を作る
「ユーザー」に関するscaffoldを自動生成。
bin/rails generate scaffold user name:string address:string age:integer
ユーザー管理機能に使うテーブルをデータベースに作成。
bin/rails db:migrate
ユーザー一覧画面を表示
http://localhost:3000/users
WEBブラウザへ上のURLを入力してアクセス。重要だと思ったポイント
- ユーザーを管理するために必要な機能「CRUD」が生成されている。 「作成(create)」「読み出し(read)」「更新(update)」「削除(delete)」
- コードの通り道 Railsアプリケーションは、MVC(モデル・ビュー・コントローラ)という考え方で構成。 コントローラーが呼ばれる。コントローラーにはリクエストに対する処理が書かれている「アクション」と呼ばれるメソッドが定義(一覧画面にアクセスした際はidnexアクション)されている。
- 投稿日:2020-06-26T21:57:45+09:00
Ruby on Rails5 速習実践ガイド5.2対応 Chapter3
Capter3タスク管理アプリケーションを作ろう
CRUDを備えたシンプルな「タスク管理アプリケーション」を、scaffoldを使わずにゼロから作成。
環境
Ruby 2.5.1
Ruby on Rails 5.2.1
PostgreSQL 10.5
Railsのエラーメッセージなどを日本語で出せるようにする
GithubのRails-i18nリポジトリにある翻訳されたファイル(rawファイル)をconfig/locales/ja.yml
としてダウンロード。
wget https://raw.githubusercontent.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml --output-file=config/locales/ja.yml
config/locales/ja.yml
を確認すると、以下のログが記載されている。--2020-06-26 14:40:30-- https://raw.githubusercontent.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml
192.168.24.1:8080 に接続しています... 接続しました。
Proxy による接続要求を送信しました、応答を待っています... 200 OK
長さ: 5117 (5.0K) [text/plain]
`ja.yml' に保存中0K .... 100% 10.3M=0s2020-06-26 14:40:31 (10.3 MB/s) - `ja.yml' へ保存完了 [5117/5117]
解決策としてWEBブラウザへ
https://raw.githubusercontent.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml
を張り付ける。
表示された以下のテキストの内容をja.yml内に張り付けて保存。
ja:
activerecord:
errors:
messages:
record_invalid: 'バリデーションに失敗しました: %{errors}'
restrict_dependent_destroy:
has_one: "%{record}が存在しているので削除できません"
has_many: "%{record}が存在しているので削除できません"
date:
abbr_day_names:
- 日
- 月
- 火
- 水
- 木
- 金
- 土
abbr_month_names:
-
- 1月
- 2月
- 3月
- 4月
- 5月
- 6月
- 7月
- 8月
- 9月
- 10月
- 11月
- 12月
day_names:
- 日曜日
- 月曜日
- 火曜日
- 水曜日
- 木曜日
- 金曜日
- 土曜日
formats:
default: "%Y/%m/%d"
long: "%Y年%m月%d日(%a)"
short: "%m/%d"
month_names:
-
- 1月
- 2月
- 3月
- 4月
- 5月
- 6月
- 7月
- 8月
- 9月
- 10月
- 11月
- 12月
order:
- :year
- :month
- :day
datetime:
distance_in_words:
about_x_hours:
one: 約1時間
other: 約%{count}時間
about_x_months:
one: 約1ヶ月
other: 約%{count}ヶ月
about_x_years:
one: 約1年
other: 約%{count}年
almost_x_years:
one: 1年弱
other: "%{count}年弱"
half_a_minute: 30秒前後
less_than_x_seconds:
one: 1秒以内
other: "%{count}秒未満"
less_than_x_minutes:
one: 1分以内
other: "%{count}分未満"
over_x_years:
one: 1年以上
other: "%{count}年以上"
x_seconds:
one: 1秒
other: "%{count}秒"
x_minutes:
one: 1分
other: "%{count}分"
x_days:
one: 1日
other: "%{count}日"
x_months:
one: 1ヶ月
other: "%{count}ヶ月"
x_years:
one: 1年
other: "%{count}年"
prompts:
second: 秒
minute: 分
hour: 時
day: 日
month: 月
year: 年
errors:
format: "%{attribute}%{message}"
messages:
accepted: を受諾してください
blank: を入力してください
confirmation: と%{attribute}の入力が一致しません
empty: を入力してください
equal_to: は%{count}にしてください
even: は偶数にしてください
exclusion: は予約されています
greater_than: は%{count}より大きい値にしてください
greater_than_or_equal_to: は%{count}以上の値にしてください
inclusion: は一覧にありません
invalid: は不正な値です
less_than: は%{count}より小さい値にしてください
less_than_or_equal_to: は%{count}以下の値にしてください
model_invalid: 'バリデーションに失敗しました: %{errors}'
not_a_number: は数値で入力してください
not_an_integer: は整数で入力してください
odd: は奇数にしてください
other_than: は%{count}以外の値にしてください
present: は入力しないでください
required: を入力してください
taken: はすでに存在します
too_long: は%{count}文字以内で入力してください
too_short: は%{count}文字以上で入力してください
wrong_length: は%{count}文字で入力してください
template:
body: 次の項目を確認してください
header:
one: "%{model}にエラーが発生しました"
other: "%{model}に%{count}個のエラーが発生しました"
helpers:
select:
prompt: 選択してください
submit:
create: 登録する
submit: 保存する
update: 更新する
number:
currency:
format:
delimiter: ","
format: "%n%u"
precision: 0
separator: "."
significant: false
strip_insignificant_zeros: false
unit: 円
format:
delimiter: ","
precision: 3
separator: "."
significant: false
strip_insignificant_zeros: false
human:
decimal_units:
format: "%n %u"
units:
billion: 十億
million: 百万
quadrillion: 千兆
thousand: 千
trillion: 兆
unit: ''
format:
delimiter: ''
precision: 3
significant: true
strip_insignificant_zeros: true
storage_units:
format: "%n%u"
units:
byte: バイト
eb: EB
gb: GB
kb: KB
mb: MB
pb: PB
tb: TB
percentage:
format:
delimiter: ''
format: "%n%"
precision:
format:
delimiter: ''
support:
array:
last_word_connector: "、"
two_words_connector: "、"
words_connector: "、"
time:
am: 午前
formats:
default: "%Y年%m月%d日(%a) %H時%M分%S秒 %z"
long: "%Y/%m/%d %H:%M"
short: "%m/%d %H:%M"
pm: 午後これでアプリケーション作成の下準備が整った。
- 投稿日:2020-06-26T21:54:10+09:00
【Rails】なんでsaveに成功したらredirect_toで失敗したらrenderなの?
はじめに
Railsで開発中、フォームの実装時にrenderの扱いについて悩むことがあったため、調べてく内にredirect_toとrenderの違いを改めて再確認出来たので、備忘として残しておくものです。
ついでにrenderで一瞬悩んだことも書いておきます。(具体的にはbefore_actionの扱い)環境
・Ruby 2.6.5
・Rails 6.0.3状況
飲食店情報を登録する様なアプリケーションを作っており、コントローラーに以下の様なアクションを設定していました。
shops_controller.rbdef new @shop = Shop.new end def create @shop = @owner.shops.new(shop_params) if @shop.save redirect_to :index else render :new end end*ストロングパラメータは今回関係ないので割愛
もはや何も考えずこう書いていましたが、保存に失敗した場合、renderを用いてもう一度投稿フォームに戻すのは何故か?という所を振り返ってみます。
結論としては、入力した内容を保持したままもう一度入力フォームに戻したいからです。
では何故その場合redirect_toではなくrenderを使えば実現できるのでしょうか?
redirect_toとrenderの挙動について振り返ってみます。redirect_toとは
redircet_toは指定したアクション(やパス)へのアクセスをクライアントサイドに行わせる処理です。
これにより、MVCの流れを再度踏まえた上でそのページにアクセスすることになります。
具体的には「指定されたアクションに再度アクセス⇨コントローラーのアクションを実行⇨対応するビューを表示」という流れですね。アクションのビューにurlからアクセスする際の挙動と全く同じことをやってくれるわけです。
renderとは
それに対しrenderは、アクションを再度経由することなく指定したアクションに対応するビューをただ表示してくれます。
redirect_toと違い、newアクションの中身を実行することなくビューを返してくれるわけです。renderで入力内容が保持される訳
先程の説明を踏まえてもう一度コードを見てみましょう。
shops_controller.rbdef new @shop = Shop.new end def create @shop = @owner.shops.new(shop_params) if @shop.save redirect_to :index else render :new end endnewアクションはお店情報の新規登録ページですので、アクション内で@shop = Shop.newを定義しています。(空のインスタンスを生成)
この後フォームに入力した値がparamsとして渡され、それがcreateアクションで今回作成した@shopの情報としてうまく渡されることで保存ができるわけです。保存に失敗した場合(elseの場合)またフォームに戻す処理をする訳ですが、この時にredirect_toを使うと、再度newアクションが実行され@shopはまた空のインスタンスになってしまいます。
一方でrenderを使うとnewアクションを介さないため、@shopにはcreateアクションで受け取ったparamsの値が保持されたまま、再度フォームのビューを表示してくれる訳です。
new.html.haml.shop-wrapper = form_with model: [@owner, @shop], html: {class: "shopform"}, local: true do |f|上記はビューの抜粋ですが、フォームは@ownerと@shopを受け取って内容を表示しているため、@shopに中身がある状態であればその中身をちゃんと表示してくれる訳です。
以上がcreateアクションに失敗した時に、renderを使うべき理由になります。
今回遭遇したエラー
ちなみに今回はrenderを設定した際、最初以下のエラーに遭遇しました。
一瞬なんだこれってなったんですが、newアクションに対して以下のbefore_acitonを設定してたんですよね。
shops_controller.rbbefore_action :set_select_lists, only: [:new] # 中略 private def set_select_lists @stations = Station.all.map {|station| [station.name, station.id] }.unshift(["以下から選んでください", nil]) @genres = Genre.all.map {|genre| [genre.name, genre.id] }.unshift(["以下から選んでください(必須)", nil]) @marks = Mark.all.map {|mark| [mark.favorite, mark.id] }.unshift(["以下から選んでください(必須)", nil]) endフォームの中にあるプルダウンの選択リストを作り、before_actionで設定していたんです。
先ほども書いた様に、renderはnewアクションを再度経由しないため、before_actionも行わない→設定された値がなくビュー表示でエラーが起きるという流れだったわけです。
これを受け一瞬、「@shop意外に定義しなきゃいけない変数があったらrender使えないの?」って思ったんですが、数分考えてすぐ解決しました。
createアクションを以下の様に書き換えます。
shops_controller.rbdef create @shop = @owner.shops.new(shop_params) if @shop.save else set_select_lists render :new end endcreateアクションでrenderする前に、before_actionで行っていたset_select_listsを行わせてあげることで、その値も取得した状態でrenderすることが出来ます(renderはあくまでcreateアクションを行った上でnewのビューを表示しているため)
一瞬???となったんですがそんな複雑な話でもなかったですね。
終わりに
実はredirect_toとrenderの挙動は、Progateで初めてプログラミングに触れ1ヶ月程度で一番悩みぬいた所だったんですよね。ここの違いに気づいた時にMVCへの理解も同時に深まったというイメージがありました。
僕と同じ様な初学者の方に何かの参考になればと思います。
*初学者ゆえ、何かあればご指摘いただけると大変ありがたいです。
- 投稿日:2020-06-26T21:11:41+09:00
Kinx Tips - UTF8 文字列のフォーマッティング
Kinx Tips - UTF8 文字列のフォーマッティング
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。Kinx で実装したちょっとした小技を紹介。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
そう、ちょっとした小技。
UTF8 文字列のフォーマッティングの問題
C 言語の printf
UTF8 はバイト数と文字幅が一致しないので、"%-20s" とかしても微妙に揃いませんよね。半角カナも試しに使ってみましょう。
#include <stdio.h> int main() { struct fruits { char *name; int price; } fruits[] = { { .name = "りんご", .price = 230 }, { .name = "みかん(1袋)", .price = 450 }, { .name = "グレープフルーツ", .price = 120 }, }; printf("01234567890123456789\n"); for (int i = 0; i < sizeof(fruits)/sizeof(fruits[0]); ++i) { printf("%-20s ... %3d 円\n", fruits[i].name, fruits[i].price); } return 0; }結果はこうなります。
01234567890123456789 りんご ... 230 円 みかん(1袋) ... 450 円 グレープフルーツ ... 120 円基本的にバイト数なので、日本語 3 バイトであれば 3 消費します。したがって幅が縮みます。
Ruby の sprintf
C と同じかと思いきや、違います。
fruits = [ ["りんご", 230], ["みかん(1袋)", 450], ["グレープフルーツ", 120], ] puts "01234567890123456789" fruits.each {|name, price| puts sprintf("%-20s ... %3d 円", name, price) }結果はこうなりました。
01234567890123456789 りんご ... 230 円 みかん(1袋) ... 450 円 グレープフルーツ ... 120 円バイト数ではなく文字数です。日本語も(何バイトであろうと) 1 文字で 1 消費します。なので、表示幅が増えます。文字数なので、半角カナだと実は一致します。
Kinx のフォーマッタ
Kinx では UTF8 の文字幅を確認してフォーマット文字列の数値部分を自動調整します。表示幅として機能します。
var fruits = [ ["りんご", 230], ["みかん(1袋)", 450], ["グレープフルーツ", 120], ]; System.println("01234567890123456789"); fruits.each(&(e) => { System.println("%-20s ... %3d 円" % e[0] % e[1]); });結果はこうなります。
01234567890123456789 りんご ... 230 円 みかん(1袋) ... 450 円 グレープフルーツ ... 120 円Qiita のコードブロックでは等幅で表示されていない感じなので微妙ですが、等幅フォントで見れば縦ラインはきちんと揃っています。東アジア文字幅(East Asian Width)に従って調整するので概ねうまく動くはず 1。
多くの人が期待する動作は コレ ではないでしょうか。
おわりに
なかなか忙しくて記事が書けないとき用に、こういった小ネタもストックしておこう。
ではまた、次回。
- 投稿日:2020-06-26T19:26:53+09:00
'%02d' % 2 のパーセントとは?
'%02d' % 2
ruby で 数字の 2 を '02' と記述したい時、
'%02d' % 2
のような構文を書きますよね。
引数に対して 0埋めをおこなってstringで返すという方法ですが、こちらの % とか 02 とか d って一体なんなんでしょうか?% って何?
ruby では % という記法に様々な意味があります。
- 剰余を表す「%」
例: 5 % 3 => 2
- Stringクラスの「%」演算子
例: "%02d" % 2 => "02"
- リテラル記法の「%」演算子
例: %w(dog cat monkey) => ["dog", "cat", "monkey"]
- コマンドラインへの入力を示す
例: %x[ DATE ] => "2020年 6月24日 水曜日 09時31分57秒 JST\n"
上記のような種類があります。
参考: Rubyで使われる記号の意味(正規表現の複雑な記号は除く)先に示した、
'%02d' % 2
という構文は、上記の例でいうと2. Stringクラスの「%」演算子
にあたりますね!02 とか d って一体なんなんでしょうか?
d は引数の型を示す「指示子」の中の一つです。
d は 10進数の整数 (Decimal number) を表しています。
指示子は他にも、文字列を表すものや浮動小数点を表すもの、指数表現を表すものなどがあります。02 は 0 と 2 に分解することができ、それぞれ「フラグ」と「幅」を示しています。
「フラグ」にもいくつか種類があるのですが、 0 は余白を 0で埋めるという意味になります。
「幅」はそのまま、出力時の長さを指定します。これらをまとめると、
'%02d' % 2
という記法は、
'%0(余白は0で埋めてくださいね〜)2(出力時の長さは2ですよ)d(引数を10進数の整数で表してくださいね' %(Stringクラスの%演算子ですよ〜) 2(引数は2ですよ!)
という意味が込められている事になります。
おわりに
引数は配列で渡す事もできますので、
「10進数で表している色コードを16進数の色コードに変換したいよ」という時には、
'%02x%02x%02x' % [255, 255, 255] => "ffffff"
と書く事ができます!
- 投稿日:2020-06-26T17:24:01+09:00
railsでgoogle認証を実装するときにハマったこと
今回は、自分がrailsでgoogle認証を実装するときにハマったことをまとめたので参考にでも。
自分がGoogle認証を実装するときに参考にした記事 ↓
[Rails] Facebook/Twitter/Googleでのユーザー登録をDevise & Omniauthを使って爆速で実装するエラー 400: invalid_request Missing required parameter: client_id
まず1つ目。グーグル認証のページでアカウントを選択肢して認証しようとしたときに出たエラーです。
client_idがないと言われました。
原因は2つあり
①スペースを入れていた
②.envを使わずそのままキーを入れていたこと①スペース
devise.rbconfig.omniauth :google_oauth2, ENV['GOOGLE_CLIENT_ID'] ,ENV['GOOGLE_CLIENT_SECRET'], skip_jwt: trueから
devise.rbconfig.omniauth :google_oauth2,ENV['GOOGLE_CLIENT_ID'],ENV['GOOGLE_CLIENT_SECRET'],skip_jwt: trueに変更しました。スペースがいらなかったみたいです。参考サイトではスペースはありました。
②.envを使わずそのままキーを入れていたこと
これは他のサイトを見る限りそのままでも行けた人がいるみたいですが僕の環境ではだめでした。
どうしても解決しない人はgem 'dotenv-rails'をいれてやってみてください。メール認証がされていません
その次に出たのがこのエラー
普通にやるとメール認証がされていませんと表示されてしまいます。これはqiita.rbclass Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController # callback for google def google_oauth2 callback_for(:google) end # common callback method def callback_for(provider) @user = User.from_omniauth(request.env["omniauth.auth"]) @user.skip_confirmation! ←これと @user.save! ←これ! if @user.persisted? sign_in_and_redirect @user, event: :authentication #this will throw if @user is not activated set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format? else session["devise.#{provider}_data"] = request.env["omniauth.auth"].except("extra") redirect_to new_user_registration_url end end def failure redirect_to root_path end end@user.skip_confirmation!
@user.save!
をコントローラーに入れることで解決しました。ぷち宣伝
公開日記というアプリを作りました。日記を公開できるアプリです(公開しないことも可)使っている人が僕しかいません(泣)。ほぼ毎日更新しているのでみてください。お願いします。
URL: https://public-diary.herokuapp.com/
まとめ
実は僕はいままで4回くらいsns認証を実装しようとし失敗しを繰り返しています。今回始めて出来たのですができたときはかなり嬉しかったです。ここまで見てくださりありがとうございました。
- 投稿日:2020-06-26T14:07:50+09:00
1つの投稿に対して複数枚の画像を紐付け、同時に投稿する実装方法
はじめに
複数枚の画像を投稿する機能の実装に、苦労したてめメモとして記事を投稿します
アプリを立ち上げる
$ rails _5.2.3_ new アプリ名 -d mysql $ cd アプリ名 $ bin/rails db:createhamlの導入
今回実装はhamlで行うため、hamlを導入します
gemファイルに以下を追加します
忘れずにbundle installをしてくださいgem 'haml-rails'簡単に投稿機能を作成します
routes
Rails.application.routes.draw do root 'products#index' resources :products, only: [:index, :new, :create] endcontroller
class ProductsController < ApplicationController def index @products = Product.includes(:images).order('created_at DESC') end def new @product = Product.new @product.images.new end def create @product = Product.new(product_params) if @product.save redirect_to root_path else render :new end end private def product_params params.require(:product).permit(:name, images_attributes: [:src]) end endmodelとマイグレーション
productマイグレーションファイル
class CreateProducts < ActiveRecord::Migration[5.2] def change create_table :products do |t| t.string :name t.timestamps end end endimageマイグレーションファイル
class CreateImages < ActiveRecord::Migration[5.2] def change create_table :images do |t| t.string :src t.references :product, foreign_key: true t.timestamps end end endproductsモデル
class Product < ApplicationRecord has_many :images accepts_nested_attributes_for :images, allow_destroy: true endimageモデル
class Image < ApplicationRecord mount_uploader :src, ImageUploader belongs_to :product endimageモデルで画像をアップロードできるようにしていきます。
Gemfileに以下を追記して$ bundle install
gem 'carrierwave' gem 'mini_magick'uploaderを作成します
ターミナルで以下を実行します
$ rails g uploader image続いて、image_uploader.rbファイルが生成されたので以下を編集します
include CarrierWave::MiniMagick // この記述を探し、コメントアウトを外す process resize_to_fit: [100, 100] // この記述は追記最後にhamlとscssの編集
haml/new
.lead =link_to "/products" do =image_tag "http://furima.tokyo/assets/logo-d3d78326971d78b06e3d6f0ba666d025a8ad681286b4d9e00e7dbe8673bcfd23.svg", class: "lead__img" = form_with model: @product, local: true do |f| .input-field .input-field__contents .input-field__contents-image .input-field__contents-image__headline .headlabel 出品画像 %span.necessary 必須 %p.upload 最大5枚までアップロードできます #image-box-1 .item-num-0#image-box__container = f.fields_for :images do |i| .input-field__contents-image__drop__js-file .input-area = i.file_field :src .input-field__contents-name .input-field__contents-image__headline .headlabel %label 商品名 %span.necessary 必須 .name-input = f.text_field :name, {class: "drop-input", placeholder: "40文字まで"} .input-field .input-field__contents .input-field__contents-price .sell = f.submit "出品する", class: "sellbtn", tabindex: "0"new.scss
.lead { background-color: rgb(245, 245, 245); text-align: center; height: 128px; line-height: 10; } .input-field { background-color: rgb(245, 245, 245); width: 100%; .input-field__contents { left: 0; background-color: white; max-width: 800px; margin: 0 auto; padding: 40px; border-bottom: 1px solid hsl(0, 0%, 77%); height: 100%; .input-field__contents-image { width: 800px; border-bottom: rgb(204, 204, 204); .input-field__contents-image__headline{ margin-top: 20px; margin-left: 5px; } .upload { margin-top: 16px; margin-left: 5px; } #image-box-1 { display: flex; height: 130px; width: 100%; margin-right: 0px; text-align: center; i{ padding-top: 50px; } .item-num-0#image-box__container { background-color: rgb(245, 245, 245); height: 100%; width: 100%; border-width: 1px; border-style: dashed; border-color: rgb(204, 204, 204); border-image: initial; text-align: center; } } } } .drop-input { width: 60%; height: 50px; border-color: #cccccc; border-radius: 4px; border-style: solid; border-width: 1px; margin: 10px 10px 0 0; } .name-input{ .drop-input{ width: 100%; height: 50px; border-color: #cccccc; border-style: solid; } } .sell { text-align: center; display: grid; width: 50%; margin-left: 200px; .sellbtn { background-color: #3ccace; color: white; border-color: transparent; font-weight: 600; line-height: 3; cursor: pointer; } } }以下の様になれば、完成です。
https://gyazo.com/a1d705516656f50c689abc7c18de5ec9画像を複数枚投稿する
jQueryの導入
gem 'jquery-rails'続いて、bundle installをしてください
application.jsの編集
//= require rails-ujs //= require activestorage //= require jquery //= require_tree .new.hamlの編集
-# 編集前 #image-box-1 .item-num-0#image-box__container = f.fields_for :images do |i| .input-field__contents-image__drop__js-file .input-area = i.file_field :src -# 編集後 #image-box-1 .item-num-0#image-box__container = f.fields_for :images do |i| .input-field__contents-image__drop__js-file .input-area = i.file_field :src, type: 'file', name: "product[images_attributes][][name]", value:"", style: "display:none", id:"img-file" %label{for: "img-file"} %i.fas.fa-camera画像を複数枚投稿する様にする new.js作成し、編集する
new.js
$(function(){ //DataTransferオブジェクトで、データを格納する箱を作る var dataBox = new DataTransfer(); //ステップ② //querySelectorでfile_fieldを取得 var file_field = document.querySelector('input[type=file]') //fileが選択された時に発火するイベント $('#img-file').change(function(){ //選択したfileのオブジェクトをpropで取得 var files = $('input[type=file]').prop('files')[0]; $.each(this.files, function(i,file){ //FileReaderのreadAsDataURLで指定したFileオブジェクトを読み込む var fileReader = new FileReader(); //DataTransferオブジェクトに対して、fileを追加 dataBox.items.add(file) //ステップ② //dataTransferオブジェクトに入ったfile一覧をfile_fieldの中に代入 file_field.files = dataBox.files //ステップ② var num = $('.item-image').length + 1 + i //ステップ② fileReader.readAsDataURL(file); //ステップ② //画像が10枚になったら超えたらドロップボックスを削除する if (num == 5){ //ステップ② $('#image-box__container').css('display', 'none') } //読み込みが完了すると、srcにfileのURLを格納 fileReader.onloadend = function() { var src = fileReader.result var html = `<div class='item-image' data-image="${file.name}"> <div class=' item-image__content'> <div class='item-image__content--icon'> <img src=${src} width="150" height="90" > </div> </div> <div class='item-image__operetion'> <div class='item-image__operetion--delete'>削除</div> </div> </div>` //image_box__container要素の前にhtmlを差し込む $('#image-box__container').before(html); }; // fileReader.readAsDataURL(file); // }); //image-box__containerのクラスを変更し、CSSでドロップボックスの大きさを変えてやる。 $('#image-box__container').attr('class', `item-num-${num}`) }); }); $(document).on("click", '.item-image__operetion--delete', function(){ //プレビュー要素を取得 var target_image = $(this).parent().parent() //プレビューを削除 target_image.remove(); //inputタグに入ったファイルを削除 file_field.val("") }) });scssの編集
.lead { background-color: rgb(245, 245, 245); text-align: center; height: 128px; line-height: 10; } .input-field { background-color: rgb(245, 245, 245); width: 100%; &__contents { left: 0; background-color: white; max-width: 800px; margin: 0 auto; padding: 40px; border-bottom: 1px solid hsl(0, 0%, 77%); height: 100%; } .input-field__contents-image { width: 800px; border-bottom: rgb(204, 204, 204); .input-field__contents-image__headline{ font-weight: 600; margin-top: 20px; margin-left: 5px; .name-input { height: 54px; .option-input { display: block; width: 93%; border-color: #cccccc; height: 100%; border-radius: 4px; font-weight: bolder; padding: 0px 2px 1px; border-width: 1px; } } } .upload { margin-top: 16px; margin-left: 5px; } #image-box-1 { display: flex; height: 130px; width: 100%; margin-right: 0px; text-align: center; i{ padding-top: 50px; cursor: pointer; } .item-num-0#image-box__container { background-color: rgb(245, 245, 245); height: 100%; width: 100%; border-width: 1px; border-style: dashed; border-color: rgb(204, 204, 204); border-image: initial; text-align: center; } .item-num-1{ background-color: rgb(245, 245, 245); height: 100%; width: 100%; border-width: 1px; border-style: dashed; border-color: rgb(204, 204, 204); border-image: initial; text-align: center; } .item-num-2{ background-color: rgb(245, 245, 245); height: 100%; width: 100%; border-width: 1px; border-style: dashed; border-color: rgb(204, 204, 204); border-image: initial; text-align: center; } .item-num-3{ background-color: rgb(245, 245, 245); height: 100%; width: 100%; border-width: 1px; border-style: dashed; border-color: rgb(204, 204, 204); border-image: initial; text-align: center; } .item-num-4{ background-color: rgb(245, 245, 245); height: 100%; width: 100%; border-width: 1px; border-style: dashed; border-color: rgb(204, 204, 204); border-image: initial; text-align: center; } .item-num-5{ background-color: rgb(245, 245, 245); height: 100%; width: 100%; border-width: 1px; border-style: dashed; border-color: rgb(204, 204, 204); border-image: initial; text-align: center; } } //レビュー表示のCSS .item-image{ height: 130px; width: 160px; border: 1px solid #eee; margin-right: 10px; .item-image__content{ padding-top: 10px; .item-image__content--icon{ } } .item-image__operetion{ .item-image__operetion--delete{ color: #00b0ff; cursor: pointer; padding-top: 5px; text-align: center; } } } } .text-area { border-radius: 4px; font-size: 16px; padding: 13px 16px; border-color: #cccccc; margin-top: 30px; } .drop-input { width: 100%; height: 50px; border-color: #cccccc; border-radius: 4px; border-style: solid; border-width: 1px; margin: 10px 10px 0 0; ::placeholder { padding: 20px; font-weight: inherit; } } .headlabel { margin-top: 30px; .necessary { background-color: #3ccace; color: white; padding: 2px 4px; font-size: 14px; margin-left: 3px; cursor: pointer; border-radius: 2px; } } .sell { text-align: center; display: grid; width: 50%; margin-left: 200px; .sellbtn { background-color: #3ccace; color: white; font-size: 20px; min-height: 48px; padding: 0 24px; border-color: transparent; border-radius: 2px; font-weight: 600; line-height: 3; } } }これで一通りは完成
動作を確認
https://gyazo.com/372657130a2696e1865c02fdd6e9e303編集や削除の機能は別記事で続きを書きます
- 投稿日:2020-06-26T13:02:24+09:00
rubyでredmineのAPIkeyを取得してみる
経緯
仕事でrubyを使ってredmineのAPIkeyを取得できるようにしろと仰せつかった。
ひとまずの完成を見たので、自分の備忘録代わりにまとめておく。
記事としては独学で勉強しただけでプログラマに就職した社会人一年目初心者プログラマが検索とコピペを繰り返して作ったものでしかない。もし自分と同じような人間がいたら今度はこの記事だけで一発成功することを祈って記述する。方法
まずは検索して見たが、該当する記述は見つからなかった。APIkeyはredmineの個人設定画面から受け取ってコードに入力するのが前提で、APIkeyをわざわざプログラムで取得するなどという方法は全然見つからなかった。が、rubyという条件を除外して検索した結果、以下の記事を発見した。
RedmineのJavaScriptから各種データを取得する方法
https://qiita.com/forenoonM/items/7f42701b2ea40353a820この記事内にはJavaScriptを使ってAPIkeyを取得する方法が記述されている。ざっくりいえばスクレイピング を使った方法だ。
スクレイピング 実践
なるほど、スクレイピング か。さて、恥ずかしながら私はスクレイピング がなんたるかすら知らなかった。なにせrubyを独学で勉強していただけの初心者なので。
というわけで、rubyでスクレイピング する方法を検索し、以下のサイトにたどり着いた。【5分で理解】Rubyでスクレイピングの基礎を解説!
https://www.sejuku.net/blog/13230こうして作ったコードがこれ
equire 'open-uri' require 'nokogiri' #APIキー取得に使う url = "http://192.168.33.11/my/api_key" charset = nil html = open(url) do |page| charaset = page.charset page.read end contents = Nokogiri::HTML.parse(html,nil,charset) apikey = contents.search("div[@class='box']").search('pre') puts apikeyさて、実際に動かすと動かない。
色々調べた結果、ログイン画面で止まっていることに気づいた。という事はログインの処理を作らねばならない
ログイン処理作成
で、例によって検索でたどり着いたのがこのページ。
Mechanizeでログインしてスクレイピングする
https://qiita.com/katsuyuki/items/1a78360988d96eec1d54なるほど完全に理解した。
agent = Mechanize.new agent.user_agent = 'Mac Safari' agent.get("http://#{adress}/my/api_key") do |page| response = page.form_with(:action => '/login') do |form| formdata = { :mail => mail, :password => password, } form.field_with(:name => 'username').value = formdata[:mail] form.field_with(:name => 'password').value = formdata[:password] end.submit end url = "http://#{adress}/my/api_key" html = agent.get("#{url}").content.toutf8 contents = Nokogiri::HTML(html, nil, 'utf-8') apikey = contents.search("div[@class='box']").search('pre') puts apikey追加部分だけを抜き出した。
ちなみに#{adress}については「今はローカルでテストしているが、この後会社のredmineとクライアントのredmineでも動かすんだから」と
require 'yaml' #コンフィグを読むのに使う set = YAML.load_file("config.yml") puts set adress = set["train"]["adress"] puts adressこんな感じでconfig.ymlにアドレスを入力することで指定できるようにしたものだ。
結果
> <pre>[APIkey]</pre>これではダメだ。
stringkey = "#{apikey}" stringkey.slice!("</pre>") stringkey.slice!("<pre>") puts "APIKey = #{stringkey}"これを直後に加えて、ひとまずAPIkeyを取得する処理は完成。
もしログイン済みだったら?
実はこっちを先に取り掛かっていたのだが、もしログイン画面ではなかったらどうだろう? そんなことがあるのかわからないが、もしあったら逆にログインする処理でエラーを起こしてしまう。
page = RedmineController.new pagetitle = page.Myaccount_access(adress) puts "pagetitle = #{pagetitle}" if pagetitle == "個人設定 - redmine" then # ログイン済みかどうかチェックする apikey = page.GetAPIkey(adress) puts "APIKey = #{apikey}" else apikey = page.Login(adress,mail,password) endログイン後の個人設定画面はタイトルが「個人設定 - redmine」になっていたので、これに対応した。
ここでRedmineContorollerなるクラスが作られているが、このクラスについては後述する。
この後、会社のredmineで試して気づいた。うちの会社のredmineは「個人設定 - [会社名]redmine 」になっている。
調べたところ、トップページなども全て「[会社名]redmine」だ。つまり、「個人設定 - [ページ名]」というスタイルのようだ。対応しよう。page = RedmineController.new toptitle = page.toptitle(adress) pagetitle = page.Myaccount_access(adress) puts "pagetitle = #{pagetitle}" if pagetitle == "個人設定 - #{toptitle}" then # ログイン済みかどうかチェックする apikey = page.GetAPIkey(adress) puts "APIKey = #{apikey}" else apikey = page.Login(adress,mail,password) endこうだ。つまり、トップのタイトルを先に把握しておく作戦だ。
さて、ではクラス「RedmineController」を公開する。そう、toptitleメソッドのことを紹介しておかないと二度手間なので紹介した次第である。class RedmineController def toptitle(adress) url = "http://#{adress}/" charset = nil html = open(url) do |page| charaset = page.charset page.read end contents = Nokogiri::HTML.parse(html,nil,charset) return contents.title end def Myaccount_access(adress) url = "http://#{adress}/my/account" charset = nil html = open(url) do |page| charaset = page.charset page.read end contents = Nokogiri::HTML.parse(html,nil,charset) return contents.title end def Login(adress,mail,password) agent = Mechanize.new agent.user_agent = 'Mac Safari' agent.get("http://#{adress}/my/api_key") do |page| response = page.form_with(:action => '/login') do |form| formdata = { :mail => mail, :password => password, } form.field_with(:name => 'username').value = formdata[:mail] form.field_with(:name => 'password').value = formdata[:password] end.submit end url = "http://#{adress}/my/api_key" html = agent.get("#{url}").content.toutf8 contents = Nokogiri::HTML(html, nil, 'utf-8') apikey = contents.search("div[@class='box']").search('pre') stringkey = "#{apikey}" stringkey.slice!("</pre>") stringkey.slice!("<pre>") puts "APIKey = #{stringkey}" return stringkey end def GetAPIkey(adress) url = "http://#{adress}/my/api_key" charset = nil html = open(url) do |page| charaset = page.charset page.read end contents = Nokogiri::HTML.parse(html,nil,charset) apikey = contents.search("div[@class='box']").search('pre') stringkey = "#{apikey}" stringkey.slice!("</pre>") stringkey.slice!("<pre>") puts "APIKey = #{stringkey}" return stringkey end endそして完成へ
で、この後APIを使ってなんやかんやする処理を作る必要があるので、APIkeyを得るための処理はメソッドに押し込もう。
class Getter def APIKey_Getter(adress,mail,password) page = RedmineController.new toptitle = page.toptitle(adress) pagetitle = page.Myaccount_access(adress) puts "pagetitle = #{pagetitle}" if pagetitle == "個人設定 - #{toptitle}" then # ログイン済みかどうかチェックする apikey = page.GetAPIkey(adress) puts "APIKey = #{apikey}" else apikey = page.Login(adress,mail,password) end return apikey endちなみにこの後、今はこのゲッタークラスに各種APIを呼び出すメソッドを追加している。
全容
以上で持って完成。全容はこんな感じである。
require 'open-uri' require 'nokogiri' #APIキー取得に使う require 'mechanize' #ログインに使う require 'yaml' #コンフィグを読むのに使う require 'io/console' #パソワードの伏字に使う class RedmineController def toptitle(adress) url = "http://#{adress}/" charset = nil html = open(url) do |page| charaset = page.charset page.read end contents = Nokogiri::HTML.parse(html,nil,charset) return contents.title end def Myaccount_access(adress) url = "http://#{adress}/my/account" charset = nil html = open(url) do |page| charaset = page.charset page.read end contents = Nokogiri::HTML.parse(html,nil,charset) return contents.title end def Login(adress,mail,password) agent = Mechanize.new agent.user_agent = 'Mac Safari' agent.get("http://#{adress}/my/api_key") do |page| response = page.form_with(:action => '/login') do |form| formdata = { :mail => mail, :password => password, } form.field_with(:name => 'username').value = formdata[:mail] form.field_with(:name => 'password').value = formdata[:password] end.submit end url = "http://#{adress}/my/api_key" html = agent.get("#{url}").content.toutf8 contents = Nokogiri::HTML(html, nil, 'utf-8') apikey = contents.search("div[@class='box']").search('pre') stringkey = "#{apikey}" stringkey.slice!("</pre>") stringkey.slice!("<pre>") puts "APIKey = #{stringkey}" return stringkey end def GetAPIkey(adress) url = "http://#{adress}/my/api_key" charset = nil html = open(url) do |page| charaset = page.charset page.read end contents = Nokogiri::HTML.parse(html,nil,charset) apikey = contents.search("div[@class='box']").search('pre') stringkey = "#{apikey}" stringkey.slice!("</pre>") stringkey.slice!("<pre>") puts "APIKey = #{stringkey}" return stringkey end end class Getter def APIKey_Getter(adress,mail,password) page = RedmineController.new toptitle = page.toptitle(adress) pagetitle = page.Myaccount_access(adress) puts "pagetitle = #{pagetitle}" if pagetitle == "個人設定 - #{toptitle}" then # ログイン済みかどうかチェックする apikey = page.GetAPIkey(adress) puts "APIKey = #{apikey}" else apikey = page.Login(adress,mail,password) end return apikey end end #ここから処理開始。実際にはsinatraでgetかpostで情報を受け取る puts "アカウント名を入力してください" mail = gets mail.chomp! puts "パスワードを入力してください(表示されませんが入力できています)" password = STDIN.noecho(&:gets) password.chomp! #実際にはgetsを使って取得する訳ではないが、一応設定。最終的にはこの範囲は全て消える set = YAML.load_file("config.yml") puts set adress = set["train"]["adress"] puts adress f = Getter.new apikey = f.APIKey_Getter(adress,mail,password) puts "APIKey = #{apikey}"以上、お目汚し失礼しました。
- 投稿日:2020-06-26T12:57:19+09:00
Rubyで無限に繰り返し処理をする方法
eachやtimesを使用すると繰り返し処理を始める前に、何回目で終わるか決めなければならない。loopを使えば、無限ループができる
loop do 繰り返し処理 end
- 投稿日:2020-06-26T11:43:13+09:00
【Ruby】if文の問題
背景
Rubyの問題を解いた際、自分の回答に対して、模範回答のスマートさを見て悔しかったので備忘録として残します。
問題
正の整数を入力します。その整数が、
10の倍数(10,20,30...)からの差が
2以内であるときはTrue
それ以外はFalseと出力するメソッドを作りましょう。出力例:
near_ten(12)→True
near_ten(17)→False
near_ten(19)→True私の回答
Rubydef near_ten(num) if num % 10 <= 2 puts "True" elsif num % 10 >= 8 puts "True" else puts "False" end end模範回答
Rubydef near_ten(num) quotient = num % 10 if quotient <= 2 || quotient >= 8 puts "True" else puts "False" end end感想
模範回答見たときに、スマートすぎやろぉ〜と心の中で叫びました!(出先のもので声出せません)
確かに、またはに該当する" || "の存在を忘れがち。。。。
コードは、正直動けば正解ではありますが、いかにスマートに書くかでカッコ良さがにじみ出るのだと改めて感じました。。。
リファクタリング頑張りやす。。。
- 投稿日:2020-06-26T10:23:15+09:00
VScodeを初めて使うRailsエンジニアへ
はじめに
エディタをVScodeに変更したRailsエンジニアのために、作業が捗るVScodeの設定をまとめてみました。
VScodeには非常に便利な拡張機能がありますが、意図しない設定まで入るブラックボックスなので、設定ファイルをカスタマイズしました。実行環境
この記事は以下の動作環境で動作確認しています。
ruby (2.7.1)
rails (6.0.3)
Vscode
MacBook Pro1. どうやってカスタマイズするの?
VScodeをインストール時に、メジャーな言語の設定ファイルを既に入っており、言語ごとの設定は専用のファイルで設定します。(Rubyの場合はruby.json)
言語に依存しない設定はsettings.jsonで設定します。1-1. 設定ファイル(setting.json)の開き方
VScode上でCommand + ,と打つと設定画面が出てきます。
右上にいくつかアイコンがあり、設定を開く(JSON)とあるのでクリックするとsettings.jsonが開きます。
1-2. 設定ファイル(ruby.json)の開き方
VScode上でCommand + Shift + Pと打つとコマンドパレットが出てきます。
User snippetsで検索して出てきたConfigure User Snippetsを選択して、
次に出てきたウィンドウでrubyと検索して出てきたruby.jsonを選択すると設定が開きます。
2. 設定内容
2.1 拡張機能のRubyをインストール
カスタマイズと言いましたが、Rubyの拡張機能だけは入れてください?
VScodeの左側にある拡張機能のアイコンをクリックし、Rubyで検索して一番上にある拡張機能をインストールを押すだけでOKです。2.2 pryと打つとbinding.pryと変換する
Railsで開発していると誰もが使用するbinding.pry(デバッグ)。ただ毎回これを全て打つのは面倒&タイポするので予測変換の設定をします。
設定方法は簡単で、ruby.jsonに下記を記載するだけです。"prefix"の値が変換前の文字、"body"が予測変換したい文字です。
もし"prefix"の値を"pry"から"bin"に変更すると"bin"と打つとbinding.pry に変換されます。settings.json"Debug Console": { "prefix": "pry", "body": "binding.pry\n", "description": "A runtime developer console" }画像に alt 属性が指定されていません。ファイル名: binding_pry.gif
2.3 HTMLのタグを自動補完する
RailsでViewのコードを書くにはhtml.erbファイルを使用します。
などのタグを全て打ちたくないので、div + tabキー でタグを補完してくれるようにします。下記の設定を
settings.jsonに追加します。settings.json{ "emmet.includeLanguages": { "erb": "html", }, }2.4 関数の定義元へジャンプ
Commandを押しながら関数をクリック、定義元にジャンプできる非常に便利な設定です。
Command + Shift + P でコマンドパレットを開き、keyboardで出てくるkeybinding.jsonに下記を記載します。keybinding.json[ { "key": "cmd+d", "command": "editor.action.goToDeclaration", "when": "editorTextFocus" } ]2.5 ターミナルからVScodeを開く
Command + Shift + P でコマンドパレットを開き、shellで出てくる shell command:install 'code' command in PATH をクリック。
ターミナルでcode . と打つとカレントディレクトリのファイルを開くことができます。2.6 末尾の改行と空白を自動で削除
末尾に改行がないものには改行を追加。末尾の不要な改行・空白は自動で削除する設定です。
Command ,で設定を開き、trimで検索して全ての項目にチェックをする。画像に alt 属性が指定されていません。ファイル名: 末尾改行・空白-1024x430.png
おわりに
他にもいろいろな設定があるので、好みに合わせて、拡張機能を追加したり、settings.jsonに設定を追加したりしてみてください。参考までに私のsettings.jsonを添付しておきます。
右端で折り返す、スクロールを止める、オートセーブ当たりも入れておいた方が快適になると思います。setting.json{ "files.autoSave": "onFocusChange", //ファイルのオートセーブ "editor.formatOnSave": true, "editor.tabSize": 2, //1つのタブに相当するスペースの数 "editor.detectIndentation": false, "emmet.variables": {}, "emmet.triggerExpansionOnTab": true, "ruby.rubocop.onSave": false, "html.format.endWithNewline": true, "html.format.indentInnerHtml": true, "emmet.includeLanguages": { "erb": "html", }, "window.zoomLevel": 0, "ruby.intellisense": "rubyLocate", "diffEditor.renderSideBySide": false, // Git の差分を行内に表示 "editor.wordWrap": "on", // 右端で折り返す "editor.minimap.renderCharacters": false, // ミニマップを簡略化 "editor.links": true, // リンクをクリック可能に "indentRainbow.colors": [ "rgba(255,255,64,0.07)", "rgba(127,255,127,0.07)", "rgba(255,127,255,0.07)", "rgba(79,236,236,0.07)" ], "editor.scrollBeyondLastLine": false, //下限でスクロールをストップさせる "editor.formatOnPaste": true, // ペースト時に自動でフォーマット "breadcrumbs.enabled": true, "files.trimTrailingWhitespace": true, "files.insertFinalNewline": true, "files.trimFinalNewlines": true, // ファイルの保存時に末尾を改行 // Reactの設定 "emmet.includeLanguages": { "javascript": "javascriptreact", "vue-html": "html" // }, "emmet.syntaxProfiles": { "html": { "attr_quotes": "single" }, "jsx": { "self_closing_tag": true } } } }
- 投稿日:2020-06-26T09:20:57+09:00
懐かしのコードゲーム、Ruby Warrior はいいぞ
Ruby Warrior
- 勇者を
ruby
コードで操って、レベルをクリアしていくゲーム- オリジナルは
Rogue
みたいな見た目のテキストゲームでryanb
さん作。10年くらい前に公開- オリジナルを Bloc が Web ゲームにしたようだ。 Good Job!
ルール
- コードの中の
def play_turn(warrior) ... end
の部分が1ターンで、ここにコードを書く- ターンを繰り返して、敵を倒しながら、脱出すればよい
- 1ターンで1回しか
warrior.action!
できない(ターン内でループの処理もできない)1- レベルが進むごとにスキル
warrior.action!
が増えていく- 初心者モードと中級者モードがある(今回は初心者モードでやってみた)
Level 1
You see before yourself a long hallway with stairs at the end. There is nothing in the way.
class Player def play_turn(warrior) # cool code goes here warrior.walk! end endこのレベルの見所: 楽しく歩く
Level 2
It is too dark to see anything, but you smell sludge nearby.
class Player def play_turn(warrior) if warrior.feel.empty? warrior.walk! else warrior.attack! end end endこのレベルの見所: 初めての敵
warrior.attack!
を獲得Level 3
The air feels thicker than before. There must be a horde of sludge.
class Player def play_turn(warrior) if warrior.feel.empty? if warrior.health < 15 warrior.rest! else warrior.walk! end else warrior.attack! end end endこのレベルの見所: 休む時は休む
warrior.rest
を獲得Level 4
You can hear bow strings being stretched.
class Player def play_turn(warrior) @health = warrior.health unless @health if warrior.feel.empty? if @health <= warrior.health && warrior.health < 12 warrior.rest! else warrior.walk! end else warrior.attack! end @health = warrior.health end endこのレベルの見所: 体調管理
warrior.health
を獲得Level 5
You hear cries for help. Captives must need rescuing.
class Player def play_turn(warrior) @health = warrior.health unless @health if warrior.feel.captive? warrior.rescue! elsif warrior.feel.empty? if @health <= warrior.health && warrior.health < 13 warrior.rest! else warrior.walk! end else warrior.attack! end @health = warrior.health end endこのレベルの見所: 敵か味方か?
Level 6
The wall behind you feels a bit further away in this room. And you hear more cries for help.
class Player def initialize() @captive = true end def play_turn(warrior) @health = warrior.health unless @health if @captive if warrior.feel(:backward).captive? warrior.rescue!(:backward) @captive = false else warrior.walk!(:backward) end else if warrior.feel.empty? if @health > warrior.health && warrior.health < 10 warrior.walk!(:backward) elsif @health <= warrior.health && warrior.health < 20 warrior.rest! else warrior.walk! end else warrior.attack! end @health = warrior.health end end endこのレベルの見所: ヒット & アウェイ
captive
がいるかどうかをフラグでもっておいてそれで処理を変えようとしていろいろうまくいかなかったが、最終的にinitialize()
に書いてうまくいったLevel 7
You feel a wall right in front of you and an opening behind you.
class Player def play_turn(warrior) @health = warrior.health unless @health if warrior.feel.wall? warrior.pivot! else if warrior.feel.empty? if @health > warrior.health && warrior.health < 10 warrior.walk!(:backward) elsif @health <= warrior.health && warrior.health < 20 warrior.rest! else warrior.walk! end else warrior.attack! end end @health = warrior.health end endこのレベルの見所: 振り向けば、いつかみた風景
warrior.pivot!
を獲得Level 8
You hear the mumbling of wizards. Beware of their deadly wands! Good thing you found a bow.
class Player def initialize() @captive = true end def play_turn(warrior) @health = warrior.health unless @health if @captive if warrior.feel.captive? warrior.rescue! @captive = false else warrior.walk! end else if warrior.look.any?{|s| s.enemy?} warrior.shoot! else warrior.walk! end end end endこのレベルの見所: 遠いもんがちー
warrior.look
とwarrior.shoot!
を獲得- ウィザードっぽいのが遠隔から一撃で
11
くらいライフ削ってくるので、二発くらうと死ぬ... でも意外とレンジは短いany?
のおかげでスッキリかけたのではないかLevel 9
Time to hone your skills and apply all of the abilities that you have learned.
class Player def initialize() @left_captive = true end def play_turn(warrior) if @left_captive if warrior.look(:backward).any?{|s| s.enemy?} warrior.shoot!(:backward) elsif warrior.feel(:backward).captive? warrior.rescue!(:backward) @left_captive = false else warrior.walk!(:backward) end else if warrior.look.any?{|s| s.enemy?} warrior.shoot! elsif warrior.feel.captive? warrior.rescue! elsif warrior.feel.wall? warrior.pivot! else warrior.walk! end end end endこのレベルの見所: ムーンウォーク
- めっちゃ弓だけに頼った勝ち方!後ろ向きで矢を放ち、一回も
warrior.rest!
しない無鉄砲野郎。学んだことを使っていない。こんなんでいいのか?Ruby Warrior になろう
もっといいコードがあったら教えてください!
Only one action can be performed per turn.
のエラーがでる ↩
- 投稿日:2020-06-26T05:38:31+09:00
[エラー]自動デプロイ実行時のrbenv: ruby 2.5.1 is not installedot〜に関して
はじめに
今回の投稿は私が自動デプロイを実行した時にエラーにハマったので、その内容を共有させていただきます。
エラー内容
[Deprecation Notice] Future versions of Capistrano will not load the Git SCM plugin by default. To silence this deprecation warning, add the following to your Capfile after `require "capistrano/deploy"`: require "capistrano/scm/git" install_plugin Capistrano::SCM::Git 00:00 rbenv:validate WARN rbenv: ruby 2.5.1 is not installed or not found in $HOME/.rbenv/versions/ruby 2.5.1 on 54.95.87.53仮説を立てる
1. require "capistrano/scm/git"
require〜という記述からCapfileに異常があると仮説。2. WARN rbenv: ruby 2.5.1 is not installed〜
ruby 2.5.1 is not installedという記述からruby2.5.1のインストールがされていない、またローカル環境とのverに差異があると仮説。怪しいのはdeploy.rbか...?仮設検証 Capfile
require "capistrano/setup" require "capistrano/deploy" require 'capistrano/rbenv' require 'capistrano/bundler' require 'capistrano/rails/assets' require 'capistrano/rails/migrations' require 'capistrano3/unicorn' require "capistrano/scm/git" install_plugin Capistrano::SCM::Git Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }特に異常なし。
仮設検証 ver確認
ローカル開発環境と本番環境(EC2)内でruby -vを実行したが問題なし。
仮設検証 deploy.rb
deploy.rb# config valid only for current version of Capistrano # capistranoのバージョンを記載。固定のバージョンを利用し続け、バージョン変更によるトラブルを防止する lock '3.14.1' # Capistranoのログの表示に利用する set :application, 'chat-space' # どのリポジトリからアプリをpullするかを指定する set :repo_url, 'git@github.com:ken-sasaki-222/chat-space.git' # バージョンが変わっても共通で参照するディレクトリを指定 set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system', 'public/uploads') set :rbenv_type, :user set :rbenv_ruby, 'ruby2.5.1' #ここに注目 # どの公開鍵を利用してデプロイするか set :ssh_options, auth_methods: ['publickey'], keys: ['~/.ssh/test_key.pem'] # プロセス番号を記載したファイルの場所 set :unicorn_pid, -> { "#{shared_path}/tmp/pids/unicorn.pid" } # Unicornの設定ファイルの場所 set :unicorn_config_path, -> { "#{current_path}/config/unicorn.rb" } set :keep_releases, 5 # デプロイ処理が終わった後、Unicornを再起動するための記述 after 'deploy:publishing', 'deploy:restart' namespace :deploy do task :restart do invoke 'unicorn:restart' end endrubyのverに問題はなさそう。と思いきや...
ver指定の箇所が'ruby 2.5.1'になっている!正しくは'2.5.1'でOK!
修正すると。deploy.rb#省略 set :rbenv_type, :user set :rbenv_ruby, '2.5.1' #ここに注目 #省略これでローカル環境のターミナルにてbndle exec cap production deployを実行すると
無事自動デプロイが走りました。
- 投稿日:2020-06-26T00:12:21+09:00
Rails6で lib ディレクトリの自作クラスを使用する
自作クラスを作成し利用する方法、
今回はroot/lib配下に作る方法をご紹介します。環境
rails 6.0.3
application.rbの設定
application.rbmodule Myapp class Application < Rails::Application config.load_defaults 6.0 config.paths.add 'lib', eager_load: true #これを追加 end endlib配下をautoload_pathsに追加します。
autoload_pathsについてはこちらlib配下の構成
lib ├── assets ├── asks └── utils └── hoge.rbutils/hoge.rbを作成しまいした。
分かりやすい命名にしましょう。hoge.rbの中身
hoge.rbclass Utils::Hoge endもしくは
hoge.rbmodule Utils class Hoge def greeting 'hello' end end end親ディレクトリの名称をネームスペースとして、クラスを定義しましょう。
こうすることでautoload_pathsの規則に乗っ取って以下のように呼び出せます。application_controller.rbclass ApplicationController < ActionController::Base hoge = Utils::Hoge.new hoge.greeting #=> hello end最後に
この記事を書いてる際以下のような投稿を発見しました。
(リサーチ不足が露呈した、、、)Rails Guaidによると
「Rails5より前ではlib/以下をautoload_pathsに含めることがよく行われていたが、現在はappフォルダ以外の場所にコードを置いてautoload対象にするのは勧めない」となっています。
本番環境特有のエラーが発生するかもしれないとのこと。app配下はautoload_pathsにデフォルトで入っているので、
そちらを使うのが良さそう。じゃあなんでlibディレクトリは存在してるんだ?
開発上手な人はうまく使い分けているのかな。新たな疑問が生まれたところで、今回は以上です。
日々精進