20200710のRubyに関する記事は23件です。

Digdag公式ドキュメントからDigdagを学ぶ-アーConcepts

目標

Digdagの公式サイトのドキュメントのConceptsの翻訳+α
http://docs.digdag.io/concepts.html

#目次

Getting started
Architecture
Concepts
Workflow definition
Scheduling workflow
Operators
Command reference
Language API -Ruby
REST API
Internal architecture
Release Notes

Projects and revisions

ワークフローはワークフローで使用される他のファイルと一緒にパッケージ化されます。
ファイルはSQLスクリプト、Python / Ruby / Shellスクリプト、構成ファイルなど何でもかまいません。
このワークフロー定義セットをプロジェクトと呼ばれます。

プロジェクトがDigdagサーバーにアップロードされるとDigdagサーバーは新しいバージョンを挿入し古いバージョンを保持します。プロジェクトのバージョンはリビジョンと呼ばれます。ワークフローを実行するとDigdagはデフォルトで最新のリビジョンを使用します。ただし、次の目的で古いリビジョンを使用することも可能です。

1. 過去のワークフロー実行の定義を確認する目的。
2. 以前のリビジョンを使用してワークフローを実行し以前と同じ結果を再現する目的
3. 最新バージョンで起きた問題を解決するため古いリビジョンに戻す目的

プロジェクトには複数のワークフローを含めることができます。ただし、新しいワークフローが他のワークフローに関連していない場合は、新しいプロジェクトを作成する必要があります。理由は、新しいリビジョンをアップロードすると、プロジェクト内のすべてのワークフローが一緒に更新されるためです。

Sessions and attempts

セッションは、正常に完了するはずのワークフローの実行計画です。Attemptとはセッションの実行を意味する。失敗したワークフローを再実行するとセッションは複数の試行を持つます。
セッションは実行計画、試行はその実行計画を実行するのを意味している。

セッションと試行を分離する理由は、実行が失敗する可能性があるためです。
セッションをリストアップすると、予想されるステータスはすべてのセッションが緑になることです。
失敗したセッションを見つけたらその試行をチェックしログから問題をデバッグします。
問題を修正するために新しいリビジョンをアップロードした後新しい試行を開始できます。
セッションでは、計画されたすべての実行が正常に行われたことを簡単に確認できます。
スクリーンショット 2020-07-09 21.32.04.png

Scheduled execution and session_time

セッションにはsession_timeというタイムスタンプがあります。ワークフローの実行開始時間を意味します。

session_timeはワークフローの履歴で一意です。同じsession_timeで2つのセッションを送信すると後の要求は拒否されます。これにより以前に同時に実行されたセッションが誤って送信されるのを防ぎます。
ワークフローを同時に実行する必要がある場合は、新しいセッションを送信するのではなく過去のセッションを再試行する必要があります。

Task

セッションの試行が開始されると、ワークフローはタスクセットに変換されます。
タスクには相互に依存関係があります。Digdagは依存関係を理解し​​タスクを順番に実行します。

Export and store parameters

1. local: タスクに直接設定されるパラメーター
2. export: 親タスクからエクスポートされるパラメーター
3. store: 前のタスクで保存されたパラメーター

上記のパラメーターはタスクの実行時に1つのオブジェクトにマージされます。localパラメータが最も優先されます。
exportとstoreパラメーターはお互いを上書きするため、後のタスクで設定されたパラメーターの優先度が高くなります。

exportパラメーター は、親タスクが子に値を渡すために使用します。
storeパラメーター は、子を含む後続のすべてのタスクに値を渡すタスクで使います。

exportパラメータの影響はstoreパラメータに比べて制限されています。これにより、ワークフローをモジュール化できます。たとえば、ワークフローはいくつかのスクリプトを使用してデータを処理します。
スクリプトの動作を制御するために、スクリプトにいくつかのパラメーターを設定できます。一方、他のスクリプトをパラメータの影響を受けないようにする必要があります(たとえば、データ読み込み部分はデータ処理部分の変更の影響を受けてはなりません)。この場合、スクリプトを単一の親タスクの下に置き、親タスクにパラメーターをexportさせることができます。

storeパラメータは、以降のすべてのタスクで表示されます-storeパラメータは、前のタスクでは表示されません。たとえば、ワークフローを実行して再試行したとします。この場合、タスクによって保存されたパラメーターは、タスクが最後の実行で正常に終了した場合でも、以前のタスクからは見えません。

storeパラメータはグローバル変数ではありません。 2つのタスクが並行して実行される場合、それらは異なるstoreパラメータを使用します。これにより、実際の実行タイミングに関係なく、ワークフローの動作が一貫します。たとえば、2つの並列タスクに依存して別のタスクが実行された場合、最後のタスクによって保存されたパラメーターがタスクの送信順に使用されます。

Operators and plugins

オペレーターはタスクの実行者です。 オペレーターは、ワークフロー定義でsh>、pg>などとして設定されます。
タスクが実行されると、Digdagは1つのオペレーターを選択し、すべてのパラメーター(local、export、およびstoreパラメーター)をマージしてマージされたパラメーターをオペレーターに渡します。

オペレーターは、一般的なワークロードのパッケージと見なすことができます。 オペレーターを使用するとスクリプトより多くのことができます。

オペレーターはプラグインとして設計されています(ただし、まだ完全には実装されていません)。 オペレーターをインストールしてワークフローを簡略化し、他のワークフローで再利用できるようにオペレーターを作成します。
Digdagは多くのオペレーターを実行するためのシンプルなプラットフォームです。

Dynamic task generation and _check/_error tasks

Digdagはワークフローを依存関係のある一連のタスクに変換します。 このタスクのグラフは、DAG、Directed Acyclic Graph(有向非巡回グラフ)と呼ばれます。 DAGは、最も依存性の高いタスクから最後まで実行するのに適しています。

ただし、ループを表すことはできません。 IF分岐を表現するのも簡単ではありません。

しかし、ループと分岐は便利です。 この問題を解決するために、Digdagは実行中のDAGにタスクを動的に追加します。
例)
Digdagはループを表す3つのタスクを生成します:+example^sub+loop-0、+example^sub+loop-1、+example^sub+loop-2(動的に生成されたタスクの名前は^subが追加される):

+example:
  loop>: 3
  _do:
    echo>: this is ${i}th loop
実行結果
2020-07-10 20:48:11 +0900 [INFO] (0017@[0:default]+mydag+example): loop>: 3
2020-07-10 20:48:12 +0900 [INFO] (0017@[0:default]+mydag+example^sub+loop-0): echo>: this is 0th loop
this is 0th loop
2020-07-10 20:48:12 +0900 [INFO] (0017@[0:default]+mydag+example^sub+loop-1): echo>: this is 1th loop
this is 1th loop
2020-07-10 20:48:12 +0900 [INFO] (0017@[0:default]+mydag+example^sub+loop-2): echo>: this is 2th loop
this is 2th loop

_checkおよび_errorオプションは、動的タスク生成を使用します。 これらのパラメーターは、タスクが成功または失敗した場合にのみ別のタスクを実行するためにDigdagによって使用されます。

_checkタスクは、タスクが正常に完了した後に生成されます。 これは、次のタスクを開始する前にタスクの結果を検証する場合に特に役立ちます。

_errorタスクは、タスクの失敗後に生成されます。 これは、タスクの失敗を外部システムに通知するのに役立ちます。

次の例は、後続のタスクの成功を出力します。 またタスクの失敗に失敗したとメッセージを出力します。

+example:
  sh>: echo start
  _check:
    +succeed:
      echo>: success
  _error:
    +failed:
      echo>: fail
実行結果(success)
2020-07-10 21:05:33 +0900 [INFO] (0017@[0:default]+mydag+example): sh>: echo start
start
2020-07-10 21:05:33 +0900 [INFO] (0017@[0:default]+mydag+example^check+succeed): echo>: success
success

errorを発生させるためyour_script.shを削除

実行結果(error)
2020-07-10 20:56:49 +0900 [INFO] (0017@[0:default]+mydag+example^error+failed): echo>: fail
fail
2020-07-10 20:56:49 +0900 [INFO] (0017@[0:default]+mydag^failure-alert): type: notify
error: 

Task naming and resuming

試行中のタスクには一意の名前があります。
試行を再試行するとこの名前は最後の試行でのタスクの照合に使用されます。

子タスクには、親タスクの名前がプレフィックスとして付いています。 ワークフロー名もルートタスクとしてプレフィックスされます。 次の例では、タスク名は+ my_workflow+load+from_mysql+tables、+my_workflow+load+from_postgres、および+my_workflow+dumpになります。

my_workflow.dig
+load:
  +from_mysql:
    +tables:
        sh>: echo tables
  +from_postgres:
    sh>: echo from_postgres
+dump:
   sh>: echo dump
結果
2020-07-10 21:12:12 +0900 [INFO] (0017@[0:default]+my_workflow+load+from_mysql+tables): sh>: echo tables
tables
2020-07-10 21:12:13 +0900 [INFO] (0017@[0:default]+my_workflow+load+from_postgres): sh>: echo from_postgres
from_postgres
2020-07-10 21:12:13 +0900 [INFO] (0017@[0:default]+my_workflow+dump): sh>: echo dump
dump

Workspace

ワークスペースは、タスクが実行されるディレクトリです。
Digdagは、プロジェクトアーカイブからこのディレクトリにファイルを抽出し、そこでディレクトリを変更して、タスクを実行します(注:ローカルモードの実行では、現在の作業ディレクトリがワークスペースであると想定されているため、ワークスペースを作成することはありません)。

プラグインは、ワークスペースの親ディレクトリへのアクセスを許可しません。 これは、digdagサーバーが共有環境で実行されているためです。 プロジェクトは外部環境に依存する必要がないように自己完結型である必要があります。 スクリプトオペレーターは例外です(例:sh>オペレーター)。 docker:オプションを使用してスクリプトを実行することをお勧めします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

商品情報編集機能を実装したい~part2~

前提

  • ruby on rails 6.0.0 を使用。
  • ユーザー機能はdeviseにより導入されているものとする。
  • viewファイルは全てhaml形式とする。
  • ちなみに使っているのはMacBook Air(Retina, 13-inch, 2020)です。

はじめに

前置きや手順などは part1 に記述してあるので早速実装の続きをしていこうと思います。気になったら是非読んでネ。
part2 でやることとしては手順の2番目にあたる「imageテーブルを関連づけて画像の投稿を実装」というところです。

ではさっそく、

本日もはりきってやっていきましょう。

$ rails g model image
$ rails db:migrate

まずはimageモデルを作ってマイグレートするところから。
前回と同じくマイグレーションファイルの記述等は省略しています。

次にモデル構造の説明をしていきます。

app/models/product.rb
class Product < ApplicationRecord
  belongs_to :user
  has_many :images
end
app/models/image.rb
class Image < ApplicationRecord
  belongs_to :product
