- 投稿日:2020-12-16T23:56:04+09:00
[Ruby on Rails] macで環境構築をしよう
環境
macOS Catalina バージョン 10.15.7
今回インストールするバージョン
Ruby 2.6.6
Ruby on Rails 6.1.0
手順
Homebrew
のインストールHomebrewは、macOSオペレーティングシステム上のパッケージ管理システムのひとつです。
Rails
で必要なものをインストールするためにHomebrew
を使うので入っていない場合はインストールしましょう。$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"インストールされたか確認するコマンド
$ brew help
Homebrew
で使えるコマンド一覧が表示されればインストール成功です。
rbenv
のインストール
rbenv
は、複数のRubyのバージョンを管理し、プロジェクトごとにRubyのバージョンを指定して使うことを可能としてくれるツールです。$ brew install rbenv ruby-buildインストールされたか確認するコマンド
$ rbenv --version
rbenv 1.1.2
などと表示されればインストール成功です。(数字はバージョンによって異なります)次にターミナルを起動した際にrbenvの初期化を自動で行うように設定をします。
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile $ echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.bash_profile $ source ~/.bash_profile
Ruby
のインストールインストール可能なRubyのバージョン確認をします。
$ rbenv install -lインストールしたいバージョン(今回は
2.6.6
)が一覧に表示されていたらインストールを続けていきます。
他のバージョンをインストールする場合は2.6.6
の部分を置き換えればそのバージョンがインストールされます。rubyのインストールは少し時間がかかるので注意!
$ rbenv install 2.6.6インストールされたか確認するコマンド
$ rbenv versions
system
* 2.6.6このように表示されればインストール成功です。
次に
ruby
のバージョンを確認してみます。$ ruby -vrubyのバージョンを確認してみて
2.6.6
になっていない場合は、先程インストールした2.6.6
に切り替える作業をしていきます。$ rbenv global 2.6.6 $ rbenv rehash使用するバージョンを切り替えることができたのかを確認するコマンドを実行してみましょう。
$ ruby -v
ruby 2.6.6p146
このように表示されれば成功です。
bundlerをインストール
Gemとは?
ruby
のパッケージ管理ツールです。gemを容易に管理でき、gemを配布するサーバの機能も持ちます。例えばWebアプリケーションにユーザー登録やログイン機能などのユーザー認証機能を実装したい時、これらを一からコードを書くのはとても時間がかかってしまいますが、「devise」と呼ばれるgemをインストールするだけで簡単にユーザー認証で必要な機能が使えます。
bundler とは
bundlerとは、gemのバージョンやgemの依存関係を管理してくれるgemの一つです。 bundlerを使うことで、複数人での開発やgemのバージョンが上がってもエラーを起こさずに開発できます。
以下を実行することによって、bundlerをインストールできます。
インストールしていない場合はインストールしましょう。$ gem install bundlerインストールされたか確認するコマンド
$ bundler -vRailsのインストール
次に、
Rails
のインストールです。まだしていない場合はインストールしましょう。$ gem install railsインストールされたか確認するコマンド
$ rails -vRailsアプリケーションの開発
作成するアプリケーション用のディレクトリを作成します。今回はsampleという名前で作成します。
作成したらそのディレクトリの中に移動しましょう。$ mkdir sample $ cd sampleRailsで開発を始めるには、
rails new アプリケーション名
というコマンドを実行します。
このコマンドを実行することで、入力したアプリケーション名と同名のフォルダが作成され、その中に開発に必要なフォルダやファイルが用意されます
今回はsample_appという名前のアプリケーションを作成していきます。$ rails new sample_app開発中のアプリケーションをブラウザで表示するためには、サーバーを起動する必要があります。サーバーの起動は、以下のコマンドを実行するだけで完了です。
(サーバーを起動する場所に注意!!cd sample_app
でカレントディレクトリを変更して起動)$ rails sサーバーを起動した後、ブラウザで
localhost:3000
というURLにアクセスすると、Railsの初期画面が表示されるようになります。
- 投稿日:2020-12-16T23:51:25+09:00
Rubyのコードで「ハッシュっぽいのに括弧は配列?」と一瞬混乱
Rubyでこんなコード片を見かけて混乱した。
obj = [ a: "b", x: "y" ]括弧は配列のものだが、中身はハッシュの書き方になっている。
業務で実際に動いているコードなので、文法エラーでないことは確か。
答え
irbなどで実行してみればすぐにわかる。同等のコードは以下の通り。
クリックして展開
obj = [{ a: "b", x: "y" }] #=> [{:a=>"b", :x=>"y"}]要するに「ハッシュを要素とする配列」。
思い返してみると、メソッド呼び出しの引数の最後ではハッシュの波括弧を省略できていた。配列リテラルの中も、引数と同じような扱いなのだろう。
def mtd(*args) p args end mtd 1, 2, a: "b", x: "y" #=> [1, 2, {:a=>"b", :x=>"y"}]
- 投稿日:2020-12-16T23:42:39+09:00
FormObjectをうまく使いこなしたい
はじめに
Rails Advent Calendar 2020 16日目の記事です!
業務で毎日、Railsを触れていいコードが書けるように精進しています。
そんな中、今年一扱いに苦戦した(今も苦戦している)FormObjectについて、自分が考えることを共有しておきたいと思います!!
FormObjectとは
そもそもFormObjectについて、「なにそれ????」と思う方もいると思うでの簡単に紹介します!
簡単に言うと、FormObjectは、Railsのデザインパターン設計手法の1つです。
MVC
Railsの基本設計は、MVC(Model, View, Controller)です。
View
は、ユーザーからの入力を受け付けます。その情報をController
にわたし、Controller
はその情報を整形・制御します。
そして、Controller
は整形・制御した情報をModel
に対して渡してあげます。
Model
は、Controller
から受け取った情報をもとにDBにアクセスし、データの保存・変更・削除を行い、
その結果をController
やView
に返します。
返ってきた結果は、Controller
で整形・制御しView
に表示させたり、制御・整形せずそのまま表示したりします。これが基本的な
MVC
の流れですが、、、以下のようなことがあると思います。
- 一つの
View
で複数のModel
の属性を入力してもらい、DBを更新したい。Model
やDBの制限とは別にView
側で制限をかけたい。Controller
でのView
から受け取った情報の整形が複雑化してしまって、見辛くなった。 ...これらの実装をMVCだけで行おうとすると、
Controller
かModel
に責務が偏ってしまいます。
これでは、コードが追いづらくなったり、影響範囲が大きくなったりしてしまいます。そうすると、アプリの保守性が低いことになってしまうので、
新しい機能を追加したり、、、
修正したり、、、
DBの構造を変えたり、、するときに大変苦労な思いをすることになってしまいます。
それらを防ぐための手法の一つとして、
FormObject
という概念が存在します。メリット・デメリット
FormObject
を使用する一番の目的は、責務を切り離すことだと思います。
Controller
の肥大化の原因のひとつである、View
からの情報を整形するロジックをFormObject
でカプセル化する事で、
Controller
の責務が一つ減ることになります。
これでController
は、肥大化せずに済みますし、単一責務の状態になるので依存関係が少なくなり、保守性は高くなります。また、Modelの状態に関係ないロジックを
Model
にかくと、Model
が肥大化してしまいます。そういったロジックをFormObject
に書くことで、
Model
の責務が一つ減り、肥大化を防ぐことができます。このように、
Controller
やModel
に偏りがちなロジックをFormObject
にまとめることで、過剰責務にならずに済みます!!ただ、上記のことはデメリットにもなり得ると思います。
やたら滅多に、
FormObject
にロジックを集約させると、今度はFormObjectが肥大化してしまいます。
これでは本末転倒です。。。
Controller
とModel
のView
に関するロジックをFromObject
に任せて、責務を切り離すことを目的に使用しているにもかかわらず、
FormObject
の責務がでかくなってしまって、そこの保守性が低くなるのは良くありません。そのため、使用する前はしっかりと
FormObject
の責務を明確にしてから使用しましょう!FormObjectの責務とは???
FormObject
がなんなのか大体わかってきました。かなり便利な
FromObject
ですが、、、いろいろな記事をみて、実際に書いていく中で、ふと思ったことがあります。。。「ここってFomObjectでやるべき???」
「ControllerやModelのロジックとかぶってない???」
「FormObjectの責務って、、、結局どこまで持てばいいの???」これらを思ったきっかけを実際のコードを例に説明していきたいと思います。
FormObjectのvalidate
FormObjectを使用するとき、
ActiveModel::Model
をincludeしてればModel
同様にvalidateを書けることができます。
例えば、記事の登録でタグ情報も入力するフォームがあったとします。フォームでは、記事のタイトルと内容、タグの名前が必須であった場合、以下のようにかけます。
class ArticlePostForm include ActiveModel::Model attr_accessor :article_body, :article_title, :tag_name validates :article_body, presence: true validates :article_title, presence: true validates :tag_name, presence: true endこれでFormの入力に対して制限を設けることができました。ただ、記事とタグの
Model
にも以下のようなvalidateが設けてありました。class Article < ApplicationRecord validates :title, presence: true, uniqueness: true validates :body, presence: true validates :is_display, inclusion: [true, false] end class Tag < ApplicationRecord validates :name, presence: true, uniqueness: true end
FormObject
でのvalidateとModel
でのvalidateでは、意味は違います。
FormObject
でのvalidateでは入力された値に対する制限なのに対して、Model
でのvalidateではDBに保存する際の制限です。ただ、今回では検証している属性が
FormObject
とModel
でかぶってしまっているので、FormObjectのvalidateは必要なのか?
という疑問があります。
FormObjectでのsave・update
記事を見ているとよく見かけるものです。
以下のようにFormObject
でActiveRecordの作成を行うsave
メソッドがあります。class ConractForm include ActiveModel::Model attr_accessor :first_name, :last_name, :email, :body def save return false if invalid? contact = Contact.new(first_name: first_name, last_name: last_name, email: email, body: body) if contact.save true else errors.add(:base, contact.errors.full_messages) false end end end
Controller
では、以下のように書けるはずです。class ContactController < ApplicationController def new @contact = ContactForm.new end def create @contact = ContactForm.new(set_params) if @contact.save flash.now = 'お問い合わせを承りました' redirect_to :contacts else flash.alert = '送信に失敗しました' render :new end end private def set_params params.require(:contact).permit(:first_name, :last_name, :email, :body) end endここで、
FormObject
はModel
の生成・更新の役割を持っていいのかどうかに疑問を持ちました。
FormObject
は、入力に対してロジックを持たせるのが一般的だと思います(間違っていたらすいません。。。)ここで僕は
FomObject
に対しての責務で、「Viewの入力に対しての制御のみで
Model
の生成は別」と「validateをかけているからModel
の生成までがいい」の2通りの考え方できると思ったので、どちらに寄せるべきなのかを迷いました。
FormObjectのエラーハンドリング
Model
の生成において、validateによって発生したエラーをFormObject
で取り扱うかどうかがどうなのか疑問に思いました。
Model
のエラーとFormObject
のエラーは分けたほうがいいのか、一緒にすべきなのかが一番悩ましいところです。。。(ここは僕もまだ考えがまとまっていないので、知見が増えたらまた共有します。)
def save # ここはFormObjectの入力のエラー return false if invalid? # ここはModel生成時のエラー contact = Contact.new(first_name: first_name, last_name: last_name, email: email, body: body) if contact.save true else errors.add(:base, contact.errors.full_messages) false end endまとめ
FormObject
に対しての責務は、その都度考え、その責務以上のことをさせないように実装することが大事だと思っています。ただ、僕自身も
FormObject
を完璧に把握・理解しているわけではなく、他にも設計思想があるので、そこらへんと組み合わせた
FormObject
の考えもあるのかなと感じています。参考サイト
- 投稿日:2020-12-16T23:40:52+09:00
ジャンクションを使ってWindowsのRubyのバージョンを雑に切り替える
Windowsの複数のRubyのバージョンを検証したいときに、ジャンクションにパスを通すことでうまくいったので投稿。
例えば、Ruby2.6(x86) と Ruby2.7(x64) の検証をしたいとする。
まず、RubyInstaller から Ruby2.6(x86) のインストーラーをダウンロードし、インストール先にC:\Rubies\Ruby26
を選択してインストールする。この時パスは通さない。
同様に 2.7(x64)をC:\Rubies\Ruby27-x64
にインストール。
C:\Rubies
に以下のバッチファイルを作成する。use_26.batrmdir RubyCurrent mklink /J RubyCurrent Ruby26use_27_x64.batrmdir RubyCurrent mklink /J RubyCurrent Ruby27-x64
use_27_x64.bat
をダブルクリックして実行するとRubyCurrent
という名前のジャンクションが作成される。
最後に、PATHにC:\Rubies\RubyCurrent\bin
を追加する。これにより、バッチファイルを実行することで、Rubyのバージョンを好きに切り替えることができるようになった。
Ruby2.7(x64) を使いたいときはuse_27_x64.bat
をダブルクリックし、 Ruby2.6(x86) を使いたいときはuse_26.bat
をダブルクリックすればよい。
- 投稿日:2020-12-16T23:26:32+09:00
rails:db:migrateを実行すると「Directly inheriting from ActiveRecord::Migration is not supported.」と出た
<この記事について>
自作のアプリ製作中にカラムを追加する流れで「rails:db:migrate」を実行すると「Directly inheriting from ActiveRecord::Migration is not supported.」と表示されエラーに。
初歩的なミスでしたが備忘録として投稿![環境]
・Ruby 2.6.5,
・Rails 6.0.0
・macOSrake aborted! StandardError: An error has occurred, all later migrations canceled: Directly inheriting from ActiveRecord::Migration is not supported. Please specify the Rails release the migration was written for:調べてみると、原因はマイグレーションファイルがrailsのバージョンに対応していないことが原因のようでした。。
対象のマイグレーションファイルの1行目末尾にrailsのバージョンを追加。
class AddAttachmentImageToPosts < ActiveRecord::Migration[6.0] #←[6.0]を追加 def self.up change_table :posts do |t| t.attachment :image end end def self.down remove_attachment :posts, :image end endその後、再度以下を実行すると解決しました。
$ rails db:migarate<最後に>
実装してエラーが出る、思うような結果が得られないことは多々あると思います。
そんな時は冷静な気持ちでエラー文と丁寧に向き合うようにしましょう!
- 投稿日:2020-12-16T23:24:27+09:00
【個人アプリ作成】情報管理アプリ制作16日目
昨日は仕事に100%割いたので悔いなし。今日の実績。
今日やったこと
・ 顧客データ登録画面の作成
・ 中間テーブルの保存の復習
・ JavaScriptで装飾(ボタンをクリックするとメニューが出る)中間テーブルの保存
createアクションに対して、{:users_id []}の記述で自動で連携・保存される(ストロングパラメーターは必須だけど)
アソシエーションを組んでいるためのなのか、保存される仕組みは完璧には分からないかったが、仕組みは理解。明日から実装していく。
JavaScriptが全然うまくいかない
Id属性の情報は取れているのに、Chromeでデバックするとすぐリロードされて消える、、、、
function clickbutton(){ const pushPlusButton = document.getElementById("index-plus") pushPlusButton.addEventListener('mouseover', function(){ console.log("aaaa") }) } window.addEventListener('load', clickbutton)console.logの記述が一瞬だけ出てすぐ消える、、、
turbolinksの記述は消した方がいいはずなんだけど、ダメだったのか、、、実装苦戦中です。。。。
- 投稿日:2020-12-16T23:17:08+09:00
エラーメッセージの日本語化(rails)
rails-i18n(Gem)
日本語に対応できるようになるGemのこと。
localeファイル
エラーメッセージを全て日本語にするために必要なファイルで、さまざまな言語に対応できるファイルのこと。この中に日本語化用のファイルを作成することで、英語を日本語に翻訳できる。
実装方法
①日本語の言語設定
config/application.rbrequire_relative 'boot' require 'rails/all' Bundler.require(*Rails.groups) module Asakatu5757 class Application < Rails::Application config.load_defaults 6.0 config.i18n.default_locale = :ja config.time_zone = 'Tokyo' end end今回追加した記述は「config.i18n.default_locale = :ja」で、標準言語の日本語にすることができる。
②rails-i18nの導入
Gemfilegem 'rails-i18n'ターミナル% bundle install③locales内に日本語化用のファイルを作成し、編集する。
例)Userモデルのnicknameカラムを日本語化する場合。
config/locales/ja.ymlja: activerecord: attributes: user: nickname: ニックネーム
- 投稿日:2020-12-16T22:41:17+09:00
Railsでオブジェクト配列をハッシュ化するあれこれ(injectとかindex_byとか)
例えばUserモデルがあり、nameやらemailやらageやらを各オブジェクトが持っていたとする。
ある特定のageの人たちだけに施したい処理があって、どの年齢が対象となるかは既に配列のグループがあったとする。(若干例え悪いけどそんな感じだった)
最初、以下の様な処理を書いていた。
sample.rb# 対象としたい年齢群。 targets = [1,4,5,6,10,23] targets.each do |target| User.all.each do |user| # 一致するものがあれば次の処理へ next if target != user.age user.nextaction end endこれだとeachで二重に回すことになるので、もっと簡単にできないものかと考えた時に、ageをキーとするハッシュつくればいいのでは??となった。
そして最初、injectでハッシュを生成したのがこちら。
sample.rb# 対象としたい年齢群。 targets = [1,4,5,6,10,23] # ageをキーとしてUser.allをハッシュ化 users_sort_age = User.all.inject({}) {|hash,user| hash[user.age] = user; hash } targets.each do |target| # targetをキーとした(=targetと同じ値のageが存在した)場合次の処理に飛ばす next if users_sort_age[target].blank? user.nextaction endこれでもできるんだけど、index_byを使うともっとみやすくなりました。
sample.rb# 対象としたい年齢群。 targets = [1,4,5,6,10,23] # ageをキーとしてUser.allをハッシュ化をなんとこれだけで表している。 users_sort_age = User.all.index_by(&:age) targets.each do |target| next if users_sort_age[target].blank? user.nextaction end非常にみやすくなった。
ただしindex_byはActiveRecordでサポートされているものなので、デフォルトのRubyではinjectionを使いましょうという話。
- 投稿日:2020-12-16T22:28:53+09:00
API Gateway と Lambda と Dynamo
はじめに
前回のものに API Gateway を繋いでみます。こんな感じがゴール
API Gateway
基本
GUI がかなり直感的でなので頭使わずに使える感じです
- エンドポイントのリソース
/project
作って- リソースにメソッド
GET
PUT
POST
DELETE
作って- メソッド毎にLambda のコード
project
にマッピングするだけ忘れがちなのが、APIの変更は「デプロイ」を押してデプロイしないといけないこと。これでとりあえず動くはず
APIキー
APIキーを使うときはちょっと面倒
- リソースの各メソッド毎にAPIキーでの認証を必須にする
- 「使用料プラン」を作成する(ここでAPIに対するレートリミットなどを設定)
- 「ステージ」を作成する(開発用とか商用とかそういう意味のステージのことで、ここでは
dev_1
とした)- 「APIキー」「使用料プラン」「ステージ」を関連づける
変更の反映に数分かかることもあるようですが、これでAPIキーを付けてコールできるはず
curl -s -X GET -H "x-api-key: your-api-key-here" https://<project-id>.execute-api.us-east-2.amazonaws.com/dev_1/projects | jqLambda 統合プロキシ
「Lambda 統合プロキシ」にチェックを入れるだけでHTTPリクエストに含まれる様々なパラメターがそのまま Lambda に渡される。
例えば、HTTPメソッドが
POST
なのかGET
なのかが引き渡されるので Lambda 側でわかる.こんな風に読んだ時に
curl -s -X GET -H "x-api-key:your-api-key-here" https://<project-id>.execute-api.us-east-2.amazonaws.com/dev_1/projects?say=ciao -d '{"say": "hello"}'Lambda に
event
として渡されるパラメータはこんな感じevent{ "resource": "/projects", "path": "/projects", "httpMethod": "GET", "headers": { "accept": "*/*", "content-type": "application/x-www-form-urlencoded", "Host": "<project-id>.execute-api.us-east-2.amazonaws.com", "User-Agent": "curl/7.64.1", "X-Amzn-Trace-Id": "Root=1-5fd44390-5f78ee5a289e7e3a0355bec2", "x-api-key": "your-api-key", "X-Forwarded-For": "***.**.**.**", "X-Forwarded-Port": "443", "X-Forwarded-Proto": "https" }, "multiValueHeaders": { "accept": [ "*/*" ], "content-type": [ "application/x-www-form-urlencoded" ], "Host": [ "<project-id>.execute-api.us-east-2.amazonaws.com" ], "User-Agent": [ "curl/7.64.1" ], "X-Amzn-Trace-Id": [ "Root=1-5fd44390-5f78ee5a289e7e3a0355bec2" ], "x-api-key": [ "<your-api-key>" ], "X-Forwarded-For": [ "***.**.**.**" ], "X-Forwarded-Port": [ "443" ], "X-Forwarded-Proto": [ "https" ] }, "queryStringParameters": { "say": "ciao" }, "multiValueQueryStringParameters": { "say": [ "ciao" ] }, "pathParameters": null, "stageVariables": null, "requestContext": { "resourceId": "<resource-id>", "resourcePath": "/projects", "httpMethod": "GET", "extendedRequestId": "Xa9-iG9kCYcFU8Q=", "requestTime": "12/Dec/2020:04:14:08 +0000", "path": "/dev_1/projects", "accountId": "<account-id>", "protocol": "HTTP/1.1", "stage": "dev_1", "domainPrefix": "<project-id>", "requestTimeEpoch": 1607746448145, "requestId": "06499aaf-9168-49d7-b4da-2a0a3ad5d4ad", "identity": { "cognitoIdentityPoolId": null, "cognitoIdentityId": null, "apiKey": "<your-api-key>", "principalOrgId": null, "cognitoAuthenticationType": null, "userArn": null, "apiKeyId": "<api-key-id>", "userAgent": "curl/7.64.1", "accountId": null, "caller": null, "sourceIp": "***.**.**.**", "accessKey": null, "cognitoAuthenticationProvider": null, "user": null }, "domainName": "<project-id>.execute-api.us-east-2.amazonaws.com", "apiId": "<api-id>" }, "body": "{\"say\": \"hello\"}", "isBase64Encoded": false }クライアントからのデータ
body
はevent['body']
でアクセスできるので Lambda 側の CRUD するコードを前回から少し変えて、こんな感じにしときますrequire 'json' require 'aws-sdk-dynamodb' def add_project(table, body) table.put_item({ item: body }) end def delete_project(table, body) params = { table_name: table, key: { 'project-id': body['project-id'] } } begin table.delete_item(params) rescue Aws::DynamoDB::Errors::ServiceError => error puts error.message end end def update_project(table, body) params = { table_name: table, key: { 'project-id': body['project-id'] }, attribute_updates: { name: { value: body['name'], action: "PUT" } } } table.update_item(params) end def list_project(table) scan_output = table.scan({ limit: 50, select: "ALL_ATTRIBUTES" }) scan_output.items.each do |item| keys = item.keys keys.each do |k| puts "#{k}: #{item[k]}" end end end def lambda_handler(event:, context:) http_method = event['httpMethod'] # このへんを追加 body = JSON.parse(event['body']) # このへんを追加 dynamodb = Aws::DynamoDB::Resource.new(region: 'us-east-2') table = dynamodb.table('project') case http_method when 'GET' then list_project(table) when 'PUT' then update_project(table, body) when 'POST' then add_project(table, body) when 'DELETE' then delete_project(table, body) else 0 end { statusCode: 200, body: JSON.generate(list_project(table)) } end例えばこんな感じでリクエスト投げると、HTTPのメソッドをみて CRUD 処理をする。ちなみに
jq
しとくと見やすいのでおすすめcurl -s -X POST -d '{ "project-id":101, "name":"Cycling", "is-default":false }' -H "x-api-key: <your-api-key-here>" https://<project-id>.execute-api.us-east-2.amazonaws.com/dev_1/projects | jqうむ、動いたぞ
- 投稿日:2020-12-16T22:13:04+09:00
#5 Ruby始めました 「たのしいRuby 第6版」第2部基礎を学ぶ 五章 条件判断 ハノイ作成
本日から条件判断=プログラミングの要域に入ります。
一通り、教本通りきましたので、若干独自要素を加えて、プログラミング練習にハノイを作ってみましょうw
アルゴリズムとは条件判断の教科書みたいなものです。あのアルゴリズムアプリ通りに条件判断を作っていきます。オブジェクトを条件判断のみでプリントするという流れで一つのプログラムを作ってみます。
ごくごく僅かな命令文だけで構成したシンプルな構成でいきます。#ハノイの塔 #条件判断プログラム練習コメント
アルゴリズムです。
ここが肝の場所ですからじっくりと学んでいきたいと思いますが、ここだけをちゃんとやると多分一生遊べる場所ですが、まあサラッとwアルゴリズムは昔の本では小難しいことばっかり書いてあって、さっぱり解らなかったですがw今は本当に素晴らしくわかりやすものがたくさんあります。
コンピューターとアルゴリズム書籍で一番最初に習うのがハノイの塔ですが、ハノイを応用したプログラムの使い方、プログラムの書き方は書いてませんw昔のアルゴリズムとはそういうものでしたwなのでさっぱり分かりませんでしたwどう使うの?みたいなwハノイの塔(パズル)
ハノイはアルゴリズム学習で最初に学びますが、それは同時にプログラミング学習の基礎学習になります。基礎練習になりますので、ハノイを作ってみましょうというのが答えですねw
プルグラム教育のツールとして簡単なゲームを作るのがハノイの塔の狙いで、別にこれを使って凄いことが出来るみたいなものでは無いのでしょうねw
アルゴリズムアプリ
アルゴリズム図鑑 370円
https://apps.apple.com/jp/app/%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E5%9B%B3%E9%91%91/id1047532631これを見ているだけで楽しいwアルゴリズムの本なんかより全然理解度がありますw
FortranやっていたNECに行っていたおじさんが言ってましたが、凄腕はアルゴリズムを熟知しているので、処理が速いとのことでした。
プログラマーの腕はやっぱりアルゴリズムだそうです。まあそうですよね。
- 投稿日:2020-12-16T22:13:04+09:00
#5 Ruby始めました 「たのしいRuby 第6版」第2部基礎を学ぶ 五章 条件判断
コメント
アルゴリズムです。
ここが肝の場所ですからじっくりと学んでいきたいと思いますが、ここだけをちゃんとやると多分一生遊べる場所ですが、まあサラッとwアルゴリズムは昔の本では小難しいことばっかり書いてあって、さっぱり解らなかったですがw今は本当に素晴らしくわかりやすものがたくさんあります。
コンピューターとアルゴリズム書籍で一番最初に習うのがハノイの塔ですが、ハノイを応用したプログラムの使い方は書いてませんw昔のアルゴリズムとはそういうものでしたwなのでさっぱり分かりませんでしたwどう使うの?みたいなwアルゴリズム図鑑 370円
https://apps.apple.com/jp/app/%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E5%9B%B3%E9%91%91/id1047532631これを見ているだけで楽しいwアルゴリズムの本なんかより全然理解度がありますw
FortranやっていたNECに行っていたおじさんが言ってましたが、凄腕はアルゴリズムを熟知しているので、処理が速いとのことでした。
プログラマーの腕はやっぱりアルゴリズムだそうです。まあそうですよね。
- 投稿日:2020-12-16T22:11:35+09:00
Rails + Grape + Grape SwaggerでちゃんとOpenAPI/Swaggerドキュメンテーションしようとしたらハマったこと
この記事はCBcloud Advent Calendar 2020 の12日目の記事です。
Rails + Grape + Grape Swaggerで、実際に業務でOpenAPIドキュメントを生成してメンバーに共有しようとしたときにハマったことについて紹介します。
まずはおさらい: OpenAPI、Swaggerってなんだっけ?
OpenAPI
OpenAPIのリポジトリには下記のように記述されています。
https://github.com/OAI/OpenAPI-SpecificationThe OpenAPI Specification (OAS) defines a standard, programming language-agnostic interface description for HTTP APIs, which allows both humans and computers to discover and understand the capabilities of a service without requiring access to source code, additional documentation, or inspection of network traffic.
つまりOpenAPIは、プログラミング言語に依存しないHTTP APIのインタフェースを記述するための仕様です。
その仕様に従い記述されたドキュメントは、人が読んでも理解できるし、それを解析していい感じのUIを提供することもできるようになっている、ということです。Swagger
Swaggerのページには下記のように記述されています。
https://swagger.io/about/Swagger is a powerful yet easy-to-use suite of API developer tools for teams and individuals, enabling development across the entire API lifecycle, from design and documentation, to test and deployment.
〜略〜
Swagger started out as a simple, open source specification for designing RESTful APIs in 2010. Open source tooling like the Swagger UI, Swagger Editor and the Swagger Codegen were also developed to better implement and visualize APIs defined in the specification.
〜略〜
In 2015, the Swagger project was acquired by SmartBear Software. The Swagger Specification was donated to the Linux foundation and renamed the OpenAPIつまりSwaggerはAPI開発者のためのツール群を提供するプロジェクトということですね。そして、SwaggerのAPI定義のための仕様がLinux foundationに寄付され、OpenAPIに改名されたとのことです。
この記事が対象とする範囲
上記を踏まえ、この記事でやろうとしていること(そしてハマったこと)は、Rails + Grapeの構成で開発しているプロジェクトで、Grape SwaggerのDSLを使ってOpenAPI/Swagger仕様に沿ったJSONを生成することです。
また、その生成したJSONを読み込んで見やすくしてくれるツールで見たときに、意図した構成として表示されることを目指します(そしてハマります)使用したgem
ドキュメント生成にあたり使用したgemは下記の通りです。
gem 'grape' # RESTful APIを開発するためのDSLを備えたフレームワーク gem 'grape-swagger' # Grape APIからのドキュメント生成 gem 'grape-entity' # Grapeフレームワークにレスポンス整形のツールを加える gem 'grape-swagger-entity' # grape-entityからのドキュメント生成設定と使い方は各リポジトリをご参照のこと。
https://github.com/ruby-grape/grape
https://github.com/ruby-grape/grape-swagger
https://github.com/ruby-grape/grape-entity
https://github.com/ruby-grape/grape-swagger-entityハマったこと
1. descブロックのparamsと、descと同階層のparamsは別物
descと同階層のparamsではバリデーションをしてくれる
Grapeを使っているとき、下記のような書き方をすることがあると思います。
これはuser_nameは必須パラメータであることを指しており、実際にリクエストボディを空にしてリクエストを投げると、400エラーになってuser_name is missing
として返してくれます。class Users < Grape::API resources :params_in_same_layer do desc 'descと同じ階層にparamsを書いた場合' params do requires :user_name, type: String, documentation: { desc: 'ユーザ名', type: 'string' } optional :address, type: String, documentation: { desc: '届け先情報', type: 'string' } end post do present hoge: 'fuga' end end enddescブロック内のparamsではバリデーションはしてくれない
次にGrape Swaggerでドキュメンテーションしていこうとすると、下記のような記述方法が出てきます。
これにも先程と同様にリクエストボディ無しでリクエストを投げると、今度は400エラーにはならず201が返ってきます。class Users < Grape::API resources :params_whitin_desc_block do desc 'descブロック内にのみparamsを書いた場合' do params SimpleUserParamsEntity.documentation end post do present hoge: 'fuga' end end end class SimpleUserParamsEntity < Grape::Entity expose :user_name, documentation: { desc: 'ユーザ名(エンティティで定義)', type: 'string', required: true } expose :address, documentation: { desc: '住所(エンティティで定義)', type: 'string' } endただしUIで見ると見た目はほぼ同じ
上記をSwagger UIで見たときにどのような差があるか比べてみます。
どちらもユーザ名が必須であることは表現できています。しかし2つ目のエンドポイントでは実際には必須になっていないので注意が必要です。両方にparamsを書いたら、キーが一致していればdesc内のドキュメンテーションが優先されるが、一致していないものはそれぞれ出力される
それでは両方に書くとどうなるでしょうか。さらにそれぞれでしか定義されていない、age、blood_typeというキーを追加してみました。
class Users < Grape::API resources :params_in_same_layer_and_desc_block do desc 'descブロック内と、descの同階層の両方にparamsを書いた場合' do params SimpleUserParamsEntity.documentation end params do requires :user_name, type: String, documentation: { desc: 'ユーザ名', type: 'string' } optional :address, type: String, documentation: { desc: '届け先情報', type: 'string' } optional :age, type: Integer, documentation: { desc: '年齢', type: 'string' } end post do present hoge: 'fuga' end end end class SimpleUserParamsEntity < Grape::Entity expose :user_name, documentation: { desc: 'ユーザ名(エンティティで定義)', type: 'string', required: true } expose :address, documentation: { desc: '住所(エンティティで定義)', type: 'string' } expose :blood_type, documentation: { desc: '血液型(エンティティで定義)', type: 'string' } endSwagger UIで見るとこうなります。キーが一致している場合は、ドキュメンテーションはdesc内のものが優先され、一致していない場合は、それぞれ出力されるようです。
2. 複雑な構成のリクエストをUIでちゃんと表示しようと頑張ったが、結局は使用するUIの挙動次第
複雑なパラメータのパターンでUIがどうなるか確かめてみます。またdescブロック内に記述した場合と同階層に書いたエンドポイントを用意します。
class Users < Grape::API resources :complex_params_in_same_layer do desc 'descと同じ階層にparamsを書いた場合' params do requires :user_name, type: Integer, documentation: { desc: 'ユーザ名', type: 'string' } optional :addresses, type: Array[JSON], documentation: { desc: '届け先情報', type: 'array', collectionFormat: 'multi' } do requires :name, type: String, documentation: { desc: '届け先名', type: 'string' } requires :address, type: String, documentation: { desc: '届け先住所', type: 'string' } requires :tags, type: Array[JSON], documentation: { desc: 'タグ', type: 'array', collectionFormat: 'multi' } do optional :name, type: String, documentation: { desc: 'タグ名', type: 'string'} end end end post do present hoge: 'fuga' end end resources :complex_params_in_desc_block do desc 'descブロック内にのみparamsを書いた場合' do params ComplexUserParamsEntity.documentation end post do present hoge: 'fuga' end end end class ComplexUserParamsEntity < Grape::Entity class TagEntity < Grape::Entity expose :name, documentation: { desc: 'タグ名(エンティティで定義)', type: 'string' } end class AddressEntity < Grape::Entity expose :name, documentation: { desc: '届け先名(エンティティで定義)', type: 'string' } expose :address, documentation: { desc: '届け先住所(エンティティで定義)', type: 'string' } expose :tags, documentation: { desc: 'タグ(エンティティで定義)', type: 'array', is_array: true }, using: TagEntity end expose :user_name, documentation: { desc: 'ユーザ名(エンティティで定義)', type: 'string' } expose :addresses, documentation: { desc: '届け先情報(エンティティで定義)', type: 'array', is_array: true }, using: AddressEntity endSwagger UIで確認してみます。一方は配列内が表示されず、もう一方は表示されるけど階層構造がわかりにくい表示です。
見やすくできないかあがいてみます。全パラメータにparam_type: 'body'を追加してみます。
class Users < Grape::API resources :complex_params_in_same_layer do desc 'descと同じ階層にparamsを書いた場合' params do requires :user_name, type: Integer, documentation: { desc: 'ユーザ名', type: 'string', param_type: 'body' } # 以下略再びSwagger UIで確認します。すると、Entityで定義した方は相変わらず階層構造が失われています。Entityの書き方が悪いのかもしれません。
そしてもう一方は、階層構造がわかりやすくなりましたが、説明が反映されていません。そこで、ふと他のOpenAPIを解釈できるUIを試してみます。使うのはこちら。
https://github.com/Redocly/redocEntityの方は相変わらずですが、説明がちょっとマシになっています。
そして同階層の方はなんと完全に思い通りの表示になっています!!(配列のキーにつけた「届け先情報」という説明もちゃんと表示されています)このことからわかるのは、実際の表示は使用するUIツールによるので使用するツールでちゃんと表示されるようになることを確認しましょう、ということです。
3. exampleを出力するにはワークアラウンドが必要
exampleを指定しても出力されないのでなぜだろうと思ったら下記のissuesがありました。
現時点でOpenなので解決されるまではissues内に投稿されているワークアラウンドを入れればできるようです。
https://github.com/ruby-grape/grape-swagger/issues/762おわりに
リクエストパラメータのところのエンティティの使い方なんかはまだちゃんと理解できていないので間違い等あったらご指摘いただければ助かります。
- 投稿日:2020-12-16T22:06:21+09:00
nilガードについて理解してみた
Rubyの勉強をしているなかでnilガードというものがでてきたので勉強の記録用として記事にしてみました。
認識違いがあった場合などは是非ご指摘下さい。
nilガード
Rubyにはnilになってしまう状態を防ぐためにnilガードという書き方が存在します。簡単に説明すると仮に変数がnilだった場合には値を入れるということです。
ということで分かりやすく下記に例を記載したいと思います。
# 例1 > number = nil > number ||= 6 > puts number => 6 # 例2 > number = 5 > number ||= 6 > puts number => 5初めにnilガードを実装するにパイプ演算子||と=を組み合わせます。
例1ではnumberがnilなら6を代入しています。しかし例2ではnumberがnilではないので代入が行われていません。ここでnilガードというものはnilかfalseでないと代入が行われないと分かりました。まとめ
nilガードは変数にnilが入っているかもしれない状況でnilの代わりに何らかのデフォルト値を入れておきたいという場面で、とても便利に利用できます。
参考
・ 現場で使える Ruby on rails 5 速習実践ガイド
・ https://www.sejuku.net/blog/19044
- 投稿日:2020-12-16T21:56:15+09:00
【Ruby】RubyのAPI問題
任意の文字に対してn番目の文字を消し、
その消した文字を出力するメソッドを作りましょう呼び出し方
missing_char(string, num)出力例:
missing_char('kitten', 1) → 'itten'
missing_char('kitten', 2) → 'ktten'
missing_char('kitten', 4) → 'kiten'1・sliceメソッド
sliceメソッドを用いることで、配列や文字列から指定した要素を取り出すことができます。
文字列の要素を指定する際は数字を用い、先頭の文字列は0からカウントされます。# 文字列を作成 string = "abcdefg" # 文字列から引数で指定した要素を取得して変数に代入 str = string.slice(2) # strに代入した文字列を出力 puts str #=> "c" # 文字列はもとのまま puts string #=> "abcdefg"2・slice!メソッド
末尾にエクスクラメーションマーク(!)のつくメソッドを破壊的メソッドといいます。これはもとの配列や文字列を変化させるメソッドです。slice!メソッドを使用することで、指定した要素を削除することができます。string = "abcdefg" str = string.slice!(2) puts str #=> "c" # "c"が取り除かれている puts string #=> "abdefg"
解答def missing_char(str, n) str.slice!(n - 1) puts str end任意の文字に対してn番目の文字を消すために、文字列自体から取得した値を取り除く、slice!メソッドを使用。
しかしsliceメソッドは文字列自体の形を変化させることはできないため、エクスクラメーション(!)を付けたslice!メソッドに変更している。このslice!メソッドはmissing_charメソッド内で処理を行い、
missing_charメソッドの引数strには入力した文字列で、nは何番目の文字列を消すのか指示する数字を入れる仕組み。
そして、slice!メソッドにnから1を引いた数を引数にしているが、なぜ1を引くのか?
文字列の順番を指定する際も、配列と同じように先頭の文字列は0からカウントされるため。【文字列の順番】(人間
123
s t r
012
【添字の順番】(機械人間から見た文字の順番は1〜
機械から見た文字の順番は0〜
人間→機械への翻訳なので「n - 1」って感じで理解できました。
奥が深い。
- 投稿日:2020-12-16T21:51:29+09:00
第10回
これは講義用のまとめサイトです。見た人は是非LGTMを押しといてください。
やったこと
- Fibonacci数列
Fibonacci数列
お題
Fibonacci数列を求めるプログラムを作れ
例えば「0」を入力したら数列の初項の0が表示されてほしい方針
単に
def fib(n) if n==0 return 0 end if n==1 return 1 end endとか、もうちょっときれいにして
[[0,0],[1,1]].each do |pair| puts assert_equal(pair[0], fib(pair[1])) endというように、いちいち条件を書いていくのはきりがない
そこで何を入力しようと勝手に計算してくれるようにすることを考える実際にどうやるか
勝手に計算してくれるようにするのには初項(0番目)とその次の項だけはこちらで教えてあげないといけない
その次以降は、Fibonacci数列の定義によると、n番目の項は(n-1)番目+(n-2)番目ということなので、それをプログラムにしてやる。def fib(n) if n==0 return 0 else if n==1 return 1 else return fib(n-1) + fib(n-2) end end p fib(6)という感じになる。
refactoring
だけどこのままだとコードが長いので、もっとぎゅっとまとめましょうということで
def fib(n) return 0 if n==0 return 1 if n==1 return fib(n-1) + fib(n-2) end p fib(6)となった。
これだけだと、n=6の時はfib(5)とfib(4)の結果を足すので、fib(5)とfib(4)は定義されてないので動かないはず> 8…ってあれ!?動くの!?なんで?試しにfib(30)とかにしてもちゃんと値が返ってきた
でもどこにも再帰的に処理するような記述はした覚えがないのでこれに関してはよく分かりません。誰か教えてテスト数を増やす
いくつもテストするならeach doを使いましょう。
def fib(n) return 0 if n==0 return 1 if n==1 return fib(n-1) + fib(n-2) end [0,1,2,6].each do |pair| puts fib(pair) endで
> 0 > 1 > 1 > 8と、一気に返ってきますね
assert_equalを使って本当にちゃんと計算で来てるか確認する
先週作ったassert_equalの機能を使い、9番目まで本当にちゃんと計算出来てるか確認する
def fib(n) return 0 if n==0 return 1 if n==1 return fib(n-1) + fib(n-2) end require './assert_equal.rb' [[0,0],[1,1],[2,1],[3,2],[4,3], [5,5],[6,8],[7,13],[8,21],[9,34], ].each do |index, expected| puts assert_equal(expected,fib(index)) endindexとexpectedの組み合わせでできた配列をassert_equal関数に渡して判定してもらうというもの
["expected", 0] ["result", 0] succeeded in assert_equal ["expected", 1] ["result", 1] succeeded in assert_equal ["expected", 1] ["result", 1] succeeded in assert_equal ["expected", 2] ["result", 2] succeeded in assert_equal ["expected", 3] ["result", 3] succeeded in assert_equal ["expected", 5] ["result", 5] succeeded in assert_equal ["expected", 8] ["result", 8] succeeded in assert_equal ["expected", 13] ["result", 13] succeeded in assert_equal ["expected", 21] ["result", 21] succeeded in assert_equal ["expected", 34] ["result", 34] succeeded in assert_equalオールクリア
参考記事
チャート式ruby-V(Recursive Fibonacci)
- source ~/MasahiroOba/grad_members_20f/members/MasahiroOba/chapter10.org
- 投稿日:2020-12-16T21:30:55+09:00
(解決方法)NoMethodError in Devise::SessionsController#destroy
エラー文
NoMethodError in Devise::SessionsController#destroy
undefined method `remember_created_at=' for #User:0x00007f84757e1ea8 Did you mean? remember_me=Extracted source (around line #432): else match = matched_attribute_method(method.to_s) match ? attribute_missing(match, *args, &block) : super end end ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)エラー内容の詳細
NoMethodError in Devise::SessionsController#destroy
devise sessionsコントローラーのdestroyアクションが定義されていません。
仮説①deviseがおかしい?undefined method `remember_created_at=' for #User:0x00007f84757e1ea8 Did you mean? remember_me=
remember_created_at=は定義されていない。remember_me=ではないか?
remember_created_at=はマイグレーションファイルのデフォルトの記載で書いてあったような、、、
仮説②マイグレーションファイルのremember_created_at=をremember_me=へ修正したら解決されるのでは?解決方法
仮説②がかすっていました。マイグレーションファイルのデフォルトの記述を手違いで消してしまっており、今回のエラーを発生させていました。そのため、マイグレーションファイルのデフォルトの記述を書いてあげたら、エラーは解決されました。
仮説①は、無関係でした。deviseに関するエラー文が出てきても、deviseは完璧と仮定し、その他の要因から疑っていったほうが良いみたいです。
- 投稿日:2020-12-16T21:21:02+09:00
Rubyでスマートホームを作ろう!
はじめに
こんにちは!Life is Tech!#1 Advent Calendarの16日目を担当します、Webサービスプログラミングコースメンターのみずです! よろしくお願いします!
この記事ではRubyを使ってスマートホームを作る方法について書きたいと思います!
スマートホームってなんだ?
日本ではまだあまりメジャーになっていない気もしますが、スマートホームとは「IoT」や「AI」を使ってより住みやすくなった家のことです。
スマートスピーカー(Google Home、Amazon Alexa、HomePodなど)も、従来のスピーカーにIoT/AIが備わってより便利になったものとしてスマートホームの一部だと言えるでしょう。この記事では、スマートフォンやスマートスピーカーを使って家電をコントロールできるようにしてみたいと思います!
どの技術を使おう?(技術選定)
さて、やることは決まりました。今ある家電の制御です。
ではどんな技術を使いましょうか。スマートスピーカーを使ってコントロールしたいので、以下のような選択肢が出てくると思います。
- Amazon Echo
- Google Home
- Apple HomePod
- LINE Clova
いずれも家電の制御に対応しています。僕は身の回りにApple製品が多いのでAppleのHomePodで制御出来るようにしてみたいと思います!
HomePodはHomeKitと呼ばれる仕組みを使って家電をコントロールします。このHomeKitは仕組みが公開されていて、誰でも作る事ができます。
Life is Tech ! のWebサービスプログラミングコースではRubyを使ったプログラミングをしています。なので、Rubyを使って実際にHomeKitを実装していきましょうー!開発環境について
今回この記事ではMacで開発する事を前提に進めています。WindowsやLinuxでも開発できるかとは思いますが、手順が異なると思うので各自で挑戦してみてほしいです。
下準備
ターミナルで以下のコマンドを打って準備しましょう。
$ mkdir <プロジェクト名> # フォルダ作成 $ cd <プロジェクト名> # フォルダに移動 $ bundle init # Gemfileを作るコマンド $ code . # VS Codeを開くコマンドさて、これでVS Codeが起動します。(上手く起動しない場合はVS Codeを起動して先程作ったフォルダを開きましょう。フォルダはユーザーフォルダの中にあるはずです!)
今回は
ruby_home
というライブラリを使用したいのでGemfileに以下を追記しましょう。gem 'ruby_home'追記できたらVS Codeのターミナルでbundleコマンドを実行してライブラリをインストールします。
$ bundle
これで開発の準備は完了です!!
試しに照明を作ってみよう
app.rbというファイルを作り、以下のコードを書いてみます。
app.rbrequire 'ruby_home' accessory_information = RubyHome::ServiceFactory.create(:accessory_information) fan = RubyHome::ServiceFactory.create(:lightbulb) fan.on.after_update do |status| if status puts "照明がオンになりました" else puts "照明がオフになりました" end end RubyHome.run
ruby app.rb
コマンドで実行してみると以下のように表示されるはずです!$ ruby app.rb Please enter this code with your HomeKit app on your iOS device to pair with RubyHome ┌────────────┐ │ 766-75-746 │ └────────────┘
iPhoneのHomeアプリを起動してアクセサリを追加してみましょう。
このように出てくるので「コードがないか、スキャンできません」を押しましょう。
すると、同じWi-Fiにつながっている対応デバイスを見つけてきてくれます。RubyHomeをタップしましょう!
アラートが出てきますが気にせず進めます。
ここからは続けるボタンを繰り返せばOKです!
無事追加出来たら電球マークのRubyHomeをタップしてみましょう!
このようにオンになりましたか?ターミナルを見てみるとこのようにオンオフが表示されているはずです。
$ ruby app.rb Please enter this code with your HomeKit app on your iOS device to pair with RubyHome ┌────────────┐ │ 766-75-746 │ └────────────┘ 照明がオンになりました 照明がオフになりました 照明がオンになりました 照明がオフになりました
コード説明
コメントに説明を書いたので読んでみてください。
require 'ruby_home' # ライブラリ読み込み accessory_information = RubyHome::ServiceFactory.create(:accessory_information) # アクセサリだよ!と言う為に必要な機能を追加 fan = RubyHome::ServiceFactory.create(:lightbulb) # 照明機能を追加 fan.on.after_update do |status| # 照明のオンオフが変更された時 if status # onだったらTrue、offだったらFalse puts "照明がオンになりました" else puts "照明がオフになりました" end end RubyHome.run # 起動このようにかなり短いコードで実現する事が出来ます!
この中のif文の中に実際に照明を制御するコードを書けばスマートホームが作れるわけです!実際の照明と連携してみる
さて、ここからは照明を制御していきましょう。ごく一部APIが使えるような照明もあるようですが、うちの家の照明は至って普通の照明なので赤外線でコントロールすることにします。
というわけでこんな感じのモジュールを作りました。ESP32というチップ(の開発ボード)をベースにした赤外線信号を発信出来るデバイスです。USBで通信しても良かったのですが、部屋の好きなところに置きたかったのでWi-Fiに接続し、API経由で赤外線を発信できるようにしてみました。
基本的には以下のようなコードになっています。赤外線によるコントロールは機器によって大きく方法が変わるので各自で調べて実装することをおすすめします!
https://gist.github.com/mizucoffee/912bb2032e21568821b8ddedda13d99dapp.rbrequire 'ruby_home' require 'net/http' require 'uri' on = "p344A9034A4" off = "p344A90F464" def send_ir(code) # APIを呼び出すコード uri = URI.parse('http://192.168.101.200/send?data=' + code) req = Net::HTTP::Post.new(uri) http = Net::HTTP.new(uri.host, uri.port) http.request(req) end accessory_information = RubyHome::ServiceFactory.create(:accessory_information) fan = RubyHome::ServiceFactory.create(:lightbulb) fan.on.after_update do |status| if status puts "照明がオンになりました" send_ir on # オンにする信号を送るコード else puts "照明がオフになりました" send_ir off # オンにする信号を送るコード end end RubyHome.runさて、準備は出来ました!実際に動かしてみましょう!
こんな感じで動きました!!
おわりに
この記事ではRubyを使ってHomeKitに対応したアプリを実際に開発し、照明をコントロールしてみました。結構簡単だったのではないでしょうか!
対応したデバイスを購入してスマートホームを作るのも良いですが、自分の力で作り上げてみるのもオツなものです。ここからは自分の話になりますが、僕は普段「身近な面倒を快適に!」というモットーで開発をしています。
これには自分はもちろん、家族や友達、それこそメンターやメンバーのみんなも含まれています!
今回のスマートホームは主に自分用ですが、家族に一緒に使ってもらうことも出来るし、もしかしたらオフィスにだって導入出来るかもしれませんね。いままで、家族の介護用にベッドから降りたことを通知するセンサーを作ったり、Twitterのフォロー・フォロワーをバックアップするサービスを作ったりしてきたのですが、ある程度開発する力があればこうやって周りを幸せにすることが出来たりします!
人それぞれ開発をする目的は違うと思いますが、ぜひぜひ自分や周りの人が嬉しくなるような開発にもチャレンジしてみてほしいです!
最後まで読んでいただきまして、ありがとうございました!
明日はつっちー(@taznica)が何か書いてくれるようです!お楽しみにー!!
- 投稿日:2020-12-16T21:17:07+09:00
インストール後のパス設定。みんなどうしてる?
みなさん、こんにちは。本日のお悩み相談の時間です。
前書き
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でおなじみのスクリプト言語 Kinx。詳しくは Web(下記)で。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
実は最近 バージョン 0.17.0 を(プレ)リリース しまして、そこで初めてインストーラを付けてみました。NSIS を使ってビルドしています。
ところが、今現在はインストール後に実行ファイルへのパスを環境変数
Path
に追加 していません。手動でパスを通してください的な。元々環境変数
Path
をイジるとか おっかないなー、というのもあったのですが、どうも NSIS で実現しようとすると問題がありそうなのですね(この辺とか)。ただ、やっぱりパスを自分で通せ、というのは心苦しいので次版では付けたいところ。そこで、皆さんはインストーラーで環境変数
Path
へのパスの追加/削除はどうしてますか?というのが本日のクエスチョンです。色々調べたのですが、あんまりいいアイデアが見つからずで。一応、以下のような基準で探してました。
- シンプルなソースコード
- 使いやすい機能
- 便利なライセンス
全部満たすものはなかなか見つからず。ライセンスが書いてないものとか、GPL とか1。
結論
そこでだ、若旦那!
簡単なプログラムを作成しました。こちらをご覧ください。
これは...
- シンプルなソースコード... 1つのファイル だけで単機能ツールとして実現。
- 使いやすい機能... 単機能ツールなので 使い方も単純。
- 便利なライセンス... ザ・MIT!(私がそう設定したのですが)
ですが...
このプログラムはシステム環境を変更するので、やはりとても 怖い のですよ。ですから、たくさんの人にソースコードをチェック してもらいたく、問題があれば修正 したいのです。
ということで、ソースコードを色々な人に見て貰えると嬉しいです。問題見つかれば大変感謝するでしょう(それ以上のことは力不足で何もして差し上げられませんが)。もしくは、この問題(パスの追加/削除)に対するより良い解決策があれば教えてください。
ではまた。
P.S.
もし、これ (https://github.com/Kray-G/addpath) 自体気に入ってくれるようでしたら、★ください。待ってます!
念のため、GPL が悪いものとは言ってませんので...。自分のが MIT なので採用しづらいなと。 ↩
- 投稿日:2020-12-16T20:04:29+09:00
railsでグラフ機能を導入
はじめに
今回学習記録をとるアプリ開発をしていたところ学習状況をグラフ化したい!
と思い比較的簡単に実装ができたので備忘録として残します。
筆者はrails6を使っての実装となります。
初学者のため間違え等ありましたらご指摘頂けると幸いです!手順1 gem chartkickを導入
グラフ機能を簡単に実装するにはgemのchartkickを使います。
gemファイルに以下を追加しbundleinstallします。
groupdateはDBから様々な情報をグループ化して取り出せる便利なメソッドがあるので導入してますが、chartkickを使うだけであればgem "chartkick"のみで問題ありません。gem "chartkick" gem "groupdate"rails6の場合は以下コマンドを実行
yarn add chartkick chart.js手順2 jsファイルの読み込み
chartkickではjsを使って実装するためjsファイルをapp/javascript/packs/application.jsに記述します。
rails6の場合require("chartkick") require("chart.js")rails5の場合
//= require chartkick //= require Chart.bundle手順3 htmlにグラフを挿入
ここまで行けばあとは公式ページにあるサンプルコードをhtmlに記述するだけで簡単にグラフを作成する事ができます。
私の場合このように使いました。
<%= column_chart @tweets.group_by_day_of_week(:created_at, format: "%a").count %>
https://gyazo.com/4c24263ba93c7e3bc2ea6974bfb0143f
ここでgroup_by_day_of_weekはgemのgroupdateを導入していると使えるメソッドです。
今回使ったのは棒グラフですが他にも折れ線や円グラフも簡単に実装できるのでchartkickの公式サイト見て見てください!!
- 投稿日:2020-12-16T19:36:01+09:00
Rubyでlsコマンド実装
はじめに
こんにちは!
DMM WEBCAMP Advent Calendar 2020の24日目を担当するメンターの@shinoooooです。クリスマスまであと1日ですね!
皆様は、どうお過ごしになられるでしょうか?
人それぞれ予定は異なるとは思いますが、まずは貴重なクリスマスイブの時間を割いてこの記事を読んでいただいていることに、感謝申し上げます。
ありがとうございます!この記事では、Rubyで実装したlsコマンドの解説及び、実装を通して得られた知見を書きました。
自分自身もRubyを学習中の身ですので、記事の内容やコードに関するアドバイスをいただけましたら幸いです。開発環境
- macOS Catalina 10.15.7
- Ruby 2.7.2
- zsh 5.8
lsコマンドとは
プログラミングを学習されている方は、一度は実行したことがあるコマンドかと思います。
ls
の機能は、Linux関連のドキュメントを和訳しているサイトによると、ls, dir, vdir - ディレクトリの中身をリスト表示する
と書かれています。
実際に実行すると、
$ ls hoge huga index.html #カレントディレクトリ内のファイルが出力される。このような表示が出てくるかと思います。
今回は、このlsコマンドをRubyで実装し、
$ ruby ls.rb hoge huga index.html
と出すことが目標です。
仕様
今回実装した
ls.rb
の仕様は、下記の通りです。
ls
,ls -l
,ls -a
,ls -la
と同等の機能を実装
- 'optparse'というライブラリを使った都合上、
-la
のように複数のオプションをまとめて指定することができない。組み合わせる場合は、-l -a
と入力する必要がある。- ACLと呼ばれる機能(説明は割愛します)の表示は未実装
- 実際の
ls
のソースコードを参考にしていないので、アルゴリズムや関数名が違うかもしれません。出力形式
いきなりコードを書いて模倣することはできないので、
ls
の出力の分析を行います。
ディレクトリとファイルを用意した方がわかりやすいので、下記のコマンドを実行します。# directory1~directory10とfile1~file10をtestディレクトリ内に作成。 $ mkdir test/directory{1..10} && touch test/file{1..10}そして、
ls test
を実行して出力結果を確認します。$ ls test directory1 directory3 directory6 directory9 file2 file5 file8 directory10 directory4 directory7 file1 file3 file6 file9 directory2 directory5 directory8 file10 file4 file7当たり前といえば、当たり前ですが綺麗に並んでいます。
闇雲に出力するだけではls
のような整った出力結果にはならなそうなので、課題を1つ1つ洗い出しながら完成を目指すことにします。表示順
ls
で出力されているファイル名は以下のような順番で昇順で並んでいます。
「ファイルの数」や「ターミナルの画面の大きさ」、「ファイル名の長さ」で、「1行に表示されるファイルの数」と「行数」は若干出力は変化しますが、表示順で確認したls test
の出力は以下のようになっていました。表示されている順番名前が1番目に若いファイル 名前が4番目に若いファイル 名前が7番目に若いファイル ... 名前が19番目に若いファイル 名前が2番目に若いファイル 名前が5番目に若いファイル ︙ ... 名前が20番目に若いファイル 名前が3番目に若いファイル 名前が6番目に若いファイル ︙1行ずつ
名前が1,4,7,10,13,16,19番目に若いファイル
,名前が2,5,8,11,14,17,20番目に若いファイル
,名前が3,6,9,12,15,18番目に若いファイル
を順に出力すればls
の出力の再現ができそうです。配列の処理
ディレクトリ内のファイル一覧を取得するために用いたDirクラスは、ファイル一覧を配列で渡してくれます。今回は、これをfilesという変数に代入して扱うこととします。
ですが、受け取った配列の中身が昇順ではなかったのでArrayクラスのsort
メソッドで昇順に並び替えます。sortメソッドfiles = ["file2", "file3", "file1"] files.sort #=> ["file1", "file2", "file3"]配列の添字は0から始まります。
昇順で並び替えたことにより、files内の1番目に若いファイル名はfiles[0]
とすることで取得することができます。
表示順で書いた名前が1,4,7,10,13,16,19番目に若いファイル
を出力するということは、files[0],files[3],files[6],files[9],files[12],files[15],files[18]
を出力することと言えます。1行に出力できるファイル数や、行数を求め、
files
をうまく出力する処理を書くと、下記のような出力になります。$ ruby ls.rb test directory1directory3directory6directory9file2file5file8 directory10directory4directory7file1file3file6file9 directory2directory5directory8file10file4file7可読性は低いですが、一歩
ls
に近づきました。ファイル名の間の区切り
このままだとどこからどこまでが1つのファイル名か分からないので、ファイルとファイルを区切る必要があります。
ls
の出力結果のファイル名とファイル名はどう区切られているか、確認していきたいと思います。
適当にprint.rb
というような名前のファイルを作成し、ls test
の実行結果のdirectory1 directory3をコピーし、貼り付けます。
これを文字列としてターミナルに出力してみたいと思います。
" "
で囲み、先頭にp
を付け、print.rbp "directory1 directory13"として保存します。
ターミナル上で
ruby print.rb
と実行してみると以下のように出力されます。$ ruby print.rb "directory1\tdirectory13"directory1とdirectory3の間に
\t
という文字が現れました。
これは、タブ文字を表しています。
このことから、ファイル名がタブ文字で区切られていることが確認できます。これを模倣してファイル名の末尾に
\t
を追加することにします。
そのためにprintf
メソッドを使用します。
ここではprintf
について詳しく解説はしないので、気になる方はドキュメントを読んでみてください。printfの使用例printf("%s\t", files[0]) # => %sの中にfiles[0]の中身が入り、出力される。 printf("%s\t", files[1]) # => "directory1 ""directory10 "
ls.rb
にも実装すると、以下のような出力になります。$ ruby ls.rb test directory1 directory3 directory6 directory9 file2 file5 file8 directory10 directory4 directory7 file1 file3 file6 file9 directory2 directory5 directory8 file10 file4 file7先ほどと比べると綺麗に見えますが、まだ
ls
の出力とは異なります。ファイル名の長さの統一
最後に、綺麗に見せるためにファイル名の長さを統一します。
directory10
とfile1
、file2
、file3
を使い、考えてみましょう。
現時点での出力はこのようになっています。directory10\tfile2 file1\tfile3ファイル名の長さが統一されていないことで、1行毎の出力に文字数のズレが生じています。
全てのファイル名の長さが同じであれば、
ファイル名\tファイル名\t ファイル名\tファイル名\tのように綺麗な見た目にすることができるので空白文字を用いて、文字数が一番長いファイル名の長さに統一します。
今回の例では、directory1
が一番ファイル名が長いので,directory1
の長さに統一することにします。# 空白文字を使うことで見た目を整えることができる! directory\tfile2 \t file1 \tfile3 \t文字列の長さの統一も
printf
メソッドで実現することができます。
%s
を少し改変することで幅指定を行うことができます。printfの幅指定printf("%15s", files[0]) # => " directory1" 15文字になるよう空白文字が追加される。 printf("%-15s", files[0]) # => "directory1 " -をつけると左詰めになる。 name_len = 15 printf("%-#{name_len}s\t", files[0]) # => 式展開もできる。 # => "directory1 "
ls.rb
でも幅指定をすると$ ruby ls.rb test directory1 directory3 directory6 directory9 file2 file5 file8 directory10 directory4 directory7 file1 file3 file6 file9 directory2 directory5 directory8 file10 file4 file7
ls
と同じ出力を得ることができました!解説
全てを解説すると、長くなってしまうので、出力形式で説明した箇所のみを解説します。
また、呼び出しているメソッドに関しては細かい説明はせず、機能の解説のみをコメント文で行っています。ソースコードは 完成コードにあります。
全体の実装が気になる方は、こちらを参照してください。self.display_normal
def self.display_normal(dir) files = get_files(dir) # ディレクトリ内のファイル名の配列を、filesに代入する。 name_len = 1 # ファイル名の最大値を格納するname_lenを用意する。 files.map { |file| name_len = file.length if name_len < file.length } # 一番長いファイル名を、name_lenに代入する。 total_length = (name_len + 5) * files.count # 出力すべき文字数を算出。 columns = `tput cols`.to_i # ターミナルの1行の文字数を取得。 line_count = (total_length + (columns - 1)) / columns # 必要な出力行数を求める。 column_count = (files.count + (line_count / 2)) / line_count # 1行に表示できるファイル数を求める。 line_count = 1 if line_count == 0 # 最低でも1行は出力する必要があるため、line_countが0になっていた場合、1を代入する。 (0...line_count).each do |line| (0..column_count).each do |column| idx = line_count * column + line printf("%-#{name_len}s\t", files[idx]) if idx < files_count # 添字が配列内の大きさを超えていなければ、ターミナルに出力する end print("\n") # ここに到達すると1行分の出力が終わっているので改行する。 end end実行時間
time
コマンドを使い、実行時間を計測します。$ time ls test directory1 directory3 directory6 directory9 file2 file5 file8 directory10 directory4 directory7 file1 file3 file6 file9 directory2 directory5 directory8 file10 file4 file7 ls test 0.00s user 0.00s system 78% cpu 0.008 total # ls testの実行時間 0.00s $ time ruby ls.rb test directory1 directory3 directory6 directory9 file2 file5 file8 directory10 directory4 directory7 file1 file3 file6 file9 directory2 directory5 directory8 file10 file4 file7 ruby ls.rb test 0.10s user 0.07s system 86% cpu 0.191 total # ruby ls.rbの実行時間 0.10s目で見てわかるぐらいには
ls
よりも遅いですね。所感
実用性はあまりないですが、かなりlsコマンドを再現できたかと思います。
車輪の再開発と呼ぶには拙いコードですが、取り組む前よりも実装力は上がったと思います。期日に余裕があればテストを書いたり、機能拡張をしたかったです。
また、常にドキュメント片手に作業をしていたので、ドキュメントを読み漁る力も身についたと思います。当記事では解説はできませんでしたが、
ls -l
の実装も大変学びが多かったです。おわりに
当アドベントカレンダーはWeb系の記事が多いので少し変わったことを記事にしたいと思いました。
他の方よりも、直接学びになることは少ないかもしれませんが、少しでも参考になりましたら幸いです。
いよいよ明日が最終日です。
@hide9138の記事で当アドベントカレンダーが良い締めくくりを迎えれるよう願っています。
それでは、良いクリスマスを完成コード
Github : https://github.com/shinooooo/Ruby-sh
参考
- 投稿日:2020-12-16T19:33:53+09:00
【ActiveAdmin】レコードが無い場合の、まだありません。「作成する」を非表示に【blank_slate content link】
ActiveAdminで、
1件もレコードが無い状態のまま、
indexのページを表示すると、
"%{resource_name} はまだありません。「作成する」
というようなメッセージが表示されます。
この、
「作成する」がリンクになっているので、
非表示にしようと思ったのですが、
ActiveAdminのデフォルトの機能には無い様子...
色々調べていると、
一応解決策が見つかったのと、
今後もたまに使うかと思ったので備忘録にしておきます。
解決策
ActiveAdmin::Views::Pages::Indexというクラスにある、
render_blank_slate というメソッドにモンキーパッチを当てます。
- render_blank_slate
- ja.ymlのキー (blank_slate、content、link)
[https://github.com/activeadmin/activeadmin/blob/master/config/locales/ja.yml#L66:embed:cite]
やり方
1、
initializerで、モンキーパッチを実装するファイルを読み込む。
クックパッドさんの記事を参考にしました。
[https://techlife.cookpad.com/entry/a-guide-to-monkey-patchers:embed:cite]
lib/monkey_patches/active_admin というディレクトリを作ります。
他への影響を抑えるため、
initializer内で読み込むディレクトリも最小限にします。
config/initializer/active_admin_monkey_patches.rbDir[Rails.root.join('lib/monkey_patches/active_admin/**/*.rb')].sort.each do |file| require file end
2、
1で指定したところにモンキーパッチを実装。
例えば、
monkey_patches/active_admin/index_monkey_patch.rb のようなファイルを作ります。
今回は、
ActiveAdmin::Views::Pages::Indexというクラスにある、
render_blank_slate というメソッドにモンキーパッチを当てるため、
下記のようにします。
monkey_patches/lib/active_admin/views/pages/index_monkey_patch.rbrequire 'active_admin/helpers/collection' module ActiveAdmin module Views module Pages class Index protected def render_blank_slate blank_slate_content = I18n.t("active_admin.blank_slate.content", resource_name: active_admin_config.plural_resource_label) insert_tag(view_factory.blank_slate, blank_slate_content) end end end end end
ソースは下記を参考にしました。
3、
サーバー再起動。
4、
これで、
"%{resource_name} はまだありません。「作成する」の「作成する」が消えているはずです!
- 投稿日:2020-12-16T18:59:22+09:00
has_manyで中間テーブルから親モデルの別のassociationを取ってきたい
概要
Railsを使用してデータを扱う際、殆どこんな機会は訪れない(別の方法などで代替できることがほとんど)ですが、どうしよもない事情で中間テーブルから関連付けを貼る必要があったので備忘録としてメモっておきます。
事前知識
class User < ApplicationRecord has_many :group_users has_many :groups, through: :group_users has_many :comments end class Group < ApplicationRecord has_many :group_users has_many :users, through: :group_users has_many :comments end class Comment < ApplicationRecord belongs_to :user belongs_to :group end class GroupUser < ApplicationRecord belongs_to :group belongs_to :user endこのようなクラスが存在しているとします。
通常であればGroupUserという中間テーブルを起点として何かすることはほとんどないですし、あったとしてもUserやGroup経由でCommentを取得することができるので、困ることはほとんどありません。一方、GraphQLを使用していたり、何かアソシエーションのタイミングで整ったデータが欲しいなどの稀な事情でGroupUserのインスタンスからgroupとuserが絞り込まれたcommentの一覧が欲しい場合があったりしました。
普通にthroughをして取得しようとすると、例えば下記のようなコードになります。
class GroupUser < ApplicationRecord belongs_to :group belongs_to :user has_many :comments, through: :user #groupでもいい endただ、この方法だとthroughしたどちらかのカラムの値では絞り込まれますが、もう片方のカラムでは絞り込みが行われません。
どちらでthroughしたとしても、userかgroupのどちらかでの絞り込みしか行われず、意図した値は取得できません。
解決策
じゃあどうするのかというと、socpeを使います。
class GroupUser < ApplicationRecord belongs_to :group belongs_to :user has_many :comments,->(group_user) { where(group_id: group_user.group_id) }, through: :user #groupならuserでwhere endhas_manyはスコープの引数として自分自身だけは取ることができます。なので、このようにすることでgroup_userインスタンスのgroup_idとuser_idを持ったコメントの一覧を取得することができます。
どうしても中間テーブルを起点にして取得しなければいけない状況になった場合に役立てば幸いです。
- 投稿日:2020-12-16T18:42:31+09:00
Pinterestのように写真を表示する
はじめに
絶賛ポートフォリオ製作中の駆け出しエンジニアです
インスタグラムのような古着専用の写真投稿サイトを作っています
その時に写真の投稿一覧画面でPinterest(https://www.pinterest.jp/) というサイトのように写真を並べるために奮闘しましたので備忘録として残したいと思います
コード
before
post.html.erb<div class = contents-row > <% @items.each do |item| %> <ul class='item-lists'> <li class='list'> <%= link_to items_path(item.id), method: :get do %> <%= image_tag(item.image, class:"item-img") %> <% end %> </li> </ul> <% end %> </div>post.scss.contents-row { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; margin: 0 auto; width: 95vw; } .item-lists { margin: 5px; } .list { width:25vw; } .item-img { width: 100%; height: auto; border-radius: 30px; vertical-align: bottom; }after
post.html.erb<div class = contents-row > <ul class='item-lists'> <% @items.each do |item| %> <li class='list'> <%= link_to items_path(item.id), method: :get do %> <%= image_tag(item.image, class:"item-img") %> <% end %> </li> <% end %> </ul> </div>post.scss.contents-row { display: flex; justify-content: center; align-items: center; margin: 0 auto; width: 95vw; } .item-lists { column-count: 3; column-gap: 15px; column-fill: auto; } .list { width:25vw; } .item-img { width: 100%; height: auto; border-radius: 30px; vertical-align: bottom; margin: 5px; }行ったこと
• 【html】 ulタグがeach文の中に入っていたのでeach文の外に出した
• 【css】 item-listsにcolumn-countなどを記述し、それに合わせてそれぞれ調整した参考サイト
- 投稿日:2020-12-16T18:40:19+09:00
RubyでGoogleの問題を解く
Googleの問題を解く
素数判定
回答(リファクタリング後)
def getNums() line = readlines len = line.length num = '' (0..len-1).each do |i| num = num + line[i].chomp end return num end def prime?(num) (2..Math.sqrt(num).to_i).each do |i| if(num%i == 0) return false end end return true end digits = 10 if $PROGRAM_NAME == __FILE__ num = getNums().delete('.') res = 0 (0..num.length-digits).each do |i| res = num[i, digits].to_i prime?(res) ? break : next end puts "e(自然対数の底)の値で連続する10桁の数のうち, 最初の素数は#{res}" end解説
随時更新
- source ~/Library/Mobile Documents/com~apple~CloudDocs/KG/class/M1/multi_scale_sim/grad_members_20f/members/ryomichi56/./qiita/google_recruit.org
- 投稿日:2020-12-16T18:29:43+09:00
Ruby on Railsプロジェクトの開発環境をDocker化する
ACCESS Advent Calendar 2020 の16日目の記事です。
概要
現在運用中のRuby on Railsプロジェクトの開発環境をDocker化する案件があり、
その際に行った作業の手順をまとめました。Dockerについて
Dockerとは、コンテナと呼ばれる仮想環境を構築・実行できるようにするためのプラットフォームです。
私は今回初めてDockerを触ったのですが、Dockerの理解には入門Dockerが大変参考になりました。
前提
バージョン
- macOS Catalina 10.15.7
- Docker 19.03.13
- docker-compose 1.27.4
- Ruby 2.4.5
- mongoDB 3.0.15
- postgres 10
システム構成
こちらのシステム構成図の通りにDocker化していきます。
(※大分簡略化しています)初めにRailsアプリケーションのDocker Containerを作成し、
その後オーケストレーションツールであるdocker-composeによって、
RailsアプリケーションをmongoDB及びpostgreSQLに接続します。手順
Dockerのインストール
Docker HubよりDocker Desktop for Macを導入します。
ターミナルで下記の2つのコマンドが実行できればOKです。
$ docker -v Docker version 19.03.13, build 4484c46d9d $ docker-compose -v docker-compose version 1.27.4, build 40524192必要なファイルの作成
Docker及びdocker-composeを動かすのに必要なファイルをプロジェクトのルートに作成します。
Dockerfile
ruby 2.4.5環境が予めインストールされている
ruby:2.4.5-slim
というDockerイメージをDocker Hubから取得し、そのイメージ上にRails環境をセットアップしています。DockerfileFROM ruby:2.4.5-slim # Dockerコンテナ上におけるプロジェクトルートを指定 ENV APP_ROOT=/app RUN mkdir $APP_ROOT WORKDIR $APP_ROOT # apt-utilsインストールの時の警告を抑制する # https://qiita.com/haessal/items/0a83fe9fa1ac00ed5ee9 ENV DEBCONF_NOWARNINGS yes # aptパッケージのインストール RUN apt-get update -y -qq && \ apt-get install -y -qq build-essential libpq-dev libmagickwand-dev # Railsのセットアップ COPY Gemfile Gemfile COPY Gemfile.lock Gemfile.lock RUN gem install bundler -v 1.17.3 && bundle install # プロジェクトディレクトリをDocker Imageにコピー COPY . $APP_ROOTdocker-compose.yml
postgres
,mongo
,web
の3つのサービスを定義し、
web
をpostgres
及びmongo
に依存させています。docker-compose.ymlversion: "3" services: # postgreSQL containerの定義 postgres: image: postgres:10 ports: # <Host Port>:<Container Port> - "5432:5432" environment: POSTGRES_USER: xxxxxx POSTGRES_PASSWORD: xxxxxx # mongoDB containerの定義 mongo: image: mongo:3.0.15 ports: - "27017:27017" # Rails app containerの定義 web: build: . env_file: .env # pid error の回避のため、server.pidを削除したのちにrails sを実行 # https://qiita.com/sakuraniumarete/items/ac07d9d56c876601748c command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" # 依存関係の定義 (webをビルドするとpostgresとmongoが同時にビルドされる) depends_on: - postgres - mongoビルド実行
これら2つのファイルを作成すると、
$ docker-compose build
でコンテナをビルドできるようになります。
DBの永続化
現在の状態ではDBがコンテナ内部のストレージに生成されており、
コンテナを削除して再ビルドすると、DBに保存されていたデータは全て消失してしまいます。DB上のデータを永続化するためには、Dockerが提供している
volume
という機能を利用します。
volumeは、Docker Containerのライフサイクルからは独立して生成されるデータ保存領域です。
volume上にDBを生成することにより、コンテナを再ビルドしてもDB上のデータが残り続けるようになります。
引用元: https://matsuand.github.io/docs.docker.jp.onthefly/storage/volumes/volumeを利用するためには、docker-compose.ymlに以下の内容を追記します。
docker-compose.ymlservices: postgres: + volumes: + - "postgres-data:/var/lib/postgresql/data" mongo: + volumes: + - "mongo-data:/data/db" + volumes: + postgres-data: + mongo-data:上記を追記した上で改めてビルドすると、Docker Volumeが作成されているはずです。
$ docker volume ls local mongo-data local postgres-dataホストとコンテナのソースコードを同期
現在の状態では、Dockerfileをビルドした時点で、ホストの全データををImageにコピーしています。
DockerfileCOPY . $APP_ROOTつまり、これより後にホスト側でソースコードを変更した場合、動作しているコンテナを一旦停止させ、
docker-compose build
からやり直す必要があります。開発環境において毎度ビルドからやり直しているのでは非常に効率が悪いので、
ホストのコード変更をコンテナに即時反映できるようにします。よくある方法は、下記のように、プロジェクトのルートディレクトリを無名volumeとしてコンテナにマウントする方法です。
docker-compose.ymlservices: # ${APP_ROOT}はDockerfileにおいてENVで定義した環境変数 web:.:${APP_ROOT}しかし、上記の方法を採った場合の問題として、
Docker for Mac特有のVolume I/Oの遅さが大きく影響してしまうということがあります。
この問題については、docker/for-macのGitHubリポジトリにおいて議論されています。
(この問題により最も影響を受けたのは、RSpecの実行でした。上記の方法でマウントした場合、
普段5分ほどで完了していたテストが30分ほどかかりました...)docker-sync
現状とりうる有効な解決策として、docker-syncというサードパーティライブラリが提供されています。
新たに
docker-sync.yml
とdocker-compose-dev.yml
を作成します。
作成にあたってはdocker-syncのドキュメントを参考にしました。docker-sync.ymlversion: "2" syncs: sync-volume: src: "." sync_excludes: - "log" - "tmp" - ".git"docker-compose-dev.ymlversion: "3" services: web: volumes: - "sync-volume:/app:nocopy" volumes: sync-volume: external: trueDBセットアップ
以下のコマンドで、postgreSQLとmongoDBをセットアップします。
$ docker-compose run --rm -e RAILS_ENV=development -T web rake db:setup $ docker-compose run --rm -e RAILS_ENV=devlopment -T web rake db:mongoid:create_indexes
docker-compose run --rm <container name> <command>
は、
指定したコンテナサービスを起動し、任意のコマンドを実行後、そのコンテナを削除するというコマンドです。
DBはvolumeで永続化されているので、セットアップのためだけにコンテナを作成し、その後削除してしまっても問題ないということです。Railsサーバーの実行
ここまでの手順を実施した上で、下記コマンドを実行することでRailsサーバーが起動します。
$ docker-sync-stack start
これは以下のコマンドを短縮したものです。
$ docker-sync start $ docker-compose -f docker-compose.yml -f docker-compose.yml up
-f
オプションを使って複数のdocker-composeファイルを指定すると、
コンテナ作成時の各種パラメーターを上書きすることができます。
参考: 設定の追加と上書き - Docker-docs-jaまた、上記はフォアグラウンドで起動するためのコマンドで、
バックグラウンドで起動する場合は以下のコマンドを実行します。# 起動 $ docker-sync start $ docker-compose -f docker-compose.yml -f docker-compose.yml up # 停止 $ docker-compose down $ docker-sync stopテスト実行
RSpecを実行するためには、サーバーを起動した状態で以下のコマンドを実行します。
$ docker-compose exec -e COVERAGE=true -T web bundle exec rspec
docker-compose exec
で、起動中のDockerコンテナに対して任意のコマンドを実行できます。もしくは、以下のようにコンテナに入って実行することもできます。
$ docker-compose exec web bash root@container:/app# bundle exec rspecCI対応
CIツールとしてJenkinsを使用しています。
テストを実行するシェルスクリプト
ビルドジョブにおいて、下記のシェルを実行することで自動テストが行われるようにしました。
# 終了時の処理 # docker-composeが失敗した際でもJenkinsビルドマシンにゴミが残らないよう後処理をかける # https://qiita.com/ryo0301/items/7bf1eaf00b037c38e2ea function finally { # Clean project docker-compose down --rmi local --volumes --remove-orphans } trap finally EXIT # 並列実行のために、プロジェクト名としてJenkinsの環境変数である$BUILD_TAGを指定 export COMPOSE_PROJECT_NAME=$BUILD_TAG # 環境変数で予めビルドするdocker-composeファイルを指定することで、 # -fオプションによる指定を省略できる # https://docs.docker.jp/compose/reference/envvars.html export COMPOSE_PATH_SEPARATOR=: export COMPOSE_FILE=docker-compose.yml:docker-compose-test.yml # Build Container docker-compose build --no-cache docker-compose up -d # Setup DB docker-compose exec -e RAILS_ENV=test -T web rake db:setup docker-compose exec -e RAILS_ENV=test -T web rake db:mongoid:create_indexes # Run RSpec docker-compose exec -e COVERAGE=true -T web bundle exec rspecビルドジョブを並列実行できるようにする
RailsプロジェクトをDocker化していない時の問題として、
2つ以上のビルドジョブを並列実行すると、同じマシン上でDBの取り合いが起こり、
エラーが発生する問題がありました。Docker化したことで、それぞれのビルドが独立したコンテナの中で実行されるようになり、
並列実行してもエラーが起こらないようになります。
ただし、並列ビルドの実行時にコンテナのポート番号が重複しないよう、
ポートフォワーディングの設定を変更する必要があります。
参考: ホスト上にコンテナのポートを割り当て - Docker-docs-jaexport COMPOSE_FILE=docker-compose.yml:docker-compose-test.ymlで指定している
docker-compose-test.yml
の中身でそれを行っています。docker-compose-test.ymlversion: "3" services: postgres: ports: - "5432" mongo: ports: - "27017" web: ports: - "3000"また、
docker-compose.yml
に記載したポート番号をdocker-compose-dev.yml
に移動する必要があります。
なぜなら、このままdocker-compose up -d
を実行すると、docker-compose.yml
とdocker-compose-test.yml
がマージされ、
結果としてポートの指定が以下のようになってしまい、docker-compose-test.yml
でわざわざポート指定した意味がなくなってしまうためです。services: postgres: ports: - "5432:5432" - "5432" mongo: ports: - "27017:27017" - "27017" web: ports: - "3000:3000" - "3000"Railsサーバーの実行の項で、
-f
オプションを使って複数のdocker-composeファイルを指定すると、
コンテナ作成時の各種パラメーターを上書きすることができます。と述べましたが、複数指定可能なパラメータの場合は設定値は上書きされずにマージされるので注意が必要です。
docker-compose.ymlservices: postgres: - ports: - - "5432:5432" mongo: - ports: - - "27017:27017" web: - ports: - - "3000:3000"docker-compose-dev.ymlservices: postgres: + ports: + - "5432:5432" mongo: + ports: + - "27017:27017" web: + ports: + - "3000:3000"おまけ: RubyMineへの対応
JetBrainsのIDEであるRubyMineは、Docker Container上のRuby on Railsの開発環境に完全対応しており、以下の手順で設定することができます。
チュートリアル : リモートインタープリターとしての Docker Compose — RubyMineまとめ
今回新たに作成したファイル
開発環境をDocker化するにあたり、新たに作成したファイルは以下の通りです。
. ├── Dockerfile ├── docker-compose.yml ├── docker-compose-dev.yml ├── docker-compose-test.yml └── docker-sync.ymlその他
今回初めてコンテナ技術に触れ、Docker化にあたっては様々な試行錯誤を重ねました。
これまでに書いた中で、もっと良い対応方法がある、或いは対応方法として正しくない箇所があるかもしれませんが、その時はご指摘いただければ幸いです。
- 投稿日:2020-12-16T18:26:49+09:00
Rubyでローマ数字を求める
ローマ数字(roman numerals)
アラビア数字を入力としてローマ数字を返すメソッドを作成する。
回答(リファクタリング前)
無駄が多いし長くて見ない方がいいレベルなので折り畳んでます。
def getNums() lines = readlines len = lines.length (0..len-1).each do |i| lines[i] = lines[i].chomp.to_i end return lines end class Integer def to_roman sym = ['I', 'V', 'X', 'L', 'C', 'D', 'M'] roman = '' to_arr(4).each_with_index do |num, i| case i when 0 then roman += sym[6] * num when 1 then case num when 0..3 then roman += sym[4] * num when 4 then roman += sym[4] + sym[5] when 5..8 then roman += sym[5] + sym[4] * (num-5) when 9 then roman += sym[4] + sym[6] end when 2 then case num when 0..3 then roman += sym[2] * num when 4 then roman += sym[2] + sym[3] when 5..8 then roman += sym[3] + sym[2] * (num-5) when 9 then roman += sym[2] + sym[4] end when 3 then case num when 0..3 then roman += sym[0] * num when 4 then roman += sym[0] + sym[1] when 5..8 then roman += sym[1] + sym[0] * (num-5) when 9 then roman += sym[0] + sym[2] end end end roman end def to_arr(n) arr = [] (Math.log10(self).to_i + 1).upto(n-1) do arr.unshift(0) end arr + digits.reverse end end if $PROGRAM_NAME == __FILE__ getNums().each do |i| puts i.to_roman end end回答(リファクタリング後)
def getNums() lines = readlines (0..lines.length-1).each do |i| lines[i] = lines[i].chomp.to_i end lines end class Integer def to_roman sym = ['I', 'V', 'X', 'L', 'C', 'D', 'M'] roman = '' base = sym.length-1 to_arr(4).each_with_index do |num, i| case num when 0..3 then roman += sym[base-i*2] * num when 4 then roman += sym[base-i*2] + sym[base-i*2+1] when 5..8 then roman += sym[base-i*2+1] + sym[base-i*2] * (num-5) when 9 then roman += sym[base-i*2] + sym[base-(i-1)*2] end end roman end def to_arr(n) arr = Array.new(n - Math.log10(self).to_i - 1, 0) arr + digits.reverse end end if $PROGRAM_NAME == __FILE__ getNums().each do |i| # puts "#{i} => #{i.to_roman}" printf("%4d => %s\n", i, i.to_roman) end end解説
def getNums() lines = readlines (0..lines.length-1).each do |i| lines[i] = lines[i].chomp.to_i end lines end随時更新
- source ~/Library/Mobile Documents/com~apple~CloudDocs/KG/class/M1/multi_scale_sim/grad_members_20f/members/ryomichi56/./qiita/roman_numerals.org
- 投稿日:2020-12-16T18:14:47+09:00
Sprockets::DoubleLinkError を解消した方法
やんばるエキスパートというオンラインスクールにて教材に取り組んでいた時の出来事です。
Sprockets::DoubleLinkErrorの出現
Railsのメッセージ投稿アプリを作成しており、その中でstylesheetsフォルダのcssファイルをscssファイルに拡張子を変更し、動作確認を行ったところ、「Sprockets::DoubleLinkError」が出現しました。
原因はcssファイルとscssファイルの干渉
stylesheetsフォルダ内にcssファイル、min.cssファイル、scssファイルの3つが存在しており、cssファイルとscssファイルが干渉してエラーが起こっていたようです。
cssファイルとmin.cssファイルを削除し、動作確認を行った所、エラーは解消されました。
しかし、、、
拡張機能Easy Sassにも注意
VScodeを再起動すると、再度cssファイルとmin.cssファイルが生成されていました。
拡張機能にEasy Sassを入れており、どうやらこいつが悪さをしていたようです。
Easy Sassを無効化した所、cssファイルとmin.cssファイルは自動生成されなくなりました。そして、エラーも出ず、問題なく動作できるようになりました。
- 投稿日:2020-12-16T17:56:47+09:00
【Active Admin】Unable to find input class JsonInput
エラー内容
json型のDBカラムを、
active_adminの管理画面フォームで表示させようとすると、
Unable to find input class JsonInput のエラー。
- backtraceの一部
@input_class_finder.find(as) rescue Formtastic::InputClassFinder::NotFoundError raise Formtastic::UnknownInputError, "Unable to find input #{$!.message}" end # @api private
解決方法
Formtasticのクラスを継承した、
XxxxInputというクラスを作れば良い。
今回の場合は、
エラー文の通り、(Unable to find input class JsonInput)
JsonInputというクラスを作ればOK。
# app/inputs/json_input.rb class JsonInput < Formtastic::Inputs::StringInput; end
その後、
サーバーを再起動してエラーは出なくなりました。
参考
- 投稿日:2020-12-16T17:46:48+09:00
【Ruby】ifのない条件式の?(クエスチョンマーク)の意味。三項演算子の使い方。
個人メモです。
以下のように、ifがなく、
?
(はてな)と:
(コロン)が記述された式の意味を理解する。・
x = y == "aaa" ? v : w
三項演算子
?
(はてな)と:
(コロン)を使った式は、if else文の省略形で三項演算子と呼ぶ。以下の2つの処理は同じものとなる。
通常if 条件式 戻り値1 else 戻り値1 end三項演算子条件式 ? 戻り値1 : 戻り値2(1)条件式、(2)戻り値1、(3)戻り値2の3つの項目があるため、三項演算子と呼ぶ。
参考演算子の実例
条件式がtrueの場合は戻り値1が返る。
x = 10 x == 10 ? "Yes" : "No" => "Yes"条件式がfalseの場合は戻り値2が返る。
x = 10 x != 10 ? "Yes" : "No" => "No"
x = y == "aaa" ? v : w
の処理内容これは、三項演算子の結果を変数xに代入する処理となる。
y = "aaa" v = "Yes" w = "No" x = y == "aaa" ? v : w p x => "Yes"
y == "aaa"
がtrueなので前方の戻り値v
をx
に代入する。
- 投稿日:2020-12-16T17:31:49+09:00
【Ruby】uniqメソッドで重複する要素を除外する。
個人メモです。
uniqメソッドを使うと重複する要素を排除することができる。
- 非破壊処理。
- ブロックを使って、各要素を処理したあとで比較できる
- 重複する場合は、最初の要素が残る。(後ろの要素を削除)
配列の場合
arr = [1, 4, 5, 3, 5, 6, 1, 3, 2, 2, 1, 1, 1] arr.uniq => [1, 4, 5, 3, 6, 2] ##非破壊の確認 p arr => [1, 4, 5, 3, 5, 6, 1, 3, 2, 2, 1, 1, 1]
▼処理後の値
ブロックを使うと処理後の値を比較できる。削除するのは該当する元の値。(※処理後ではない)・
オブジェクト.uniq{|変数| 処理}
例えば、文字列が入った要素を、
to_s
で全ての要素を文字列にしてから比較する。arr1 = [1, 3, 2, "2", "3", 1] arr1.uniq{|i| i.to_s} => [1, 3, 2]
元の値 1 3 2 "2" "3" 1 処理後 "1" "3" "2" "2" "3" "1" 残る要素 ○ ○ ○ × × × よって出力は重複なしか1つ目に該当する元の値
[1, 3, 2]
となる。
オブジェクトの場合
オブジェクトで値が重複している要素の削除もできる。
・
オブジェクト.uniq{|変数1, 変数2| 処理}
- ブロックは必須
- 変数は2つ必須。変数1にキー、変数2に値が入る。
- 出力は配列になるため、
to_h
でオブジェクト(シンボル)に戻す。シンボルにする場合
obj = {:a=>1, :b=>2, :c=>3, :d=>2, :f=>3} obj2 = obj.uniq{|x,y| y} => [[:a, 1], [:b, 2], [:c, 3]]} obj2.to_h => {:a=>1, :b=>2, :c=>3}
ハッシュにする場合
ハッシュにしたい場合は、
to_h
メソッドのブロックでキーにto_s
を適用し文字列にする。obj = {:a=>1, :b=>2, :c=>3, :d=>2, :f=>3} obj.uniq{|x,y| y}.to_h{|k ,v| [k.to_s, v]} => {"a"=>1, "b"=>2, "c"=>3}