- 投稿日:2020-08-03T20:25:28+09:00
active_hashを使って疑似モデルを作ろう 〜都道府県データ〜
某メルカリコピーサイト作成中
active_hash
データベースを作ることなく擬似的にモデルを作成しデータを入れることができる
とのことなので active_hash なるgemを使ってみます
まずGemfileにactive_hashを追加
gem 'active_hash'bundle install します
Model
でモデルファイルを作っていくんですが
今回は rails g model では作成しない様普段はApplicationRecordを継承してるけど、
active_hashを使用するモデルはActiveHash::Baseを継承する必要があるからみたいなので直接 app/models/ に作成していきます
prefecture.rbclass Prefecture < ActiveHash::Base endこんな感じで ActiveHash::Base を継承します
データの雛形はこんな感じ
prefecture.rbclass Prefecture < ActiveHash::Base self.data = [ ] endこの中にデータを入れていきます
prefecture.rbclass Prefecture < ActiveHash::Base self.data = [ {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'}, {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'}, {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'}, {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'}, {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'}, {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'}, {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'}, {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'}, {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'}, {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'}, {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'}, {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'}, {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'}, {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'}, {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'}, {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'} ] endこの様にハッシュ形式で入れていきます
次にアソシエーションproduct.rbclass product < ApplicationRecord extend ActiveHash::Associations::ActiveRecordExtensions belongs_to_active_hash :prefecture end今回はproductテーブルと紐付けるのでproduct.rbに記述します
通常はbelongs_toですがactive_hashの場合はbelongs_to_active_hashになります
prefecture.rbclass Prefecture < ActiveHash::Base self.data = [ {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'}, {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'}, {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'}, {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'}, {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'}, {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'}, {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'}, {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'}, {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'}, {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'}, {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'}, {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'}, {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'}, {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'}, {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'}, {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'} ] include ActiveHash::Associations has_many :users endprefecture側はこの様に
あとは表示させるだけ!!
- 投稿日:2020-08-03T20:13:46+09:00
AWS Elastic Beanstalk + Rails環境でnginxのconfがうまく反映されない場合
■方法
公式ドキュメントの通り、[.platform]配下にファイルを配置すれば反映されると思います。~/workspace/my-app/
|-- .platform
| `-- nginx
| `-- conf.d
| `-- myconf.conf
`-- other source files■公式ドキュメント
・Elastic Beanstalk Linux プラットフォームの拡張
https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/platforms-linux-extend.html■おまけ
どうして公式に記載されている内容をわざわざ投稿したのかというと、全然検索に引っかからないからです。
[413 Request Entity Too Large]エラー対応でconfを編集する必要があり、
調べると2通りの記述方法が引っかかりました。
- [.ebextensions]配下に[nginx]を置く
- [.ebextensions]配下に[01_XXX.config]みたいなファイルを置く
この二つを延々試してたんですがウンともスンとも言わず、調べても他の方法が全然引っかからず・・・
デプロイ中にドキュメントを眺めてたら上記の内容を見つけた次第です。JavaやGoの場合は[.ebextensions]に配置する方法が書かれており、
「Railsでも同じ形でいけるよ!!」って記事が上位に引っかかるのもハマりやすいポイントかと思います。(めちゃくちゃハマりました。)
・Increasing client_max_body_size in Nginx conf on AWS Elastic Beanstalk
https://stackoverflow.com/questions/18908426/increasing-client-max-body-size-in-nginx-conf-on-aws-elastic-beanstalk
- 投稿日:2020-08-03T19:40:26+09:00
フォロー機能(Ajax)実装
自分用にまとめます。
実装
モデル作成
$ rails g model Relationship follower_id:integer followed_id:integer $ rails db:migrate
マイグレーションファイル
20200723070930_create_relationships.rbclass CreateRelationships < ActiveRecord::Migration[5.2] def change create_table :relationships do |t| t.integer :follower_id, null: false t.integer :followed_id, null: false t.timestamps end end end
relationship.rbclass Relationship < ApplicationRecord # フォローするユーザー belongs_to :follower, class_name: 'User' # フォローされるユーザー belongs_to :followed, class_name: 'User' validates :follower_id, presence: true validates :following_id, presence: true endclass_name: 'User':「followerもfollowedもUserにいるよ」という意味
app/models/user.rbclass User < ApplicationRecord # フォロー取得 has_many :follower, class_name: 'Relationship', foreign_key: 'follower_id', dependent: :destroy # フォロワー取得 has_many :followed, class_name: 'Relationship', foreign_key: 'followed_id', dependent: :destroy # 自分がフォローしている人 has_many :following_user, through: :follower, source: :followed # 自分をフォローしている人(フォロワー) has_many :follower_user, through: :followed, source: :follower # ユーザーをフォローする def follow(user_id) follower.create(followed_id: user_id) end # ユーザーのフォローを外す def unfollow(user_id) follower.find_by(followed_id: user_id).destroy end # フォロー確認を行う def following?(user) following_user.include?(user) end end
コントローラ作成
$ rails g controller relationships
config/routes.rbRails.application.routes.draw do post 'follow/:id', to: 'relationships#follow', as: 'follow' post 'unfollow/:id', to: 'relationships#unfollow', as: 'unfollow' get 'users/following/:user_id', to: 'users#following', as: 'users_following' get 'users/follower/:user_id', to: 'users#follower', as: 'users_follower' end
users_controller.rbclass UsersController < ApplicationController # @userがフォローしているユーザー一覧 def following @user = User.find(params[:user_id]) @followings = @user.following_user end # @userをフォローしているユーザー一覧 def follower @user = User.find(params[:user_id]) @followers = @user.follower_user end end
relationship_controller.rbclass RelationshipsController < ApplicationController # フォローする def follow @user = User.find(params[:id]) current_user.follow(params[:id]) render :create end # アンフォローする def unfollow @user = User.find(params[:id]) current_user.unfollow(params[:id]) render :destroy end endrenderでcreate.js.erbとdestroy.js.erb呼び出すことで、
ページ遷移なしでフォロー・解除が行えるようになる。
users/show.html.slim# create.js.erbとdestroy.js.erbで書き換えるため記述必須 div id="follow_form" # フォローボタン部分をパーシャルで飛ばす = render 'relationships/follow', user: @user
_follow.html.slimファイルをrelationshipsディレクトリ配下に手動で作成
パーシャルで飛ばしたフォローボタン部分の記述を貼り付ける。relationships/_follow.html.slim- unless user == current_user - if current_user.following?(user) # jsファイルを呼び出すためにremote: trueを追記 = link_to unfollow_path(user), method: :post, remote: true, class: 'btn btn-outline-info m-0 btn-sm' do i.fas.fa-user フォロー中 - else # jsファイルを呼び出すためにremote: trueを追記 = link_to follow_path(user), method: :post, remote: true, class: 'btn btn-outline-success m-0 btn-sm' do i.far.fa-user フォローする
create.js.erbファイルをrelationshipsディレクトリ配下に手動で作成
relationships/create.js.erb# id="follow_form"部分を_follow.html.slimの内容にページ遷移なしで書き換える $('#follow_form').html("<%= j(render 'relationships/follow',{ user: @user }) %>");
destroy.js.erbファイルをrelationshipsディレクトリ配下に手動で作成
relationships/destroy.js.erb# id="follow_form"部分を_follow.html.slimの内容にページ遷移なしで書き換える $('#follow_form').html("<%= j(render 'relationships/follow',{ user: @user }) %>");完成!
- 投稿日:2020-08-03T17:59:13+09:00
Rails + postgres で jsonb の中の Array をクエリする方法
難しかったのでメモ
データの形
こんな感じのが jsonb のカラムの中に保存されている。
{ hoge: String, items: String[] }やりたいことと解決策
それでやりたいのは
items
の中に特定の文字列["a", "b", "c"]
のいずれかが含まれているものをクエリすること。結論としては次のようなコードでいけた。
items_to_query = ["a", "b", "c"] Model.where("jsonbColumnName -> 'items' @> ?", items_to_query.to_json)参考
https://stackoverflow.com/questions/35737931/rails-postgres-query-with-jsonb-array
https://www.postgresql.org/docs/9.4/functions-json.html
- 投稿日:2020-08-03T17:29:15+09:00
Graphql-rubyでのエラーハンドリング
はじめに
最近、graphql-rubyでAPIを実装しています。
そこで、graphqlでのエラーハンドリングについて、ベストプラクティスを模索してみました。
まだ始めたばかりなので、もっといい方法あるよ!っていうコメント募集中です。エラーの分類
エラーハンドリングの実装に移る前にエラーにはいくつか種類があるので、紹介します。
以下の記事が非常にわかりやすく、まとまっていたので参考にしています。Railsアプリケーションにおけるエラー処理(例外設計)の考え方
ここでは、以下のように異常系と準異常系の2つに分類します。
- 異常系:nwエラーや、プログラムのエラー(システムエラー)
- 準異常系:作り手が予期できるエラー(業務エラー)
この2種類のエラーに対して、以下のような要件があります。
- 異常系:フロント側でテンプレートメッセージを表示 & 開発者に通知
- 準異常系:エラー内容や回避方法をフロント側(この辺りは要件によって変わってくるかもしれません)
肝となるのは、異常系は決まったテンプレートのメッセージを表示するが、準異常系はAPIからメッセージを送るというところです。この要件をどのように実装させるかが本題となります。
Graphql-rubyの仕様
graphql-rubyでは、エラーの場合以下のようなerrorsをkeyにしたjsonが返ってきます。
"errors": [ { "message": "error message", "locations": [], "extensions": { "code": "ERROR_CODE" } } ]このエラーメッセージは、主にインターフェイスでバグがあったりすると発生します。
例えばフロント側で渡す引数にnullが入ってるとき等です。上のようなエラーは以下のように直接コードを書いて発生させることもできます。
sample.rubyraise GraphQL::ExecutionError.new('error message', extensions: {code: "ERROR_CODE"})実装
上記の仕様から異常系のエラーはerrorsが戻り値で返ってくることがわかります。
では準異常系はどうでしょうか?準異常系の要件は以下です。
- 画面に表示するエラーメッセージを渡す
- フロントが準異常系エラーと判断できる
まず1つ目の要件は上記のgraphqlエラーをraiseする方法でエラーメッセージを渡せるのでokです。しかし、そのままではフロント側で異常系のエラーと区別ができません。
そこで以下のようにjsonをカスタマイズすることにしました。
sample.rubyraise GraphQL::ExecutionError.new('error message', extensions: {customCode: "ERROR_CODE"})code->customCodeとなっています。フロント側ではcustomCodeがkeyとして存在すれば、そのままerror messageを表示。それ以外はテンプレートのメッセージを表示させることで、異常系と準異常系の処理を切り分けることができます。
参考資料
- 投稿日:2020-08-03T16:59:04+09:00
コメント機能(Ajax)実装
自分用にまとめます。
実装
コントローラー・モデル作成済み
routes.erbresources :posts do resources :comments, only: [:create, :destroy] endコメントはpostsのshowページで投稿や一覧表示、削除など行うため、createとdestroyのみ作成。
comments_controller.rbclass CommentsController < ApplicationController def create @post = Post.find(params[:post_id]) @comment = @post.comments.new(comment_params) @comment.user_id = current_user.id @comments = @post.comments.all @comment.save render :create end def destroy @comment = Comment.find_by(id: params[:id], post_id: params[:post_id]).destroy @comment.destroy render :destroy end private def comment_params params.require(:comment).permit(:comment, :post_id) end endredirect_to などで遷移の記述部分を変更。
上記の場合、
createアクションの際は、create.js.erb
destroyアクションの際は、destroy.js.erb
に飛ぶようになっている。js.erbは後ほど作成するファイル。
posts_controller.rbclass PostsController < ApplicationController def show @post = Post.find(params[:id]) @comment = Comment.new @comments = @post.comments.all end end今回はposts/showでコメントの投稿をできるようにする
posts/show.html.slim# コメント一覧表示 .row .col-lg-4 .col-lg-4 # create.js.erbで書き換えるため記述必須 div id="comment-text" # コメント一覧部分をパーシャルで飛ばす = render 'comments/comment', comments: @comments # コメントフォーム .row .col-lg-3 .col-lg-6.text-center .frame-post-show # jsファイルを呼び出すためにlocal: trueの記述を外す = form_with(model: [@post, @comment]) do |f| = f.label :comment, class: "font-weight-bold col-lg-3 text-center" <br> = f.text_area :comment, :size=>"57x5" <br> .text-center= f.submit "コメントする", class: "btn btn-outline-secondary btn-sm"
_comment.html.slimファイルをcommentsディレクトリ配下に手動で作成
パーシャルで飛ばしたコメント一覧部分を貼り付ける。comments/_comment.html.slim- @comments.each do |comment| # destroy.js.erbで書き換えるため記述必須 div id="comment-#{comment.id}" .frame-post-comment = attachment_image_tag comment.user, :image, fallback: 'noimage.png', size: "50x40", class: "mb-3" = link_to comment.user.name, user_path(comment.user) - if comment.user == current_user # jsファイルを呼び出すためにremote: trueを追記 = link_to '削除', post_comment_path(comment.post, comment), method: :delete, remote: true, data: { confirm: "削除してよろしいですか?" }, class: "btn btn-outline-danger btn-sm m-0 ml-3" <br> small.m-0= comment.created_at.to_s(:datetime_jp) .mt-4.mb-2= comment.comment
create.js.erbファイルをcommentsディレクトリ配下に手動で作成
comments/create.js.erb# id="comment-text"部分を_comment.html.slimの内容にページ遷移なしで書き換える $('#comment-text').html("<%= j(render 'comments/comment',{ comments: @comments }) %>"); # f.text_areaを空にする(記述しないと、投稿した後もフォームにコメント内容が残ったままになる) $('textarea').val('');
destroy.js.erbファイルをcommentsディレクトリ配下に手動で作成
comments/destroy.js.erb# "comment-#{comment.id}"を持った投稿を非表示にする $('#comment-<%= @comment.id %>').remove();完成!
- 投稿日:2020-08-03T15:45:16+09:00
No template for interactive requestの対処法
アプリ作成中にNo template for interactive requestが発生した。
エラー
私の場合は
No template for interactive request
PagesController#index is missing a template for request formats: text/htmlであった。
routes.rbRails.application.routes.draw do resources :pages root 'pages#index' endpagescontrollerclass PagesController < ApplicationController def index end endビューファイルもしっかりapp/view/pagesにindex.html.hamlで作成していたので、原因がわからず解決できなかった。
原因
PagesController#index is missing a template for request formats: text/htmlとエラー文ではhtml文を呼んでいたが、自分はhamlファイルを生成してしまっていた。
解決法
ファイル名をindex.html.hamlからindex.html.erbに変更
これで解決する事ができた
hamlでのビューファイルを呼び出すために
投稿者はhamlを利用したかったが、hamlを使うには haml-railsというgemをインストールしなければならなかったらしい。
gemfilegem 'haml-rails'これで、hamlファイルを呼び出す事ができた。
反省
この解決だけで1時間ほど時間を食ってしまったのが辛かったので、もっと速い間隔で試行錯誤を行うことを心がける。
- 投稿日:2020-08-03T14:38:52+09:00
ターミナルでコマンドを実行した際にOperation not permitted が表示された場合の対処法
rails中級チュートリアルアプリ作成
の最中にトップページへのルーティング、コントローラー作成をし、rails sを実行しようとしたところ、
ターミナルOperation not permittedと、エラー文が表示されてしまった。
解決法
システム環境設定→セキュリティとプライバシー→プライバシー→左に並んでいる中からフルディスクアクセスを選択→ターミナルにチェックを入れる。
これでターミナルを再起動すると解決する事ができた。
参考サイト
- 投稿日:2020-08-03T14:16:42+09:00
rails sを実行したら Address already in use と表示された時の対処法
アプリ作成中にこのエラーに遭遇したため記録しておく
解決したい事
rails sを行うと
Address already in use - bind(2) for "127.0.0.1" port 3000 (Errno::EADDRINUSE)とターミナルに表示されてしまいrails sを実行できない
解決法
すでにrails sが他のターミナルなどで実行されているかを確認。
rails sが実行されているのが確認できなければ
ターミナル% ps ax | grep railsを打ち込み出てきたプロセス番号を
ターミナル% kill -9 プロセス番号
でプロセスを止めることで解決する事ができる。
- 投稿日:2020-08-03T12:21:46+09:00
docker rails db:migrateにてエラー(StandardError: An error has occurred, all later migrations canceled:)
Qiita初投稿となります。よろしくお願いします。
環境
- image: mysql:5.7
- FROM ruby:2.6.3
- Rails 5.2.4.3
エラー
docker環境で構築したrailsにて、docker-compose exec web rails db:migrateとしたところ、以下エラー
rails aborted! StandardError: An error has occurred, all later migrations canceled: Mysql2::Error: Invalid use of NULL value: ALTER TABLE `tasks` CHANGE `name` `name` varchar(255) NOT NULL ....以下省略原因
完全な特定には至らなかったが、以前行ったマイグレーションの処理がうまくいっていない可能性がある。テーブル作成周りでエラーが出ていた。
エラーのMysqlに関しては修正しているはず・・・。解決方法
データベースのリセットを行う。
$ docker-compose exec web rails db:migrate:reset再度マイグレーション
$ docker-compose exec web rails db:migrateこれで修復できました。確認の為、コンソールから正しくDB周りが動いてるか見るといいと思います。
- 投稿日:2020-08-03T09:11:08+09:00
オブジェクト指向で考えるコードの書き方 Ruby
オブジェクト指向とは
設計手法であって、文法では無い。最初聞いたときは何かの文法か構文の類か何かだと思っていましたが、そうでは無いんですね。Rubyはオブジェクト指向に沿って書きやすいように構文を揃えてくれている、というのが正しい理解のようです。
どうしてオブジェクト指向で書くのか
- 非常にコードの可読性が向上する
- 見えないバグを可視化しやすい
ことにあるのでは無いかと思っています。今回はここに焦点を置いて、まとめていこうと思います。
まずは、オブジェクト指向を考えないで書いてみる
toriaezuugoku.rbx_max = ARGV[0] y_max = ARGV[1] if !x_max || !y_max puts "引数を指定してください" exit 1 end x_max = x_max.to_i y_max = y_max.to_i #状態 x = 1 y = 1 step = 1 x_way = 1 y_way = 1 puts " #{step} (#{x},#{y})" x += 1 y += 1 loop do step += 1 puts " #{step} (#{x},#{y})" if y == 1 && x == x_max || x == 1 && y == y_max || x == 1 && y == 1 || x == x_max && y == y_max puts "GOAL!!" break elsif x == x_max x_way = -1 elsif y == y_max y_way = -1 elsif x == 1 x_way = 1 elsif y == 1 y_way = 1 end x += x_way y += y_way end以前の記事で書いたコードです。関数定義もせず、上から下へ、全ての処理が流れるように処理されるように書いています。これが1番読みにくいコードとなります。変数の定義から何をしているのか予想して実際の処理を追わなければ、機能を読めない仕様になっているからです。このコードが何をしているのかをここで時間をかけて読もうとするのを諦めて次で書き換えたコードを見ていきます。
methodtohash.rbdef move_ball(hash) hash["x"] += hash["x_way"] hash["y"] += hash["y_way"] hash["step"] += 1 end def reflect_x(hash) if hash["x_way"] == 1 hash["x_way"] = -1 elsif hash["x_way"] == -1 hash["x_way"] = 1 end end def reflect_y(hash) if hash["y_way"] == 1 hash["y_way"] = -1 elsif hash["y_way"] == -1 hash["y_way"] = 1 end end def goal?(hash, x_max, y_max) hash["y"] == 1 && hash["x"] == x_max \ || hash["x"] == 1 && hash["y"] == y_max \ || hash["x"] == 1 && hash["y"] == 1 \ || hash["x"] == x_max && hash["y"] == y_max end def boundary_x?(hash, x_max) hash["x"] == x_max || hash["x"] == 1 end def boundary_y?(hash, y_max) hash["y"] == y_max || hash["y"] == 1 end x_max = ARGV[0] y_max = ARGV[1] if !x_max || !y_max puts "引数を指定してください" exit 1 end x_max = x_max.to_i y_max = y_max.to_i state = { "x" => 1, "y" => 1, "step" => 1, "x_way" => 1, "y_way" => 1 } puts " #{state["step"]} (#{state["x"]},#{state["y"]})" loop do move_ball(state) puts " #{state["step"]} (#{state["x"]},#{state["y"]})" if goal?(state, x_max, y_max) puts "GOAL!!" break elsif boundary_x?(state, x_max) reflect_x(state) elsif boundary_y?(state, y_max) reflect_y(state) end end今度は関数定義とハッシュを利用して書いてみました。こうしてみると、loopの文のところを見て見ると、どうやら、stateというハッシュの中身の状態をmove_ballというメソッドで操作して、条件式の中でboundary_xとboundary_yでボールがバウンドした時にxとy座標をreflect_xとreflect_yで向きを変えているのかな・・・と読めなくは無いものになってきています。
関数定義した中身の処理が正しいとするならば、読む側の人はここだけ読めば、楽に何をやっているのか、このコードは何をするモノなのかが分かるようになります。状態をどういう時に変更しているのかの部分を細かく関数に分断していくとこういうメリットがあります。
また、ハッシュを使えば、多くのグローバル変数を使って表現していた前のコードより、状態の管理を安全にできますし、ボールの数が増えたりしてもまだ多少の融通が効きます。(ボールの数が無茶苦茶多かったり、未知数だった場合、対応できないですが)
が、まだ、読みやすさの追求においてまだ足りていないです。また、ハッシュにもし、タイポといったミスがあったとして、nilが返るだけになるこのコードは、エラーが発見しにくいものになります。大きいプロダクトであればあるほど発見しずらくなります。ではオブジェクト指向の出番ですね
object.rbclass Ball attr_accessor :x, :y, :x_way, :y_way, :step def initialize(x: 1, y: 1, x_way: 1, y_way: 1, step: 1) @x = x @y = y @x_way = x_way @y_way = y_way @step = step end def move @x += @x_way @y += @y_way @step += 1 end def reflect_x if @x_way == 1 @x_way = -1 elsif @x_way == -1 @x_way = 1 end end def reflect_y if @y_way == 1 @y_way = -1 elsif @y_way == -1 @y_way = 1 end end def goal?(x_max, y_max) @x == x_max && y == 1 \ || @x == 1 && @y == y_max \ || @x == 1 && @y == 1 \ || @x == x_max && @y == y_max end def boundary_x?(x_max) @x == x_max || @x == 1 end def boundary_y?(y_max) @y == y_max || @y == 1 end end class BilliardTable attr_accessor :length_x, :length_y, :ball def initialize(length_x: nil, length_y: nil, ball: nil) @length_x = length_x @length_y = length_y @ball = ball end def cue print_status loop do @ball.move print_status if @ball.goal?(@length_x, @length_y) puts "GOAL!!" break elsif @ball.boundary_x?(@length_x) @ball.reflect_x elsif @ball.boundary_y?(@length_y) @ball.reflect_y end end end def print_status puts "#{@ball.step}, (#{@ball.x}, #{@ball.y})" end end x_max = ARGV[0] y_max = ARGV[1] if !x_max || !y_max puts "引数を指定してください" exit 1 end x_max = x_max.to_i y_max = y_max.to_i ball = Ball.new() bt = BilliardTable.new(length_x: x_max, length_y: y_max, ball: ball) bt.cue上記のように書くことができるようになります。ボールの状態とビリヤード台の状態をクラスで分け、ボールの機能に必要な状態操作のメソッドを、ballクラスに、ボールを動かした台の上での操作の処理ををbilliardクラスにそれぞれ定義することで、実質、このコードの概ねの所作を知るためというのであれば、billiardクラスの処理系統と、コマンドライン引数を受け取る部分、つまり、クラスの外で書かれているコード部分を読めば、掴むことができます。英語の命名規則もグッと見やすくなっています。
最終的に、bt(ビリヤード台)に対して、cue(突く)を実行するとボールが動くということですね。非常に読みやすいです。また、状態操作をかなり細かく定義しているので、コードの修正のしやすさや、メソッドの呼びやすさといったものも上がっていると思います。これによりバグ修正が先にあげた二つのベタ書きのコードやハッシュで書いたコードより容易になると言えると思います。まとめ
1番最初に書いたベタ書きの処理から、2段階コードを書き直してみましたが、自分の最初に書いたコードがいかに読みづらく、言ってしまえば、汚いコードであるかが理解できました。
オブジェクト指向によるクラス設計が簡単にできるように作られたフレームワークであるところのrailsに私は1番最初に書いたようなコードの処理をガリガリ書いていたので、ヤベーコードを生み出していたということが更に際立ってよく理解できました。
- 投稿日:2020-08-03T07:23:37+09:00
Rubyの仕組み #1【基礎の基礎】
はじめに
こんにちは。
こちらの記事は、Ruby on rails をある程度学び、個人でアプリケーションをつくりあげた人間がRubyの基礎を学び直し短くまとめ上げた記事です。(かなり最低限。ここ大事。)移動中とかに軽い気持ちで見ていただければと思います。
ちなみに作成したアプリケーションはこちら→ライブ情報共有アプリケーション Parrot
他の回はこちらから
シリーズ化してまとめております。
#2 → もうちょっと待ってね??♂️
そもそもRubyってなによ
日本人プログラマのである、まつもとひろゆき氏によって開発されたオブジェクト指向言語です。バージョン1.0は1996年に公開されたようです。(筆者の生まれる前でびっくり)
そして、皆さんもお馴染みフレームワークであるRuby on Rails は2004年に公開されました。
Rubyに魅力はたくさんありますが、なんと言っても「楽しさ」だと思います。筆者も実は高校生の頃にいわゆるC言語を学習していたことがありますが、当時は半泣きでやっていました。しかし、このRubyはエラーの原因が分かりやすかったり、プログラミング初心者の方であっても分かりやすい表現が多く比較的楽しく学習できています。基礎知識
では早速基礎知識に入りましょう
オブジェクト指向言語って何?
Rubyは以下のように全てがオブジェクトになっており、数値やnillについてもメソッドを呼び出せます。
(.to_sは文字列化するメソッドです。)sample.rb1.to_s > 1 nill.to_s >""メソッドの呼び出し
メソッドの呼び方については何個か種類があります。
sample.rbオブジェクト.メソッド(引数1,引数2,,,) #カッコは省略可 オブジェクト.メソッド 引数1,引数2,,,コメントのやり方
コメント化したい文の前に # をつけるだけでOKです。
改行までがコメントになります。sample.rb# コメントコメントコメント 1.to_s #こんな感じで行の途中からコメントにすることもできます。リテラル
数値の123や、文字列などをソースコードに直接埋め込めることができる値のことです。(あまり意識しなくても良いかもしれません。)
sample.rb# 数値 123 #文字列 "やぁ"変数の扱い方
変数に文字列や数値を代入する場合は以下のようになります
sample.rba = "apple" b = 1 + 1 #スネークケース(単語を_で区切る記法)で表現することも可能 admin_name = "taro" #キャメルケース(大文字でで区切る記法)はあまり使いません AdminName = "taro"変数名をひらがなや感じにすることもできますが、全体のバランスが崩れますし一般的ではありません。
2つ以上の値に同時に代入することも可能です。ただ、これも理解時辛くなるのであまり一般的ではありません。
sample.rba, b = 1, 2 a >1 b >2 #右辺が少ない場合はnillが入る。 c, d = 3 c >3 d >nill #逆に多いと切り捨てられる e, f = 4, 5, 6 e >4 f >5 #2こ以上の変数に同じ値を代入することも可能(理解しづらいので一般的ではない) a = b = 7 a >7 b >7最後に
今回は本当に基礎の基礎という感じでしたね。
だからと言って侮れるわけでもなく、意外に知らなかった表現方法もあるんだなぁと思いました。
(いろいろな表現方法を知っておくと、もしその表現方法に出会ったときにすぐ理解できますしね。)ではでは。
- 投稿日:2020-08-03T02:16:56+09:00
Dockerでwheneverを扱う時No such file or directory - crontabが出る
簡単に定時処理が書ける便利なgem「whenever」
dockerでアプリ作成時、wheneverを使った処理をしようとしたが、つまづいてしまったので忘備録として記述しておきます。
環境
Ruby 2.5.1
Rails 5.2.4.3やりたいこと
今回はpostというモデルを用意し、wheneverを使って1分事にpostを作成してみる。
dockerの環境構築は終わっているものとしてスタート。modelとcontrollerを作成
docker上で
root@6181bdf78fa7:/myapp# bundle exec rails g model post name:string root@6181bdf78fa7:/myapp# bundle exec db:migrate root@6181bdf78fa7:/myapp# bundle exec rails g controller postswheneverの設定
まずはgemをインストール
Gemfilegem 'whenever', require: falsebuildし直してgemをインストールしたらコマンドを実行してファイルを作成
root@6181bdf78fa7:/myapp# bundle exec wheneverize . [add] writing `./config/schedule.rb' [done] wheneverized!作成されたschedule.rbに処理をかいていきます。
schedule.rbrequire File.expand_path(File.dirname(__FILE__) + "/environment") set :environment, :development set :output, 'log/cron.log' ENV.each { |k, v| env(k, v) } every 1.minutes do runner "Post.create" endここまできたら、cronに設定を反映させる
root@6181bdf78fa7:/myapp# bundle exec whenever --update-crontab bundler: failed to load command: whenever (/usr/local/bundle/bin/whenever) Errno::ENOENT: No such file or directory - crontab /usr/local/bundle/gems/whenever-1.0.0/lib/whenever/command_line.rb:77:in `popen' /usr/local/bundle/gems/whenever-1.0.0/lib/whenever/command_line.rb:77:in `write_crontab' /usr/local/bundle/gems/whenever-1.0.0/lib/whenever/command_line.rb:38:in `run' /usr/local/bundle/gems/whenever-1.0.0/lib/whenever/command_line.rb:6:in `execute' /usr/local/bundle/gems/whenever-1.0.0/bin/whenever:44:in `<top (required)>' /usr/local/bundle/bin/whenever:23:in `load' /usr/local/bundle/bin/whenever:23:in `<top (required)>'エラーが出ましたね。
cron初心者の自分はここでつまづいてしまいました。cronをインストール
どうやらdocker環境にcronをインストールしないといけないので、インストールに必要な処理をdockerfileに記述。
今回はこんな感じで1行追記しました。FROM ruby:2.5.1 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs RUN apt-get install -y cron #この行を追記 RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapproot@6710f08a3504:/myapp# bundle exec whenever --update-crontab [write] crontab file updated無事updateされてcronに反映されました。
これで自動処理が始まってると思い、しばらく待ってみます……どうやら動いてないですね。
wheneverが作動していればclog/cron.logが作成されるはずなので。cronを起動させる
cronが動いていないので状態を確認します。
root@827fe138767f:/myapp# service cron status [FAIL] cron is not running ... failed!やはり動いていませんでした。
起動しましょう。root@827fe138767f:/myapp# service cron start [ ok ] Starting periodic command scheduler: cron.1分後…
cron.logRunning via Spring preloader in process 122ついにcron.logが生成されlogが書かれました。
rails consoleで確認します。root@827fe138767f:/myapp# bundle exec rails c Running via Spring preloader in process 135 Loading development environment (Rails 5.2.4.3) irb(main):001:0> Post.all.count (4.5ms) SELECT COUNT(*) FROM "posts" => 1データが生成されたのがわかりますね!
課題
しかしcronの起動は手動で毎回行うのは面倒なので、できればdockerfileに記述して自動で起動されるようにしたいところです。
今後その方法も追記していきたと思います。
- 投稿日:2020-08-03T02:16:56+09:00
Dockerでwheneverを扱う時No such file or directory - crontabとなる
簡単に定時処理が書ける便利なgem「whenever」
dockerでアプリ作成時、wheneverを使った処理をしようとしたが、つまづいてしまったので忘備録として記述しておきます。
環境
Ruby 2.5.1
Rails 5.2.4.3やりたいこと
今回はpostというモデルを用意し、wheneverを使って1分ごとにpostを作成してみる。
dockerの環境構築は終わっているものとしてスタート。modelとcontrollerを作成
docker上で
root@6181bdf78fa7:/myapp# bundle exec rails g model post name:string root@6181bdf78fa7:/myapp# bundle exec db:migrate root@6181bdf78fa7:/myapp# bundle exec rails g controller postswheneverの設定
まずはgemをインストール
Gemfilegem 'whenever', require: falsebuildし直してgemをインストールしたらコマンドを実行してファイルを作成
root@6181bdf78fa7:/myapp# bundle exec wheneverize . [add] writing `./config/schedule.rb' [done] wheneverized!作成されたschedule.rbに処理をかいていきます。
schedule.rbrequire File.expand_path(File.dirname(__FILE__) + "/environment") set :environment, :development set :output, 'log/cron.log' ENV.each { |k, v| env(k, v) } every 1.minutes do runner "Post.create" endここまできたら、cronに設定を反映させる
root@6181bdf78fa7:/myapp# bundle exec whenever --update-crontab bundler: failed to load command: whenever (/usr/local/bundle/bin/whenever) Errno::ENOENT: No such file or directory - crontab /usr/local/bundle/gems/whenever-1.0.0/lib/whenever/command_line.rb:77:in `popen' /usr/local/bundle/gems/whenever-1.0.0/lib/whenever/command_line.rb:77:in `write_crontab' /usr/local/bundle/gems/whenever-1.0.0/lib/whenever/command_line.rb:38:in `run' /usr/local/bundle/gems/whenever-1.0.0/lib/whenever/command_line.rb:6:in `execute' /usr/local/bundle/gems/whenever-1.0.0/bin/whenever:44:in `<top (required)>' /usr/local/bundle/bin/whenever:23:in `load' /usr/local/bundle/bin/whenever:23:in `<top (required)>'エラーが出ましたね。
cron初心者の自分はここでつまづいてしまいました。cronをインストール
どうやらdocker環境にcronをインストールしないといけないので、インストールに必要な処理をdockerfileに記述。
今回はこんな感じで1行追記しました。FROM ruby:2.5.1 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs RUN apt-get install -y cron #この行を追記 RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapproot@6710f08a3504:/myapp# bundle exec whenever --update-crontab [write] crontab file updated無事updateされてcronに反映されました。
これで自動処理が始まってると思い、しばらく待ってみます……どうやら動いてないですね。
wheneverが作動していればclog/cron.logが作成されるはずなので。cronを起動させる
cronが動いていないので状態を確認します。
root@827fe138767f:/myapp# service cron status [FAIL] cron is not running ... failed!やはり動いていませんでした。
起動しましょう。root@827fe138767f:/myapp# service cron start [ ok ] Starting periodic command scheduler: cron.1分後…
cron.logRunning via Spring preloader in process 122ついにcron.logが生成されlogが書かれました。
rails consoleで確認します。root@827fe138767f:/myapp# bundle exec rails c Running via Spring preloader in process 135 Loading development environment (Rails 5.2.4.3) irb(main):001:0> Post.all.count (4.5ms) SELECT COUNT(*) FROM "posts" => 1データが生成されたのがわかりますね!
課題
しかしcronの起動は手動で毎回行うのは面倒なので、できればdockerfileに記述して自動で起動されるようにしたいところです。
今後その方法も追記していきたと思います。
- 投稿日:2020-08-03T00:28:18+09:00
[rails]redirect_toとrenderの違い
- 投稿日:2020-08-03T00:18:56+09:00
バリデーションを設定
- 投稿日:2020-08-03T00:18:56+09:00
[rails]バリデーションを設定
- 投稿日:2020-08-03T00:06:32+09:00
RSAなら、自分の好きな文字列を含む公開鍵が作れる件
はじめに
背景
※注: これは秘密鍵を10億個作れば、高確率で自分の名前を含む公開鍵が作れる件にインスパイアされて書いたネタ記事です
今、意識高い人向けにのSSH認証鍵(公開鍵/秘密鍵)はやはり ed25519 ですが、RSAであれば、なんと! 次のように自分の好きな文字列を含む公開鍵が作れてしまいます!
そう、つまりRSA最大のメリットは好きな文字列を含む自分だけの鍵をカスタマイズできることだったのです! ということで、RSAの魅力を見直そうという記事になります。( ウソです )
TL;DR;
- 好きな文字列を公開鍵に埋め込めるネタツール(Rubyスクリプト)を作ったよ!
※ https://github.com/angel-p57/qiita-sample/blob/master/rsa/rsa-genmykey.rb- RSAでは、公開鍵の主要パラメータ $n$ の値の範囲をコントロールできるので、結果として文字列埋め込みにつなげることができるよ!
- RSAの鍵データを決めるのは、秘密鍵のパラメータ$p,q$ ( 2つの巨大素数 ) だけど、ランダムに作った $p$ に応じて $q$ の範囲を絞ることで、積 $n=pq$ の範囲をコントロールするよ!
※注: 「好きな文字列を埋め込む」ことが鍵の脆弱性にはつながらないと考えていますが、なにかあっても責任とれないので、あくまでネタということでお願いします。
作成したツール
仕様
- 単一のRubyスクリプトです。
※ https://github.com/angel-p57/qiita-sample/blob/master/rsa/rsa-genmykey.rb にあります- コマンドライン引数で好きな文字列を指定することで、RSA SSH秘密鍵 ( OpenSSH形式の新しくないやつ ) を出力します。
※リダイレクト等でファイルに保存してください- 鍵長は4,096固定にしています。
- 文字列に指定できる文字種は英数および
+/
の64種です。( つまりbase64の文字種 )- 原理的に、埋め込める文字列の長さはかなり長くできると思いますが、ツール上では64文字を上限にしています。
- 特にチューニングしていないので、実行には数十秒~分単位で時間がかかるとお考え下さい。( 同じ環境でも結構ブレが出ます )
使用例
- 秘密鍵生成
ruby rsa-genmykey.rb 埋め込む文字列 > 保存先ファイル名
のようにして保存しています。- 公開鍵抽出
ツールはあくまで秘密鍵データを作るだけなので、公開鍵はssh-keygen -y -f 秘密鍵ファイル > 保存先ファイル名
で抽出します。- 試用
実際にssh-copy-id
で接続先に公開鍵を登録し、公開鍵認証で使えることを確認しています。からくり
公開鍵の内容
今回作った公開鍵のデータ
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDF/angelp57/a+cat+of+Flanders/…
ですが、先頭のssh-rsa
を除くと、次の図が示すようなバイナリ(一部テキスト)をbase64変換した文字列になっています。この中で、埋め込まれた文字列
/angelp57/a+cat+of+Flanders/
を決めているのは、巨大整数パラメータ $n$ (n=0xc5fde9e07a5a79eff6be71ab7e…
) のうち、最上位のc5
を除いた上位桁です。
逆に言えば、パラメータ $n$ をある程度の大きさの範囲に絞り込むことができれば、好みの文字列を埋め込むことができるのです。
※なお、最上位の桁はある程度の範囲でランダムに決めています。鍵のパラメータの調整
さて、RSAの場合にはいくつかの鍵のパラメータ ( いずれも整数 ) があります。
※詳細は公開鍵暗号RSAの数的構造をご覧ください
- 秘密鍵
- 素数 $p$(prime1), $q$(prime2)
- 指数 $d$(privateExponent)
- 派生パラメータ $d_p$(exponent1), $d_q$(exponent2), $q_{inv}$(coefficient)
- 公開鍵
- 積 $n$(Modulus)
- 指数 $e$(publicExponent)
この中で $e$ は現在 65537 ( 0x10001 ) の固定値が一般的であり、それ以外のパラメータは $n=pq$ を含め、全て素数 $p,q$ によって決まります。
つまり、$n$をどう決めるかは$p,q$をどう決めるかの話に帰着するのです。一般には、$n=pq$ が鍵長 ( ここでは4,096bit ) に合うように、$p,q$ がそれぞれ同じ桁数( 2,048bit )になるように、$p,q$が近すぎないように ( 少なくとも上位100bitで違いが出るくらい )、ランダムに素数を選びます。
ここで、$p$をランダムに選ぶのはあまり変えず、$q$をnの範囲から逆算して調整することで、最終的に$n$の範囲を調整することができます。計算内容
今回作成したツールの計算内容は、ざっくりと次のようなことをしています。
- 埋め込む文字列から $n$ の範囲を決定
- 素数 $p$ の範囲を決定 ( $\sqrt{n}$ よりある程度以上大きくなる範囲 )
- 範囲内でランダムな素数 $p$ を決定
- $p$の値および$n$の範囲から、素数$q$の範囲を決定
- 範囲内でランダムな素数 $q$ を決定
- $p,q$ の値に基づき鍵データを生成
なので、「いかに素数を探すか」が計算の中心であり、以下の2つを実装しました。
- 素数判定
ミラー・ラビン判定法をそのまま実装しました。試行40回としているので、誤判定の確率 $1/2^{80}$ ということで、実用上問題にならないと思います。
詳しくはFIPS186-4のAppendix C、C.3.1をご覧ください。- 素数候補探索
小さめの素数2つから目的のサイズの素数の候補を探索する方法を実装しました。 詳しくはFIPS186-4のAppendix C、C.9をご覧ください。
詳しくは精査していませんが、闇雲に候補を探すよりも効率が良いのではないかと思います。OpenSSLもbn_rsa_fips186_4.cで、関数bn_rsa_fips186_4_derive_prime()
としてこの方法を採用しているようです。
なお、小さな素数は普通にランダムに探します。サイズは171bitとしており、この数値はOpenSSLのbn_rsa_fips186_4.cの関数bn_rsa_fips186_4_aux_prime_min_size()
を参考にしています。鍵の安全性
RSAにおいて鍵の安全性は「$n$の素因数分解の困難性」として数体ふるい法の計算量が支配的であり、素数候補の無差別探索は攻撃者にとって割りに合わない方法です。
なので、素数の範囲にせいぜい数百bit程度の制限を設ける今回の手法であれば、鍵の安全性はほとんど低下しない…と考えています。
しかし、ツールにどんな瑕疵があるかは分かりませんので、あくまで「ネタ」としての利用にとどめることをお勧めします。終わりに
秘密鍵を10億個作れば、高確率で自分の名前を含む公開鍵が作れる件がなければ、「公開鍵に文字列埋め込めるじゃん」と思いつくこともありませんでした。感謝です。
なお、このツールでは鍵データの出力 ( ASN1 )、素数判定等全部自前で簡易的に書いてるのですが、素直にOpenSSLの機能使った方が楽でかつ性能も良かったのではないかという気もしてます。
そこらへんの書き換えなんかも含め、RSAの応用として、今年の夏休みの自由研究にいかがでしょうか?