end

上記を見ていただく通り、1つのproductモデルに対して、複数のimageモデルを持つことができる、1対多の関係になっています。(わかりやすいようバリデーションは省略しております)
imageテーブルでは1枚の画像に1つのレコードが対応していて、それぞれにproduct_idを紐づけているといった形です。

ざっと構造が把握できたところでローカル環境で画像をアップロードする準備を行っていきます。

Gemfile
~省略~
gem 'carrierwave'
gem 'mini_magick'

まずはgemの導入からです。画像をアップロードするために、今回は carrierwave の uploader を使っていきたいと思います。

$ bundle install
$ rails g uploader image

忘れずに bandle install を行ってから、uploaderファイルを生成します。これらをしっかり使うために、生成したuploaderファイルと先ほどのモデルに記述を追加していきましょう。

app/models/image.rb
class Image < ApplicationRecord
  belongs_to :product
  mount_uploader :src, ImageUploader
end

このように記述することで、imageモデルのsrcカラムにおいてImageUploaderを使用することが可能になります。

app/uploaders/image_uploader.rb
include CarrierWave::MiniMagick
process resize_to_fit: [50, 50]

こちらはMiniMagickの設定です。アップロードした画像のサイズを調整できる便利なgemですね。
上記の2行は新しく追加するというより、コメントアウトの中からこの記述を見つけてコメントアウトを外していただく形になります。(resizeの中身は自由に調節していただいて構いません)

ここまででアップロード自体はできるようになったので、実際に画像を投稿するフォームを作っていきます。

app/models/product.rb
class Product < ApplicationRecord
  belongs_to :user
  has_many :images, dependent: :destroy
  accepts_nested_attributes_for :images, allow_destroy: true
end
app/views/products/_form.html.haml
= form_with model: @product, local: true do |f|
  = f.text_field :name, placeholder: 'name'
  = f.fields_for :images do |i|
    = i.file_field :src
  = f.submit 'SEND'

fields_forとやらが出てきました。こちらは1つのテーブルに対して、それに紐づいた複数のテーブルに同時にデータを保存することができるフォームヘルパーです。
使い方としては、まず親となるモデルに、accepts_nested_attributes_for という記述を追加します。因数にはそれに紐づく子モデルをもってきましょう。後ろに続く allow_destroy の部分は、trueにすると親レコードが削除された場合に、関連する子レコードも同時に削除してくれるといった便利な機能です。

app/controllers/products_controller.rb
#~省略~
def new
  @product = Product.new
  @product.images.new
end
#~省略~
def product_params
  params.require(:product).permit(:name, images_attributes: [:src]).merge(user_id: current_user.id)
end 

扱うデータに伴って、コントローラーにも少し修正を加えます。
まずnewアクションの部分では、productに関連する新しいimageインスタンスを生成する記述を追加しています。これがないとimageに正しく画像が保存されないので注意です。
そしてストロングパラメータですね。こちらは少し特殊な記述をしています。attributes: [: ] というのは関連づくモデルのカラムを指定するというものです。
フォームヘルパーのfields
forを使用した際にセットで使用するものと考えていただければ大丈夫だと思います。

これで画像の投稿機能は実装できたことになります。
ただこのままだと画像が1枚しか選ぶことができないので次回そこをアップグレードしていきたいと思います。

最後に

今回はimageモデルを作成して実際にフォームで画像を送り、データベースに保存するところまで実装しました。

文字だけではわかりにくいと思ったので参考画像やgifなんかも付け足したいなと思いました。今は少し忙しいので余裕ができたらやろうと思います。(多分)

次のpartではとうとうjQueryを導入していきます。表示をあれこれいじって画像を複数投稿できるフォームを作ろう!といった感じですね。頑張ります。

それではまた次のpartで。ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像の登録?もちろんできません(できる)~part2~

前提

  • ruby on rails 6.0.0 を使用。
  • ユーザー機能はdeviseにより導入されているものとする。
  • viewファイルは全てhaml形式とする。
  • ちなみに使っているのはMacBook Air(Retina, 13-inch, 2020)です。

はじめに

前置きや手順などは part1 に記述してあるので早速実装の続きをしていこうと思います。気になったら是非読んでネ。

part2 でやることとしては手順の2番目にあたる「imageテーブルを関連づけて画像の投稿を実装」というところです。

別テーブルに画像を登録するだけなのに実装するのに3万年くらいかかったので正しい手順を後世に残したいと思います。

ではさっそく、

本日もはりきってやっていきましょう。

$ rails g model image
$ rails db:migrate

まずはimageモデルを作ってマイグレートするところから。
前回と同じくマイグレーションファイルの記述等は省略しています。

次にモデル構造の説明をしていきます。

app/models/product.rb
class Product < ApplicationRecord
  belongs_to :user
  has_many :images
end
app/models/image.rb
class Image < ApplicationRecord
  belongs_to :product
end

上記を見ていただく通り、1つのproductモデルに対して、複数のimageモデルを持つことができる、1対多の関係になっています。(わかりやすいようバリデーションは省略しております)
imageテーブルでは1枚の画像に1つのレコードが対応していて、それぞれにproduct_idを紐づけているといった形です。

ざっと構造が把握できたところでローカル環境で画像をアップロードする準備を行っていきます。

Gemfile
~省略~
gem 'carrierwave'
gem 'mini_magick'

まずはgemの導入からです。画像をアップロードするために、今回は carrierwave の uploader を使っていきたいと思います。

$ bundle install
$ rails g uploader image

忘れずに bandle install を行ってから、uploaderファイルを生成します。これらをしっかり使うために、生成したuploaderファイルと先ほどのモデルに記述を追加していきましょう。

app/models/image.rb
class Image < ApplicationRecord
  belongs_to :product
  mount_uploader :src, ImageUploader
end

このように記述することで、imageモデルのsrcカラムにおいてImageUploaderを使用することが可能になります。

app/uploaders/image_uploader.rb
include CarrierWave::MiniMagick
process resize_to_fit: [50, 50]

こちらはMiniMagickの設定です。アップロードした画像のサイズを調整できる便利なgemですね。
上記の2行は新しく追加するというより、コメントアウトの中からこの記述を見つけてコメントアウトを外していただく形になります。(resizeの中身は自由に調節していただいて構いません)

ここまででアップロード自体はできるようになったので、実際に画像を投稿するフォームを作っていきます。

app/models/product.rb
class Product < ApplicationRecord
  belongs_to :user
  has_many :images, dependent: :destroy
  accepts_nested_attributes_for :images, allow_destroy: true
end
app/views/products/_form.html.haml
= form_with model: @product, local: true do |f|
  = f.text_field :name, placeholder: 'name'
  = f.fields_for :images do |i|
    = i.file_field :src
  = f.submit 'SEND'

fields_forとやらが出てきました。こちらは1つのテーブルに対して、それに紐づいた複数のテーブルに同時にデータを保存することができるフォームヘルパーです。
使い方としては、まず親となるモデルに、accepts_nested_attributes_for という記述を追加します。因数にはそれに紐づく子モデルをもってきましょう。後ろに続く allow_destroy の部分は、trueにすると親レコードが削除された場合に、関連する子レコードも同時に削除してくれるといった便利な機能です。

app/controllers/products_controller.rb
#~省略~
def new
  @product = Product.new
  @product.images.new
end
#~省略~
def product_params
  params.require(:product).permit(:name, images_attributes: [:src]).merge(user_id: current_user.id)
end 

扱うデータに伴って、コントローラーにも少し修正を加えます。
まずnewアクションの部分では、productに関連する新しいimageインスタンスを生成する記述を追加しています。これがないとimageに正しく画像が保存されないので注意です。
そしてストロングパラメータですね。こちらは少し特殊な記述をしています。attributes: [: ] というのは関連づくモデルのカラムを指定するというものです。
フォームヘルパーのfields
forを使用した際にセットで使用するものと考えていただければ大丈夫だと思います。

これで画像の投稿機能は実装できたことになります。
ただこのままだと画像が1枚しか選ぶことができないので次回そこをアップグレードしていきたいと思います。

最後に

今回はimageモデルを作成して実際にフォームで画像を送り、データベースに保存するところまで実装しました。

文字だけではわかりにくいと思ったので参考画像やgifなんかも付け足したいなと思いました。今は少し忙しいので余裕ができたらやろうと思います。(多分)

次のpartではとうとうjQueryを導入していきます。表示をあれこれいじって画像を複数投稿できるフォームを作ろう!といった感じですね。頑張ります。

それではまた次のpartで。ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】多対多のアソシエーションを用いてコミュニティ加入の申請/承認機能の実装を行う

実施したいこと

申請・承認の仕組みを実装する

具体的には?

「ユーザ(小西)が、とあるコミュニティに加入したいと思いますが、
加入にはコミュニティの管理者(大西)の承認が必要になる」といったケースでの実装例です。

ざっくりイメージ

キャプチャ.PNG

処理フロー概要

  1. 該当コミュニティ画面にて、ユーザ(小西)が「申請」ボタンを押す。
  2. 該当コミュニティの申請待ちリストにユーザ(小西)が追加される。管理者が、該当コミュニティの申請待ち一覧画面を見て、申請者を承認するかどうか判断。
  3. 申請を承認する場合は、承認ボタンを押す。申請ユーザがコミュニティに加入し、申請待ちリストからは消去される。
  4. 申請を却下する場合は、否認ボタンを押す。申請待ちリストから消去される。

環境

Ruby 2.6.5
Rails 5.2.4.2
mysql 5.7

現在のアプリケーション機能の構成

ユーザが色々なコミュニティに所属できるアプリケーションを作成しています。
- userモデル:アプリの利用ユーザ情報を保持
- communityモデル:コミュニティ情報を保持
- belongingモデル:ユーザが所属するコミュニティ情報を保持。ユーザは複数のコミュニティに所属できます。
※userとcommunityはbelongingモデルを介して多対多の関係となっています。

以下ソースは関連部分のみ抜粋

user.rb
class User < ApplicationRecord

  has_many :belongings, dependent: :destroy
  has_many :applies, dependent: :destroy
  has_many :communities, through: :belongings # ユーザが所属しているコミュニティ

end

community.rb
class Community < ApplicationRecord
    has_many :belongings, dependent: :destroy
    has_many :applies, dependent: :destroy
    has_many :users, through: :belongings # コミュニティに所属しているユーザ

    # ユーザがコミュニティに所属していればtrueを返す
    def user_belonging?(user)
      users.include?(user)
    end

end

belonging.rb
class Belonging < ApplicationRecord
    belongs_to :user
    belongs_to :community
    validates :user_id, presence: true
    validates :community_id, presence: true

    validates  :user_id, uniqueness: { scope: :community_id }
    validates  :community_id, uniqueness: { scope: :user_id }

end

Applyモデルを追加し、申請状況を保持させる。

Applyモデルのルーティングやコントローラ、ビューファイルを追加します。

ルーティング(抜粋)

routes.rb
Rails.application.routes.draw do
  root 'home#index'

  resources :communities do
    resources :applies, only: %i[index create destroy]
    resources :belongings, only: %i[index create destroy]
  end

  resources :users, only: [:index, :show]

end

1.該当コミュニティ画面にて、ユーザ(小西)が「申請」ボタンを押す。

  • 申請ボタン(コミュニティ詳細画面に配置) (views/communities/show.html.erb)
show.html.erb
<!-- ログインユーザが当該コミュニティに所属している場合 -->
<% if @community.user_belonging?(current_user) %>
    <%= link_to '退会する', community_belonging_path(@community, @belonging), method: :delete, data:{ confirm: "コミュニティ「#{@community.name}」を退会します。よろしいですか?" } ,class:"mini-red-link-btn font-bold text-line-none" %>
<!-- 当該コミュニティには所属していないが、ログインはしている場合 -->
<% elsif current_user %>
    <% if @apply %>
        <%= link_to '申請取消', community_apply_path(@community, @apply), method: :delete, class: "mini-red-link-btn font-bold text-line-none" %>
    <% else %>
        <%= link_to '加入申請', community_applies_path(@community), method: :post, class: "mini-green-link-btn font-bold text-line-none" %>
    <% end %>

申請ボタンは、以下の条件で表示させます。
1. コミュニティにすでに加入している場合:退会するボタンが表示
2. コミュニティにまだ加入していない場合:申請ボタンが表示
3. すでに加入申請済の場合:申請取消ボタンを表示

  • 申請ボタン押下後(createアクションが発生)
applies_controller.rb
class AppliesController < ApplicationController

  def create
    current_user.applies.create(community_id: apply_params[:community_id])
    redirect_to community_url(apply_params[:community_id]), notice: "加入申請しました"
  end

  private

    def apply_params
      params.permit(:community_id)
    end

end

Ajaxは使わず、同画面へのリダイレクトが発生するようにします。
(Ajaxを使ってもよさそう)

これで、applyモデルに申請情報が追加されました。

2.該当コミュニティの申請待ちリストにユーザ(小西)が追加される。管理者が、該当コミュニティの申請待ち一覧画面を見て、申請者を承認するかどうか判断。

次に、申請待ちリストの確認です。
管理者(大西)が該当コミュニティ画面から「申請待ち一覧画面」を開きます。

  • 申請待ち画面へのリンク(コミュニティ詳細画面) (views/communities/show.html.erb)
show.html.erb
<% if user_admin_flg(current_user,@community) == 1 %>
    <%= link_to "承認待ち一覧", community_applies_path(@community), class:"btn btn-primary" %>
<% end %>

リンクをクリックすると、申請待ち一覧画面が開きます。

  • 申請待ち一覧画面 (views/applies/index.html.erb)
index.html.erb
<div class="container applicant-wrapper">
    <h3>承認待ちユーザ一覧</h3>
    <div class="row">
        <% @applies.each do |app| %>
        <div class="col-6">
            <% if app.user.image.attached? %>
                <%= link_to app.user.image, user_path(app.user), class:"user-icon" %>
            <% else %>
                <%= link_to user_path(app.user) do %>
                    <%= image_tag ("no_image.png"), class:"user-icon" %>
                <% end %>
            <% end %>
            &nbsp<%= app.user.username %><br>
            <%= link_to "承認", community_belongings_path(app.community, user_id: app.user.id, apply_id: app.id), method: :post, class:"mini-green-link-btn font-bold text-line-none" %>
            <%= link_to "却下", community_apply_path(app.community, app), method: :delete, class:"mini-red-link-btn font-bold text-line-none" %>
            <br>
        </div>
        <% end %>
    </div>
</div>

applyモデルから、該当コミュニティへの申請情報を一覧表示させています。
同画面において、承認ボタン、却下ボタンも配置しています。

3.申請を承認する場合は、承認ボタンを押す。申請ユーザがコミュニティに加入し、申請待ちリストからは消去される。

承認ボタンを押すと、以下の処理が実行されます。
1. belongingsコントローラのcreateアクションが実行される。
2. 申請待ち一覧から削除するため、Applyモデルの該当の申請情報が削除される。
3. 申請待ち一覧画面へリダイレクト

  • belongingsコントローラ
belongings_controller.rb
class BelongingsController < ApplicationController

    def create
        @belonging = Belonging.create(community_id: belonging_params[:community_id], user_id: belonging_params[:user_id])
        Apply.find(belonging_params[:apply_id]).destroy!
        redirect_to community_applies_url(@belonging.community), notice:"「#{@belonging.user.username}」が、コミュニティ:#{@belonging.community.name}へ加入しました。"
    end

    private

        def belonging_params
            params.permit(:community_id, :user_id, :apply_id)
        end

end

4.申請を却下する場合は、否認ボタンを押す。申請待ちリストから消去される。

却下ボタンを押すと、以下の処理が実行されます。
1. 申請待ち一覧から削除するため、appliesコントローラのdestroyアクションが実行され、Applyモデルの該当の申請情報が削除される。
2. 申請待ち一覧画面へリダイレクト

  • appliesコントローラ
applies_controller.rb
class AppliesController < ApplicationController

  def destroy
    @apply = Apply.find(params[:id])
    @apply.destroy!
    @comminity = Community.find(params[:community_id])
    redirect_to community_url(@comminity), notice: "加入申請を取り消しました"
  end

end

以上になります。

文中でappliesコントローラとbelongingsコントローラはアクション毎に分けて紹介していますが、最後に全部まとめて載せておきます。

applies_controller.rb
class AppliesController < ApplicationController

  def create
    current_user.applies.create(community_id: apply_params[:community_id])
    redirect_to community_url(apply_params[:community_id]), notice: "加入申請しました"
  end

  def destroy
    @apply = Apply.find(params[:id])
    @apply.destroy!
    @comminity = Community.find(params[:community_id])
    redirect_to community_url(@comminity), notice: "加入申請を取り消しました"
  end

  def index
    @applies = Apply.where(community_id: params[:community_id])
  end

  private

    def apply_params
      params.permit(:community_id)
    end

end
belongings_controller.rb
class BelongingsController < ApplicationController

    def create
        @belonging = Belonging.create(community_id: belonging_params[:community_id], user_id: belonging_params[:user_id])
        Apply.find(belonging_params[:apply_id]).destroy!
        redirect_to community_applies_url(@belonging.community), notice:"「#{@belonging.user.username}」が、コミュニティ:#{@belonging.community.name}へ加入しました。"
    end

    def destroy
        @belonging = Belonging.find(params[:id])
        @belonging.destroy!
        @comminity = Community.find(params[:community_id])
        redirect_to community_url(@comminity), notice: "コミュニティ「#{@comminity.name}」を退会しました。"    
    end

    private

        def belonging_params
            params.permit(:community_id, :user_id, :apply_id)
        end

end

初心者が自分の頭でロジック・コードを考えて実装したので、
間違いや、もっと適した実装方法があるかもしれません。
ぜひご指摘いただけますと幸いです。

この実装で、多対多の関係のことがより理解できたなと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby と Java で解く AtCoder ABC129 D 2次元配列

はじめに

AtCoder Problems の Recommendation を利用して、過去の問題を解いています。
AtCoder さん、AtCoder Problems さん、ありがとうございます。

今回のお題

AtCoder Beginner Contest D - Lamp
Difficulty: 1080

今回のテーマ、2次元配列

やっていることは、単純です。

...#..#. 元の配列
12301201 元の配列を左から右にスキャン
33302201 スキャンした配列を右から左にスキャン

合計2回スキャンして左右方向の光の届く範囲を求めます。
次に、上下に2回スキャンして上下方向の光の届く範囲を求めます。

左右用の配列と上下用の配列の値を合計し、その最大値を求めます。

Java

lamp.java
import java.util.*;

class Main {
    public static void main(String[] args) {
        final Scanner sc = new Scanner(System.in);
        final int H = Integer.parseInt(sc.next());
        final int W = Integer.parseInt(sc.next());
        final char S[][] = new char[H+2][W+2];
        for (int i=1; i<H+1; i++) {
            S[i] =  ("#" + sc.next() + "#").toCharArray();
        }
        sc.close();
        for (int i=0; i<W+2; i++) {
            S[0][i] = '#';
            S[H+1][i] = '#';
        }
        int lr[][] = new int[H+2][W+2];
        int ud[][] = new int[H+2][W+2];

        for (int i=1; i<H+1; i++) {
            int cnt = 0;
            for (int j=0; j<W+1; j++) {
                if (S[i][j]=='.') {
                    cnt++;
                } else {
                    cnt = 0;
                }
                lr[i][j] = cnt;
            }
            for (int j=W; j>0; j--) {
                if (lr[i][j]==0) {
                    cnt = 0;
                } else if (cnt==0) {
                    cnt = lr[i][j];
                } else {
                    lr[i][j] = cnt;
                }
            }
        }
        for (int j=1; j<W+1; j++) {
            int cnt = 0;
            for (int i=0; i<H+1; i++) {
                if (S[i][j]=='.') {
                    cnt++;
                } else {
                    cnt = 0;
                }
                ud[i][j] = cnt;
            }
            for (int i=H; i>0; i--) {
                if (ud[i][j]==0) {
                    cnt = 0;
                } else if (cnt==0) {
                    cnt = ud[i][j];
                } else {
                    ud[i][j] = cnt;
                }
            }
        }

        int ans = 0;
        for (int i=1; i<H+1; i++) {
            for (int j=1; j<W+1; j++) {
                int cnt = lr[i][j] + ud[i][j];
                if (ans<cnt) ans = cnt;
            }
        }
        ans -= 1;
        System.out.println(ans);
    }
}

なんの捻りもないコードです。

スクリプト言語などで競プロをすることについて

スクリプト言語などで競プロをすることについて

スクリプト言語の速度について書かれたブログです。
その中で、スクリプト言語で厳しい例として取り上げられているのが、今回の D - Lampになります。

Ruby

ruby.rb
h, w = gets.split.map(&:to_i)
s = Array.new(h + 1).map{Array.new(w + 1, 0)}
1.upto(h) do |i|
  c = 0
  l = 1
  b = gets
  1.upto(w) do |j|
    if b[j - 1] == '.'
      l = j if c == 0
      c += 1
    elsif c > 0
      l.upto(j - 1) do |k|
        s[i][k] = c
      end
      c = 0
    end
    if j == w && c > 0
      l.upto(j) do |k|
        s[i][k] = c
      end
    end
  end
end
ans = 0
1.upto(w) do |j|
  c = 0
  l = 1
  1.upto(h) do |i|
    if (s[i][j] > 0)
      l = i if c == 0
      c += 1
    elsif c > 0
      l.upto(i - 1) do |k|
        ans = s[k][j] + c if ans < s[k][j] + c
      end
      c = 0
    end
    if i == h && c > 0
      l.upto(i) do |k|
        ans = s[k][j] + c if ans < s[k][j] + c
      end
    end
  end
end
puts ans - 1

スキャンの回数を少し工夫したコードで旧環境Ruby (2.3.3)ではTLEでしたが、新環境Ruby (2.7.1)では何とか通る様です。

C++14 Java Ruby 2.3.1 Ruby 2.7.1
旧環境 旧環境 旧環境 新環境
コード長 (Byte) 1420 2044 797 797
実行時間 (ms) 94 465 TLE 1567
メモリ (KB) 35328 108364 36988 47324

どのくらい速くなったか

ケース名 実行時間(旧) 実行時間(新) 新旧比
01.txt 7 59
02.txt 7 63
12.txt 1460 1089 1.34
13.txt 1844 1250 1.48
18.txt 1983 1289 1.54
19.txt 1949 1360 1.43
20.txt 11 59
21.txt 7 63
22.txt 22 70
23.txt 1449 981 1.48
24.txt 9 62
25.txt 10 63

大雑把に、1.4倍速くなっているようです。
rubyの時代が来ましたね。

まとめ

  • ABC 129 D が通った
  • Ruby が速くなった
  • コンパイル系にはかなわないけどね

参照したサイト
スクリプト言語などで競プロをすることについて

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kinx アルゴリズム - 騎士巡回問題

Kinx アルゴリズム - 騎士巡回問題

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。「プログラム=アルゴリズム+データ構造」。アルゴリズムの実装例をご紹介。

元ネタは「C言語による(30年経っても)最新アルゴリズム事典」。今回は騎士巡回問題です。

最新アルゴリズム事典にはこういうのも結構載ってる。パズル的な。

このアルゴリズムも Kinx 初期の頃に書いてテストに使った。やはり二重配列の制御と再帰処理あたりで。お世話になりました。

騎士巡回問題

Wikipedia より

ナイト・ツアー(Knight's Tour)は、チェスを使った数学的パズルの一種。「騎士の巡歴(じゅんれき)」「桂馬拾い」[1]とも呼ばれ、チェスをモチーフにしたパズルの中でも昔からよく知られている。チェスボード上のナイトを移動させ、64マス全てを一回ずつ通過させる。

ソースコード

const N = 5;    // N x N

var board = [],
    dx = [ 2, 1,-1,-2,-2,-1, 1, 2 ],
    dy = [ 1, 2, 2, 1,-1,-2,-2,-1 ];
var count = 0;
var solution = 0;

function printboard() {
    System.print("\nSolution %d\n" % ++solution);
    for (var i = 2; i <= N + 1; i++) {
        for (var j = 2; j <= N + 1; j++) System.print("%4d" % board[i][j]);
        System.print("\n");
    }
}

function test(x, y) {
    if (board[x][y] != 0) return;
    board[x][y] = ++count;
    if (count == N * N) printboard();
    else for (var i = 0; i < 8; i++) test(x + dx[i], y + dy[i]);
    board[x][y] = 0;  count--;
}

function knight() {
    for (var i = 0; i <= N + 3; i++)
        for (var j = 0; j <= N + 3; j++) board[i][j] = 1;
    for (var i = 2; i <= N + 1; i++)
        for (var j = 2; j <= N + 1; j++) board[i][j] = 0;
    test(2, 2);
}

knight();

結果

Solution 1
   1   6  15  10  21
  14   9  20   5  16
  19   2   7  22  11
   8  13  24  17   4
  25  18   3  12  23

Solution 2
   1   6  11  18  21
  12  17  20   5  10
   7   2  15  22  19
  16  13  24   9   4
  25   8   3  14  23

Solution 3
   1   6  11  16  21
  12  15  20   5  10
   7   2  13  22  17
  14  19  24   9   4
  25   8   3  18  23

...(省略)

Solution 304
   1  10   5  16  25
   4  17   2  11   6
   9  20  13  24  15
  18   3  22   7  12
  21   8  19  14  23

おわりに

これはほとんど C 言語版と一緒の形で書けている。

こうしてみると、最初の頃(半年ほど前)は、この手のソースコードを使ってテストしてたんだなー。こういうのがちゃんと動くのを見られると嬉しいよね。

ではまた、次回。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

商品情報編集機能を実装したい~part1~

前提

  • ruby on rails 6.0.0 を使用。
  • ユーザー機能はdeviseにより導入されているものとする。
  • viewファイルは全てhaml形式とする。
  • ちなみに使っているのはMacBook Air(Retina, 13-inch, 2020)です。

はじめに

プログラミング学習のため、某フリマサイトのコピーサイトを作っています。
商品情報編集機能を実装したが、画像の複数投稿になかなか苦戦したので参考になればと思い書くことにしました。
大した量じゃないのにpart分けしているのはあとで見たときに達成感を得るためです。許してください。(いっぱい書いたぞ!ってなりたい)
part1で特に詰まる部分はないと思いますが、part2以降の土台になってくるのでウォーミングアップ程度に書いていきます。

仕様書

  • 商品に登録された情報をひとつひとつ変更できる。
  • 商品情報を編集できるのは商品を登録したユーザーのみ。
  • 画像の差し替えは一枚ごとにできる。
  • 商品名や画像など、すでに登録されている情報は編集画面にあらかじめ表示される。
  • エラーハンドリングを行う。
(マークアップまで完成したら参考画像を載せようと思ってます)

手順

1, 土台となるコントローラやモデルの作成。
2, imageテーブルを関連づけて画像の投稿を実装。
3, jQueryを導入して複数画像の投稿を実装。
4, メインとなる編集機能を実装。
5, 画像のプレビューをする。

これら5つの手順をそれぞれpart分けして実装していくことにする。

それでは、

いざ実装へ

まずは基礎的な部分を固めていく。
最初にターミナルから。

$ rails g contoroller products
$ rails g model product
$ rails db:migrate

いつもの流れですね。データベースの作成やマイグレーションファイルの記述など細かい点は省略します。

それでは早速ルートファイルから記述していきましょう。

app/config/routes.rb
Rails.application.routes.draw do
  root "products#index"
  resources :products
  devise_for :users, controllers: {
    registrations: 'users/registrations'
  }
end

deviseの部分はあらかじめ実装してあるユーザー機能に関する部分なので気にしなくて大丈夫です。
ここも細かい説明は不要ですね。resourcesで基本のアクションを作り、ルートパスにindexアクションを指定しています。(今回、destroyとshowは使わないのでexceptして頂いても大丈夫です)

次にcontrollerの記述です。

app/controllers/products_controller.rb
class ProductsController < ApplicationController
  before_action :ensure_current_user, only[:edit, :update]
  before_action :set_product, only[:new, :create, :edit, :update]

  def index
    @products = Product.all
  end

  def new
    @prodcut = Product.new
  end
  def create
    @product = Product.new(product_params)
    if @product.save
      redirect_to products_path
    else
      render :new
    end
  end

  def edit
  end
  def update
    if @product.update(product_params)
      redirect_to products_path
    else
      render :edit
    end
  end

  private
  def product_params
    params.require(:product).permit(:name).merge(user_id: current_user.id)
  end
  def ensure_current_user
    product = Product.find(params[:id])
    if product.user_id != current_user.id
      redirect_to action: :index
    end
  end
  def set_product
    @product = Product.find(params[:id])
  end
end

ひとまずはこんなところでしょうか。
順を追って解説していきます。

まずは基本のアクションについて。

@products = Product.all

indexは問題ないですね、productテーブルに登録された全てのレコードを取り出しています。

@prodcut = Product.new

new,createでは商品の登録を行っています。モデル.newで新しいオブジェクトを生成し、そこにフォームの値を代入します。

def product_params
 params.require(:product).permit(:name).merge(user_id: current_user.id)
end

そうして送信されたデータをproduct_paramsメソッドで受け取っています。みんな大好きストロングパラメータですね。
最低限の機能を実装するだけなのでカラムはnameだけにしています。

if @product.save
 redirect_to products_path
else
 render :new
end

続くif文ですが、これがエラーハンドリングというやつです。
処理が成功した場合はindexへ、逆に失敗した場合はnewを再表示させています。
概念としては複雑な部分もありますが、機能としてはもうお馴染みといっていいと思います。

@product = Product.find(params[:id])

次にedit,updateアクションとなりますが、やっていることは先ほどと同じです。createと違うところと言えば、すでにデータが存在するので新しくオブジェクトを作る必要がなく、findメソッドで選択したproductをもってきていることです。

def ensure_current_user
 product = Product.find(params[:id])
  if product.user_id != current_user.id
  redirect_to action: :index
 end
end

最後にensure_current_userメソッドです。
一見難しそうに見えますが処理はすごく簡単。要は選択したproductのユーザー情報とログインしているユーザー情報が違う場合は編集できませんよーってことです。

さて、コントローラーの記述が終わったのでそろそろviewファイルを記述します。

app/view/products/index.html.haml
- if user_signed_in?
  - @products.each do |product|
    - if product.user_id == current_user.id
      = link_to edit_product_path(product.id) do
        #{product.name}
  = link_to("出品", new_product_path)

  %h2 ログインしています
  = link_to 'ログアウト', destroy_user_session_path, method: :delete

- else
  %h2 ログインしていません
  = link_to '新規登録', new_user_registration_path
  = link_to 'ログイン', new_user_session_path

まずはindexからですが、着目すべきは最初の6行だけです。
ユーザーがログインしているか。
その商品はそのユーザーが登録したものか。
といった二つの条件のもと、editとnewのパスを表示させています。

app/views/products/_form.html.haml
= form_with model: @product, local: true do |f|
  = f.text_field :name, placeholder: 'name'
  = f.submit 'SEND'

こちらも特別に説明すべきことはないと思います。
form_withメソッドを使ってproductモデルにデータを送るというものですね。あとはこいつを部分テンプレートとしてnew.html.hamlとedit.html.hamlにrender表示させるだけです。

最後に

ここまでで、商品のデータを作成し、それを編集する機能を実装することができました。(カラムは名前だけですが、、、)

仕様書でいうところの、

  • 商品に登録された情報をひとつひとつ変更できる。
  • 商品情報を編集できるのは商品を登録したユーザーのみ。
  • エラーハンドリングを行う。

これら3つをクリアすることができたことになります。
次のpartでは画像(imageテーブル)を追加できるようにしていくのでよければお付き合いください。

商品情報編集機能を実装したい~part2~

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jekyll を Windows でインストールしてテーマを読み込むときの注意点!! tzinfo が必要!!

Jekyll を Windows でインストールしてテーマを読み込むときの注意点

cmd
PS C:\Users\----\Documents\blog\beautiful-jekyll-master> bundle exec jekyll serve
  fatal: not a git repository (or any of the parent directories): .git
  Configuration file: C:/Users/hideki/Documents/blog/beautiful-jekyll-master/_config.yml
  Dependency Error: Yikes! It looks like you don't have tzinfo or one of its dependencies installed. In order to use 
  Jekyll as currently configured, you'll need to install this gem. The full error message from Ruby is: 'cannot load such 
  file -- tzinfo' If you run into trouble, you can find helpful resources at https://jekyllrb.com/help/!
  jekyll 3.8.7 | Error:  tzinfo

こんなエラーが出たとしたら以下のようにbeautiful-jekyll-master/Gemfileを書き直してください。

beautiful-jekyll-master/Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

gem 'tzinfo', '~> 2.0'
gem 'tzinfo-data', '~> 1.2020', '>= 1.2020.1'

gemspec

これでまた

cmd
PS> bundle install 

すればうまくいくはず。

体験したエラー

上のエラーが出たので、tzinfoを追加でgem install したけどうまくいかず、
tzinfo-data がwindowsだと必要らしいのでgem install したけどうまくいかず、
最後に上の方法で一括でインストールするとうまくいきました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AtCoder】Rubyで競プロに入門する

大学4年生(2020年7月時点)のゆうやと申します。
僕は趣味で競技プログラミング(AtCoder)をやっています。
茶色コーダーになって何かアウトプットしたくなり、この記事を書くことにしました。

対象読者

プログラミング言語Rubyと競プロの初心者に向けた記事です。
Rubyの基本的な文法やメソッド、競プロの基本やヒントについて書いています。

ここで紹介しているのはあくまで僕の書き方・やり方なので、必ずしも最適ではないかもしれない点には注意してください。

この記事に書いてあることを理解して、少し練習問題を解いていくと、AtCoderのだいたいのA問題(一番易しい問題)とB問題(二番目に易しい問題)は解けるようになるはずです。

AtCoderについて

AtCoderは、日本で一番有名な競技プログラミングのサイトです。
言い切ってますが、たぶん間違いないと思います。

概要

土日の21時から、毎週のように競技プログラミングのコンテストが開催されています。
過去のコンテストの問題はいつでも閲覧可能になっています。

コンテストの種類は、大きく次の3つがあります。

  • AtCoder Beginner Contest (ABC)
  • AtCoder Regular Contest (ARC)
  • AtCoder Grand Contest (AGC)

下にいけばいくほど難易度が上がります。
基本的にはABCが開催されることが多いです。

また、企業などが主催で、上記いずれかの難易度相当のコンテストが開催されることもあります。
上位になることで、賞金が貰えるものもあります。(!)

問題の難易度

基本的には、それぞれのコンテストで6問ほど出題されます。
A, B, C, D, E, Fとそれぞれ名前が付いています。
後になればなるほど難しくなります。

ちなみに、茶色コーダーの僕だと、A, B問題はだいたい毎回解けます。
C問題がたまに解けないことがあり、D問題は解けたことがない、という感じです。

C問題以降は競プロのコツのようなものが必要で、D問題以降はちゃんとした知識が必要になってくるかな~という印象です。

参考になる記事を見つけたので、問題のレベル感の部分を引用します。

AtCoder コンテストについての tips

  • A 問題: スコア 100 点であることが多い、簡単な文法の確認
  • B 問題: スコア 200 点であることが多い、簡単な for 文、if 文を含む処理, Fizz Buzz と同程度
  • C 問題: スコア 300 点であることが多い、計算量を意識したコーディングが求められるようになります
  • D 問題: スコア 400 点であることが多い、ここから先はアルゴリズムの勉強も必要になってきます
  • E 問題: スコア 500 点であることが多い、本格的な難しい問題です
  • F 問題: スコア 600 点であることが多い、一気に難しくなります。黄色を目指すなら解きたい問題です

色(レーティング)

コンテストでの回答状況に応じて、各ユーザーにレーティングと、それに応じた色が付きます。
AtCoder社長のchokudaiさんが書かれているこの記事から引用します。

AtCoder(競技プログラミング)の色・ランクと実力評価、問題例

400ごとに色がついていて、赤・橙・黄・青・水・緑・茶・灰・黒、という順番になってます。

誤解を恐れずに超ざっくりとイメージでの評価をするなら、

  • 灰色は参加すれば誰でもなれるので意欲以外の保証はなし。
  • 学生で茶色なら優秀だがエンジニアとしてはちょっと物足りない。派遣とかで来たエンジニアが茶色あれば一安心。
  • 緑あれば大抵の企業でアルゴリズム力は十分。AtCoder的には決して上位ではないが、他社評価サイトなら最高評価。
  • 水色だと基礎的なアルゴリズム処理能力については疑いのないレベル。
  • 青以上は一部上場のIT企業でも、一人もいないことが結構あるレベルになる。
  • 黄色からは化け物。競プロの問題を解く機械だと思っておけば良い。
  • 橙はあたまおかしい。
  • 赤はもうなんか世界大会とかに招待されたりする。

基本知識

標準入出力

競技プログラミングでは、
与えられた値を受け取って(入力)、
それを加工するなどして、望まれる値を出します(出力)。
これらを標準入出力といいます。

入力と出力のそれぞれのやり方を解説します。
色々なやり方がありますが、ここでは僕のやり方を紹介します。

入力

gets で値を受け取ります。
これで1行分の値を受け取れます。
1行で複数の値が与えられるときは、split を使って受け取ります。

# 入力
# Hello

input = gets
puts input

# 出力
# Hello
# 入力
# Hello World

input = gets.split(" ") # 空白文字で区切って、配列として受け取る
puts input

# 出力
# Hello
# World

出力

出力は上の例のように puts を使います。
値を出力して、改行してくれます。

データ型

gets で受け取った値は、文字列として扱われています。
そのため、数値を受け取りたい場合は、データ型を変換する必要があります。
ここでは数値に変換するので to_i を使います。

# 入力
# 10

input = gets.to_i
puts input + 5

# 出力
# 15

他には、小数にする to_f や文字列に変換する to_s 、配列にする to_a などがあります。

参考

繰り返し

繰り返しには、以下のようなものがあります。

  • times
  • each
  • while
  • loop

それぞれ具体例と一緒に解説します。

times

数値に対するメソッドです。
その数値の大きさ回数、繰り返します。

3.times do
    puts "Hello"
end

# 出力
# Hello
# Hello
# Hello

each

複数の値のそれぞれに対して実行します。

["red", "blue", "green"].each do |color|
    puts color
end

# 出力
# red
# blue
# green

while

条件式が満たされる間、繰り返し実行します。
条件式に使う値があれば、その更新を忘れないように注意が必要です。

i = 0
while i < 3
    puts i
    i += 1
end

# 出力
# 0
# 1
# 2

loop

これは使わない方が良いと思います(なら書くな)。
なぜなら、抜け出す処理をさせないと、無限ループに陥ってしまうからです。
break で強制的に終了させるようにして使うことがあります。

(追記)
loop に限らず、他のどのループでも無限ループに陥る恐れはあります。
きちんとループを抜け出す処理を書くこと、そしてその条件も丁寧に設定する必要があります。

i = 0
loop do
    if i >= 3
        break
    end
    puts i
    i += 1
end

# 出力
# 0
# 1
# 2

便利なメソッド

文字列の長さ、配列の要素数

lengthsize は、文字列の長さや配列の要素数を返してくれます。

str = "Hello"
puts str.length

# 出力
# 5
arr = ["red", "blue", "green"]
puts arr.length

# 出力
# 3

どちらも lengthsize に変えても、同じ結果を返します。

並び替え関連

sort, reverse

sort は、昇順に配列の要素を並び替えてくれます。
reverse は、並び順をひっくり返してくれます。

arr = [3, 1, 2]
arr.sort
# [1, 2, 3]
arr = [3, 1, 2]
arr.sort.reverse
# [3, 2, 1]
arr = ["red", "blue", "green"]
arr.sort
# ["blue", "green", "red"]

最大、最小:max, min

配列の要素の中で、最大や最小の値を返してくれるメソッドがあります。
最大値が max で、最小値が min です。

arr = [4, 5, 1, 3, 2]
puts arr.max
puts arr.min

# 出力
# 5
# 1

最初、最後:first, last

配列の要素の、最初と最後の値を返してくれるメソッドがあります。
最初が first で、最後が last です。

arr = [4, 5, 1, 3, 2]
puts arr.first
puts arr.last

# 出力
# 4
# 2

順列、組み合わせ

permutation で順列、
combination で組み合わせを生成できます。
to_a という配列を生成するメソッドも使います。

arr = [1, 2, 3]
arr.permutation.to_a
# [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
a = [1, 2, 3, 4]
a.combination(1).to_a
# [[1],[2],[3],[4]]
a.combination(2).to_a
# [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
a.combination(3).to_a
# [[1,2,3],[1,2,4],[1,3,4],[2,3,4]]
a.combination(4).to_a
# [[1,2,3,4]]

たまに使うもの

最大公約数

gcd() というメソッドを、整数に対して使います。
ちなみに、最大公約数は英語で greatest common divisor です。
そのため gcd と略されています。

num = 9
puts num.gcd(6)

# 出力
# 3

最小公倍数

lcm() というメソッドを、整数に対して使います。
ちなみに、最小公倍数は英語で least common multiple です。
そのため lcm と略されています。

num = 9
puts num.lcm(6)

# 出力
# 18

素数

prime というモジュールを使います。
ちなみに、素数は英語で prime number です。

モジュールを使うために require 'prime' を実行する必要があります。

(追記)
「モジュール」ではなく「ライブラリ」の誤りでした。

# 入力
# 7

require 'prime'
num = gets.to_i
if Prime.prime?(num)
  puts "#{num}は素数です。"
else
  puts "#{num}は素数ではありません。"
end

# 出力
# 7は素数です。

(追記)
if文の書き方が長ったらしい表現になっていました。
以下のように書く方がスッキリしていて良いです。

if num.prime?

よくあるミス

TLE

競プロをやり始めたばかりのときは、これが連発するかと思います。笑
TLE とは Time Limit Exceeded の略です。

競プロでは、いかに効率良く計算するかが大切です。
不要な計算は避ける必要があります。

ただしこれに関しては、ある程度やっているうちに「これだとTLEになるな…」というのが肌感で分かってくるようになります。
そして、どうやったら避けられるかというのも、ある程度は分かるようになってきます。

僕自身もまだ勉強中ですが、TLEになってしまった問題の解説を読んで、少しずつ効率的な計算の仕方を学んでいくしかないと思います。

とにかく、不要な計算をしていないか、自分なりに見直す癖をつけることが大切です。

問題を誤解してしまう

A問題やB問題は比較的難易度が低いため、慣れてくると問題文を飛ばし読みしてしまうことがあります。(自分
そうすると、ちょっとした勘違いがあったりして、沼にハマってしまうということが起きてしまいます。

数学の文章題は計算よりも読解が大切だ、というのを受験期に何度も聞いたことがありますが、競プロでもそれは同様です。
問題文を正しく理解できていないと、どんなに高速なアルゴリズムをたくさん知っていたとしても問題を解くことはできません。

解くスピードが早ければ早いほどパフォーマンスも上がるため、ついつい焦ってしまいますが、問題文をきちんと読んで、条件も正しく理解したうえで問題を解き始めることが大切です。

0 を含むか否か

よくある落とし穴として、これが挙げられます。
何度このトラップにかかったことか…

上述した通り、問題文をきちんと読んで条件を正しく理解することが大切です。
見落としがちなのが、この 0 を含むか否かというところです。

どう見ても時間内に計算が終わるし、ロジックの組み立てにも問題が見当たらないのに WA (Wrong Answer) となってしまうのは、これが原因かもしれません。

蛇足

知らなくても全く問題ありませんが、ほんの少しだけ時間効率が上がる(?)ものを3つほど紹介します。

1行でif文

num = gets.to_i
if num > 0
    puts num
end

if文の書き方といえばこれが基本形かと思いますが、以下のように書くこともできます。

num = gets.to_i
puts num if num > 0

三項演算子

num = gets.to_i
if num > 0
    puts num
else
    puts 0
end

三項演算子というものを使うと、以下のように書くこともできます。

num = gets.to_i
puts num > 0 ? num : 0

OR

if x == "a" || x == "b" || x == "c"
    # 処理
end

これをちょっとカッコよく書きたいと思ったら、以下のように書くこともできます。

if ["a", "b", "c"].include?(x)
    # 処理
end

練習問題リンク

いくつかリンクを貼っておきます。
一番上の「過去問精選10問」は、僕も始めたてのときに解きました。

まとめ

Rubyと競プロの基本について書いてみました。
ぶっちゃけ、過去問を解きまくって慣れることができれば、茶色になることはそう難しくないと思います。

今は緑色になるために頑張っていますが、そろそろアルゴリズムやデータ構造の勉強が必要になってきたな~というのが素直な感想です。
大学生のうちに緑色になりたいなぁ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ポートフォリオ

ポートフォリオ1

こちらのポートフォリオは環境構築だけで開発は行っていません。
(aws,nginx,ruby,docker,mysql)を使って環境構築を行いました。

https://github.com/tmk616window/docker-ruby2

ポートフォリオ2 

ポートフォリオ投稿サイトを作りました。

URLはこちらになります

https://github.com/tmk616window/review00
https://review0.herokuapp.com/

経緯

まずなぜこれを作ろうと思ったのか説明させていただきます。
自分がインターンの採用のためにポートフォリオを作ろうと思ったときに「何を作ればいいかわからない。どこまで作りこめばいいかわからない。」という悩みがありました。
ツイッターやyoutubeのコメント欄でもこういった悩みを持った方々をよく見かけました。
それだったら何を作ればいいのかまたどれくらい作りこめばいいのかの指標になるようなものをポートフォリオに落とし込んでみようと思いました。

コンプト

エンジニア採用が決まった方が記事を投稿し、それを未経験の方が参考にしてポートフォリオを作る指標を定めることを目標にしています。

機能

では実際に機能の説明をさせていただきます。

トップページ

2020-07-10.png

こちらがトップページになります。
採用された方が自分のポートフォリオを6項目で評価し、数値化してわかりやすくしています。

詳細ページ

2020-07-10 (7).png

興味がある投稿をクリックすると詳細ページに移動します。
詳細ページにはポートフォリオの情報がより詳しく記載されています。
いいねボタンはajaxで実装しました。
いいね数の数字もリアルタイムで変わるように実装しました。

相談

2020-07-10 (2).png

詳細ページの「相談する」のボタンを押すと詳細ページに移動します
相談ページではslack形式でリアルタイムに相談できるようなものを作りました。
こちらでは詳細ページで気になったことなどを質問できるようにするために作りました。
入力したメッセージは相手のマイページに通知が行くようになっています
actioncableを使いました。

マイページ

2020-07-10 (3).png

上のチャットメッセージのところに先ほど他のユーザーが相談ページで送ったメッセージの通知が来るようになっています。
マイページの画像はawsのs3に接続していましたが料金がかかりすぎていたので接続を切りました。
コード自体は残っているのでそちらを参照お願いします。
ほかのユーザーのマイページに入ると画像の下にフォローボタンが出てきてフォローすることができます。

イベント一覧

2020-07-10 (5).png

こちらは時間があったのでサブで機能追加したものです。
画像はawsのs3に接続していましたが料金がかかりすぎていたので接続を切りました。
コード自体は残っているのでそちらを参照お願いします。
気になるイベントをクリックすると詳細ページに飛びます。

イベント詳細ページ

2020-07-10 (6).png

詳細ページに飛びます。
下のイベント申し込みボタンをクリックすると申し込みページに飛んで申し込むことができます。
申し込みが完了すると相手のマイページのイベントメッセージのところに通知が行くようになっています。

以上でポートフォリオの説明を終わります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails6】hamlを導入したらmissing a templateエラーが発生したときの対処法【超簡単】

hamlのメリット

  • 閉じタグや<% end %>が必要なくなり、簡潔にかける。
  • インデントがきちっとしていないとエラーになるので嫌でも綺麗にかける。

上記のメリットに加えて、現場でも使われる機会が多いので、勉強がてらRailsにhamlを導入してみました。
そのときmissing a templateというエラーが発生し少し困ったのでここへメモとして残しておきます。
このページを見ているということは既にhamlを導入済身の方が多いと思うので一番下の解決方法へ飛んでください。
そうでない方のためにも、hamlの導入方法についても解説します。

(環境)

  • windows10 Pro
  • Rails: 6.0.3.2
  • ruby: 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]
  • Docker for windows
  • MySQL 5.6
  • nginx:1.15.8

hamlの導入方法

1.Gemを追記

Gemfile
gem 'haml-rails'

2.いつものやつ

$ docker-compose exec サービス名 bundle

開発環境にDockerを使っているのでdocker-compose exec サービス名 を付けてます。
これでhamlの導入は完了です。

erbファイルをhamlに変換

$ docker-compose exec サービス名 rails haml:erb2haml

たったこれだけでerbファイルをhamlへ変換してくれます。

missing a templateエラーが発生

ActionController::UnknownFormat (PostsController#index is missing a template
 for this request format and variant.

request.formats: ["text/html"]
request.variant: []

NOTE! For XHR/Ajax or API requests, this action would normally respond with
204 No Content: an empty white screen. Since you're loading it in a web
browser, we assume that you expected to actually render a template, not
nothing, so we're showing an error to be extra-clear. If you expect 204 No
Content, carry on. That's what you'll get from an XHR or API request. Give
it a shot.)

localhostにアクセスしたら上記のようなエラーが出てviewが表示されなくなりました。

解決方法

解決方法はサーバーを再起動するだけ。(rails s)
僕の場合は開発環境にdockerを使っていたのでコンテナを再起動しました。

$ docker-compose restart

これで解決すると思います。
視野が狭くなると再起動するという発想すらでなくなり迷宮入りしかけました。
エラーが起きた時は視野を広く持てるように気を付けたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】テストの準備からモデル単体テストまで【RSpec】

はじめに

Railsのテストについて書きます。
RSpecを使ったモデルの単体テストです。バリデーションがうまく働くかどうかを確認することがメインとなります。

RSpec導入

まずはgemをインストールします。
Gemfileを編集し、bundleインストールしましょう。

Gemfile
group :development, :test do
  gem 'rspec-rails'
end

# web-consoleというgemがtest環境の中にある場合はdevelopmentのみに移しておく
group :development do
  gem 'web-console'
end
ターミナル
$ bundle install

続いてrspec用のファイルを用意します。

ターミナル
$ rails g rspec:install

これでいくつかファイルが追加されたと思います。

以下を追加されたファイルの.rspecに追記します。

.rspec
--format documentation

続いて必要なディレクトリを用意しておきます。

spec/modelsディレクトリを作成

続いてテストコードを書くファイルを用意したmodelsディレクトリの中に作成します。
今回はpost_spec.rbを作成することにします。
ファイル名はモデル名_spec.rbとしましょう。

これで準備はOKです。

テストコード記述

テストコードを書いてみます。

spec/models/post_spec.rb
require 'rails_helper' # rails_helperを使うために必要です。何も考えず書いてください
describe Post do # Postモデルについて
  describe '#create' do # createアクションについて
    it "contentがない場合は登録できないこと" do # テストの確認内容を記述
      post = Post.new(content: "") # Postモデルのインスタンス(データ)を作成
      post.valid? # インスタンスを保存する際に、バリデーションにより保存ができない状態であるかを確かめる
      expect(post.errors[:content]).to include("can't be blank") # エラー文に"can't be blank"が含まれるかどうかを判定
    end
  end
end

それぞれの処理はコメントアウトで書いたとおりですが、コンソールで一つずつ確かめてみるともう少し理解できると思うので試してみましょう!
ターミナルでコンソール画面を立ち上げてみましょう。

ターミナル
$ rails c

テストコードのit 〜 endを順番に実行してみます。

ff85d977fcb5b51a8144d55c95468715.png
[1]ここでPostモデルのインスタンス(contentカラムが空)が作成されます。
3ce62d1bbec5ec9557a79f44cbc6ab93.png
[2]上で作成したインスタンスが保存に失敗すればfalseが返ってきます。
valid?やinvalid?メソッドについての詳しい話はここを参考にしてください。

67d49b9cce2dfa00427194114ba6a853.png
[3]エラー文はerrorsメソッドを使って見ることができます。
@messageにエラーメッセージが出ています。
今回Postモデルには空を許さないバリデーションの他に、最低でも2文字というバリデーションも書いてあるので、その2つのエラーが出ています。

便利なツール(FactoryBot)

上の例で使ったPostモデルはカラムが少なく、テスト用のデータを作成するのが簡単でした。
しかし、名前やメールアドレス、パスワードなどの複数の情報を持つUserモデルなんかだと、いちいちデータを書いていくのが面倒です。

user = User.new(name: "hoge", email: "hoge@gmail.com", password: "xxxxxx")

このように書かなくてはいけなくなり、冗長なコードになってしまいます。

FactoryBotというgemを使えばデフォルトのデータを用意しておき、必要な箇所だけデータを変更して使えるので便利です。
導入方法、使い方を順番に書いていきます。

まずはgemを入れます。

Gemfile
group :development, :test do
  #省略
  gem 'factory_bot_rails'
end
ターミナル
$ bundle install

spec/factoriesディレクトリを作成します。

続いてデータを書くファイルを用意したfactoriesディレクトリの中に作成します。
今回はusers.rbを作成することにします。
ファイル名はモデル名の複数形.rbとしましょう。
書き方は以下のとおりです。

spec/factories/users.rb
FactoryBot.define do

  factory :user do
    nickname              {"neko"}
    email                 {"neko@gmail.com"}
    password              {"nyannyan"}
    password_confirmation {"nyannyan"}
    profile               {"hello"}
  end

end

factory_botを使うことで、specファイルで簡単にインスタンスを作成できます。

user_spec.rb
user = FactoryBot.create(:user)

と記述するだけでusers.rbで用意したインスタンスを作成してくれます。

実はもう少しだけ簡単に書くことができるのでその設定もしておきましょう。

factory_botの記法の省略
rails_helperに以下のように追記します。

rails_helper.rb
RSpec.configure do |config|
  # 以下を追記
  config.include FactoryBot::Syntax::Methods
  # (省略)
end

これを使って、例えばUserモデルのテストを書いてみます。

user_spec.rb
require 'rails_helper'

describe User do
  describe '#create' do

    it "nikcnameがない場合は登録できないこと" do
      user = build(:user, nickname: "")
      user.valid?
      expect(user.errors[:nickname]).to include("を入力してください")
    end

    it "emailがない場合は登録できないこと" do
      user = build(:user, email: "")
      user.valid?
      expect(user.errors[:email]).to include("を入力してください")
    end

  end
end

このようにbuild(:モデル名)とすることでインスタンスを作成できます。
空データなど、factory_botで用意した値と違う値を使いたい場合は、build(:モデル名, カラム名: 値)とすればOKです。

テストの実行

テストを実行するにはターミナルからコマンドを入力するだけでOKです。

ターミナル
$ bundle exec rspec  

fd3dba157c0da00633c15f6a630b06f2.png
テストが通るとこんな感じで表示されます。
失敗すると通らなかったテストの数だけfeiluresの数字がカウントされ、エラー内容が表示されます。

ファイルを指定してテストを実行

上で書いたコマンドbundle exec rspecはspecファイル全てを実行してしまうので、すでに確認済みのテストがある場合は少し無駄が出てしまいます。

ターミナル
$ bundle exec rspec spec/ディレクトリ名/ファイル名(_spec.rb)

とすることでファイルを指定して実行できます。
今回使用したuserモデルのテストを例にすると以下のようになります。

ターミナル
$ bundle exec rspec spec/models/user_spec.rb
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Uncaught Error: Cannot find module 'bootstrap-form'と出た場合はコメントアウト

自分用忘備録

環境

  • macOS Catalina
  • ruby 2.6.6
  • Rails 6.0.3.2
  • gem 'bootstrap_form'
  • Google chrome

管理者一覧画面から管理者を削除
すると以下のエラーがブラウザ上に出現。

Unknown action
The action 'show' could not be found for Administrator::AdminsController

と出る。
destroy actionのはずが何故show??

controller,view,pathの確認をするも異常なし。

web developer consoleでerror logを確認すると、

Uncaught Error: Cannot find module 'bootstrap-form'
    at webpackMissingModule (application.js:1)
    at Module../app/javascript/packs/application.js (application.js:1)
    at __webpack_require__ (bootstrap:19)
    at bootstrap:83
    at bootstrap:83

bootstrap-formのmoduleでエラーが発生していることを確認。

app/javascript/packs/application.jsの
import 'bootstrap-form';
をコメントアウト。

動作確認すると無事削除出来た。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】実は理解できてないかも?【classとmoduleの違い】

こんにちは市原です。

以前

「Railsで作ったアプリでよく使う自作メソッドを別ファイルにまとめて、必要ところで使いたいけど、やり方が分からぬ…」

といったことがありました。
意外と分かっているようでわかっていなかったclassとmodule、せっかくなので記事にまとめてみました。

calssとモジュール

ざっくりまとめるとこのようになります。

特徴 使用方法 継承 インスタンス 優位性
class データやメソッドをまとめたもの requireでファイルを読み込んでから継承しインスタンスメソッドとして使用 継承を使って複数のクラスをまとめるため、フレームワーク的な使い方ができる
module メソッドや値のみをまとめたもの reequireでファイルを読み込んでからinclude,extendで使用 データをまとめて必要個所で使用する、パーツとして使用

class

classは継承やインスタンスの生成を通じてより広い範囲のオブジェクトに影響を与えます。

class Formula
  def pythagorean_theorem(a, b)
    p "c = #{Math::sqrt(a**2+ b**2)}"
  end

  def self.quadratic_formula(a, b, c)
    x1 = (- b +Math::sqrt(b*b -4*a*c))/ 2 * a
    x2 = (- b -Math::sqrt(b*b -4*a*c))/ 2 * a
    p "x is #{x1} and #{x2}"
  end
end


calc/calculate.rb
# requireを使ってまずはファイルを読み込む
require "calc/formulas"

# クラスを継承してメソッドやデータを引き継ぎ
class Hoge < Formula

  attr_accessor :name

  def initialize(name, age)
    @name = name
    @age = age
  end

end

hoge = Hoge.new("hogehoge",22)
hoge.pythagorean_theorem(3, 4)

#-----------------------------------------------------------

"c = 5.0"

クラスの場合、まずはrequireを使ってファイルを読み込み、使いたいクラスに継承させ、その後インスタンスを生成するという流れになります。

そのためMVCモデルやフレームワークの構築といった、多くのデータを継承しそれぞれの連携を取る際は向いておりますが、ちょっとデータをまとめて使いたいときに使うという目的であれば後述するmoduleの方が適していると言えます。

module

モジュールもclassと同じ値やメソッドをまとめておける役割を持ちますが、インスタンスの生成と継承ができません

そのため単に情報をまとめておき、必要な場所で呼び出すといった面ではclassよりモジュールの方が使い勝手がいいです。

moduleをclass内で呼び出すにはincludeとextendの2種類があります。

includeとextend

一言で表すとincludeはインスタンスメソッドとしてmoduleを組み込み、extendはクラスメソッドとしてmoduleを組み込みます。

以下では数学の公式をmoduleとしてまとめ、インスタンスメソッドとして呼び出しています。

calc/formulas.rb
module Formula

 # 三平方の定理
  def pythagorean_theorem(a, b)
    p "c = #{Math::sqrt(a**2+ b**2)}"
  end


  # 解の公式
  def quadratic_formula(a, b, c)
    x1 = (- b +Math::sqrt(b*b -4*a*c))/ 2 * a
    x2 = (- b -Math::sqrt(b*b -4*a*c))/ 2 * a
    p "x is #{x1} and #{x2}"
  end

end

calc/calculate.rb
# 読み込み
require "calc/formulas"

class Hoge

  attr_accessor :name

  # クラス内でincludeを使用
  include Formula

  def initialize(name, age)
    @name = name
    @age = age
  end

end


hoge = Hoge.new("hogehoge",22)
hoge.pythagorean_theorem(3, 4)

#---------------------------------------------------

"c = 5.0"

このようにHogeクラスから生成されたインスタンスがピタゴラスの定理であるpythagorean_theoremをインスタンスメソッドとして使えるようになっています。

続いてextendで同様に読み込んでみると…

require "calc/formulas"

class Hoge
  attr_accessor :name

  extend Formula

  def initialize(name, age)
    @name = name
    @age = age
  end

  def hello
    p "Hello! I'm #{@name} "
    p "I'm #{@age} old"
  end

end

hoge = Hoge.new("hogehoge",22)
hoge.pythagorean_theorem(3, 4)

#--------------------------------------------------------------------

calculate.rb:26:in `<main>': undefined method `pythagorean_theorem' for #<Hoge:0x000000000504a3a8 @name="hogehoge", @age=22> (NoMethodError)

と、生成されたインスタンスはmoduleのメソッドを使うことはできないのが分かります。

Hoge.pythagorean_theorem(3, 4)

#----------------------------------------------------------------

"c = 5.0"

クラスメソッドとして呼び出してあげれば使えます。

ちなみにclass内でmoduleを展開するため、それを継承したクラスでもそれぞれインスタンスメソッド、クラスメソッドを使うことができますよ~

以下はincludeを使ってクラスを継承しました。

class Fuga < Hoge

end


fuga = Fuga.new("fugafuga",20)
fuga.quadratic_formula(1, 3, 2)

#----------------------------------------------------------------
"x is -1.0 and -2.0"

moduleメソッド

いちいちクラスに組み込まずに、ダイレクトでメソッドだけを使いたい場合moduleメソッドというものが使えます。

module Formula

  def pythagorean_theorem(a, b)
    p "c = #{Math::sqrt(a**2+ b**2)}"
  end

  def quadratic_formula(a, b, c)
    x1 = (- b +Math::sqrt(b*b -4*a*c))/ 2 * a
    x2 = (- b -Math::sqrt(b*b -4*a*c))/ 2 * a
    p "x is #{x1} and #{x2}"
  end

 # module_function + メソッド名でmoduleメソッドを定義
  module_function :quadratic_formula

end

#クラスメソッドと同じ要領で呼び出せる
Formula.quadratic_formula(1, 8, 12)

#---------------------------------------------------

"x is -2.0 and -6.0"

このようにmoduleはclassを作るまでは必要ないが、ちょっと機能を追加したい場合に使えます。

まとめ

データをまとめるだけならmodule、継承を使ってのアプリの一部にデータを組み込みたいならclassを使用するのが望ましいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]初学者によるRubocopの導入

はじめに

Rubocopとは、Rubyの静的コード解析ツール。
ruby-style-guide
に従って、ソースコードの修正・改善点を指摘してくれます。

また、インデントのずれや、不必要なスペース・改行などは特定のコマンドを打つことで自動で修正してくれるので、自分にとってはもちろん、他の人が見ても読みやすいコードに整形することが出来ます。
※あくまで初学者の観点で設定等を決めています。

導入

gemfile
gem 'rubocop', require: false
ターミナル
bundle install

使ってみる

ターミナル
bundle exec rubocop

実行すると、以下のような出力が得られます。
スクリーンショット 2020-07-10 8.06.47.png

これらがRubocopが検知した修正点になります。
しかし、デフォルトのルール全てに従っていると、コードを書く際に気を配る点があまりにも増えてしまうので、自分やチーム内で許容するルールを設定することが出来ます。

設定をカスタマイズする

アプリケーションのディレクトリに
.rubocop.yml というファイルを作成し、その中に設定を記述します。

rubocop.yml
AllCops:
  # 除外するディレクトリを設定。例えばschemaやmigrationファイルなどは書き直すことが少ないため、検知対象外に
  Exclude:
    - bin/*
    - db/schema.rb
    - node_modules/**/*
    - db/migrate/*
    - vendor/**/*
  # Rails向けのチェックを行う
  Rails:
    enabled: true

# "Missing top-level class documentation comment."を無効
Style/Documentation:
  Enabled: false

# "Prefer single-quoted strings when you don't need string interpolation or special symbols."を無効
Style/StringLiterals:
  Enabled: false

# "Line is too long"を無効
Metrics/LineLength:
  Enabled: false

#'frozen_string_literal: true'を無効
Style/FrozenStringLiteralComment:
  Enabled: false

など様々な設定が可能です。
デフォルトの設定を参考にいろいろいじってみようと思います。

設定後、もう一度

ターミナル
bundle exec rubocop

スクリーンショット 2020-07-10 8.29.43.png
すると、72filesが56filesと、先ほどよりも修正点が減っていることがわかります。

修正する

ターミナル
bundle exec rubocop --auto-gen-config

.rubocop.todo.yml というファイルが自動生成されます。
これにより、一時的に修正点を全て無効とみなされます。
ここでrubocopを実行すると、修正点がない状態になります。

.rubocop.todo.yml
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include.
# Include: **/*.gemfile, **/Gemfile, **/gems.rb
Bundler/OrderedGems:
  Exclude:
    - 'Gemfile'

一例として、上記のような記述があります。
この記述を削除すると、該当部分の無効化していた修正点が復活します。
早速修正して行きますが、
Cop supports --auto-correct.
という記述がある場合、Rubocopが以下のコマンドで自動で修正してくれます。

ターミナル
bundle exec rubocop -a

今回修正したのは、Gemfileのgemの記述がアルファベット順になっていないのを正しい順番に並べ替えました。

まとめ

1.gemを導入し、.rubocop.ymlに設定をカスタマイズする
2.bundle exec rubocop --auto-gen-configを実行し、
 .rubocop.todo.ymlを生成する。
3..rubocop.todo.ymlに書かれている記述を1つ消し、修正する
4.3.を繰り返す。

実際の現場では、自動化されていてあまり自分で修正することはなさそうですが、
まだまだ初学者ですので、一つ一つ改善していって規約を確認し、
初めから読みやすいコードを書けるエンジニアになれるよう心がけます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

localhost:3000がESETで遭難した

tl; dr

  • 初めてRailsのサーバーを立ち上げたが、localhost:3000にまずアクセスできない
  • ESET(セキュリティソフト)のパーソナルファイアウォールが悪さをしているらしい
  • localhost:3000の替わりに127.0.0.1:3000でアクセスできた
参考にしたサイト

eset を入れた mac で、 [::1] で開発中ウェブアプリケーションに接続できないのは何故?

インターネットのことがわかってない

IPv6経由ではアクセスできず、IPv4経由だとアクセスできるという状態らしい。
そもそもIPアドレスがなんなのか、なぜ解決したのかもよく分からないので、本を読むなりして行きたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

基礎から学ぶ 人工知能の教科書 第3章 章末問題

はじめに

機械学習の勉強として、「基礎から学ぶ 人工知能の教科書」 を読んでいます。

この本の特徴は、章末問題にPythonの簡単なプログラムが載っていてるところです。

ここでは、それをRubyにて写経しています。

第3章 章末問題

kneighbor.rb
itemdata = [[30, 50, 'A'], [65, 40, 'B'],
            [90, 100, 'A'], [90, 60, 'B'],
            [70, 60, 'B'], [40, 50, 'A'],
            [80, 50, 'B']]

print '分類対象の高さを入力してください:'
h = gets.to_i
print '分類対象の上部表面積を入力してください:'
a = gets.to_i

print itemdata.sort_by{|x, y, _| (x - h) ** 2 + (y - a) ** 2}

k近傍法による分類問題を解くプログラムを作成する問題です。

k近傍法 - wikipedia

分類対象の高さを入力してください:50
分類対象の上部表面積を入力してください:50
[[40, 50, "A"], [65, 40, "B"], [30, 50, "A"], [70, 60, "B"], [80, 50, "B"], [90, 60, "B"], [90, 100, "A"]]

例えば、高さ50 上部表面積50のデータは、Aに分類されます。

まとめ

  • 第3章まで読んだ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nokogiriのcssセレクタでコロン(:)、namespaceを含むタグを指定する方法

NokogiriのCSSセレクタでコロン:namespaceを含むタグを指定するには、コロン:をパイプ|に変換する。

doc.css("namespace|tag")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails testができなくて立ち止まった話

結論として、Tera Termを再起動したら直りました。

【環境】
メインOS:Windows 10
仮想環境:Vagrant、VirtualBox、TeraTerm(ver.4.99)

Railsチュートリアルの第6章を学習中に起きたこと。
rails testを実行しようとすると下記の指示が発生。

[vagrant@localhost sample_app]$ rails test
Running via Spring preloader in process 14722
Migrations are pending.
To resolve this issue, run:
bin/rails db:migrate RAILS_ENV=test

指示のとおり実行してみるも特に何も表示されず、その後テストを実行するも同じ内容となる。

[vagrant@localhost sample_app]$ bin/rails db:migrate RAILS_ENV=test
[vagrant@localhost sample_app]$ rails test
Running via Spring preloader in process 14722
Migrations are pending.
To resolve this issue, run:
bin/rails db:migrate RAILS_ENV=test

その他実行したコマンド↓
・rails db:migrate
・rails db:migrate:reset
・rails db:reset
・rails db:migrate:reset

どのコマンドを実行してもテストができず、test.sqlite3の中身をTTで見ようとすると文字化けしたので、TTを再起動。

その後rails testを実施したら問題なく実行できました。
[vagrant@localhost sample_app]$ rails test
Running via Spring preloader in process 15868
Started with run options --seed 31329

17/17: [=========================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.31768s
17 tests, 37 assertions, 0 failures, 0 errors, 0 skips

数日前からTTをつけっぱなしだったのが悪かったのか?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyのメソッドについて

プログラミングの勉強日記

2020年7月10日 Progate Lv.165

メソッドとは

 複数の処理を1つにまとめたもの。def メソッド名endの間にまとめたい処理を書くことでメソッドを作成できる(=定義する)。
 メソッドの呼び出しはメソッド名を書くことで行う。

index.rb
def introduce
  puts "Hello World"
end
#メソッドの呼び出し
introduce
コンソール
Hello World

引数

 引数はメソッドに与える情報追加みたいなもの。def メソッド名(引数)で引数の指定ができる。引数を渡してメソッドを呼び出すにはメソッド名(値)とする。

index.rb
def introduce(name)
  puts "こんにちは"
  puts "#{name}です"
end
introduce("田中")
introduce("山本")
コンソール
こんにちは
田中です
こんにちは
山本です

※引数のあるメソッドでは引数を渡さないと呼び出せない

複数の引数

 括弧の中に受け取る引数をコンマ,で区切って並べることで複数の引数を使える。左から順番に第1引数、第2引数という。

index.rb
def introduce(name, age)
  puts "#{name}です"
  puts "#{age}歳です"
end
introduce("田中",17)

メソッドのスコープ

 スコープはその変数が使える範囲のこと。メソッドを定義するときに用意した引数はそのメソッドの中(def~end)でしか使えない。メソッドの中で定義した変数もそのメソッドの中(def~end)でしか使えない。

戻り値

 呼び出し元で受け取る処理結果を戻り値といい、このことを「メソッドが戻り値を返す」という。メソッドの中でreturnを使うことで、呼び出しもとで値を受け取れる。
 returnは戻り値を返すだけでなく、メソッドを終了させる性質も持つ。

index.rb
def add(a, b)
  #a+bの値が戻り値として呼び出し元に戻る
  return a+b
end
sum=add(1, 3)
puts sum
コンソール
4

戻り値は真偽値を返すこともできる。この場合、メソッド名の末尾に?をつける。

index.rb
def negative?(number)
  return number<0
end
puts negative?(5)
コンソール
false

キーワード引数

 引数の値が多くなると、呼び出し側で分かりにくくなってしまうので、キーワード引数で呼び出し側で引数を明記する。
 通常のメソッドに加えて、①定数側で引数の後にコロンを付ける、②呼び出し側で値の前に引数名を書く。この2点をすることで、キーワード引数を持つメソッドを書ける。

index.rb
def introduce(name:, age:, height:)
  puts "私は#{name}です"
  puts "#{age}歳です"
  puts "身長は#{height}cmです"
end
introduce(name:"田中", age:17, height:176)
コンソール
私は田中です
17歳です
176㎝です
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(1分で決める)empty?とblank?とpresent?の使い分けについて

(備忘録)説明最小限にしてすぐ使用できるようにまとめた。(結論)blank?かprresent?をどっちか覚えとけば何とかなる。

empty?を使う時は

下記の条件を満たしたうえで中身が空であるか知りたいときに使用。
①必ず入れ物が存在していること
②中身が数字やtrue,falseでないこと

  if box.empty?

①②を満たさない場合は NoMethodErrorが生じる。

blank?を使うときは

中身が空orそもそも存在しているかを知りたいときに使用

 irb(main):001:0> box = nil
=> nil
irb(main):002:0> box.blank?
=> true

present?を使用するときは

存在していてかつ中身が空ではないことを知りたいときに使用(!blank?と同義)

irb(main):006:0> box = nil
=> nil
irb(main):007:0> box.present?
=> false

もう少し詳しく知りたい、この場合はどうすればいいの?ってときは

https://qiita.com/go_d_eye_0505/items/541110cb9821734b0623

表形式でパターンを網羅しているのでわかりやすかったです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TECH CAMP学習 個人アプリ作成②

deviseを使用して、ユーザーモデルを作成

個人アプリ作成でdeviseのインストールを行いました。
まずはGemfileに下記を入力します。
image.png

ちなみにdeviseは日本語で「工夫する、考案する、案出する、発明する」という意味だそうです。

次にターミナルにて
『bundle install』
『rails g devise:install』
『rails g devise user』

を順番に実行します。

そしてマイグレーションファイルを以下2点のように編集します。
※注意 #は省いていただいて結構です。
① null:falseは空の状態での保存を防ぐ為に使用します。
image.png
②unique:trueはテーブル内で名前の重複を禁止させる為に使用します。
image.png

次にapp/models/user.rbを下記のように編集します。
image.png
validatesは日本語で検証する、presenceは存在、uniquenessは唯一性という意味だそうです。

英語で書かれてあるコードを普通に日本語に訳すことで、意味もよく捉えられるように感じました。
あとmigrationは移動、移行という意味のようです。つまりマイグレーションファイルはデータベースのテーブルへの移動するための設計図のようなイメージでよいのではないでしょうか。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails Active Recordってなんなんだよ。

Active Recordとは、Ruby on Railsで直接SQLを記述することなくデータベースに存在しているデータを扱えるようにするRuby on Railsの機能らしい。

DBからデータを検索する際に、このActive Recordとかいうのを使うらしい。
これを理解して、使いこなせるようになればレベルアップできる気がする:fist:

例えば、Active Recordにはこのようなものが存在する。

all
find
find_by
first
where
pluck
order
distinct
select
group
joins
having

覚えられそうにないけど、頻繁に使うものは多分そのうちRailsを使っていれば無意識のうちに覚えると思っている。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む