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

administrate で refileを使ってファイルを読み込めるようにしたい【rails6】

ハードはMacBook Air, 開発環境はVScodeを用いています。
ruby2.6.5
rails6.0.3.2

rails6で管理者gemにadministrate, 画像読み込みgemにrefileを用いた時に, administrateとrefileの連携でハマってしまったので, その詳細と解決方法を紹介します.

ちなみに、administrate_field_refileはrails6に対応していなかったので、使わない方向で頑張りました!

管理者画面(localhost:3000/admin)で, 新しいデータを追加しようとすると, 画像の入力部分がファイル選択ではなく, テキストボックスになってしまっている

localhost:3000/admin へ移動し, 新規Userを作製しようとすると, profile_imageの部分がテキストボックスになっています。(二枚目の画像の一番下)

スクリーンショット 2020-09-08 21.47.03.png
スクリーンショット 2020-09-08 21.47.12.png

そこでこれを解決するために, いろいろ調べてみるとadministrate_field_refileというgemがあるらしいのですが、こちらはrails6に未対応だったので、administrateをカスタムする方向で進めることにしました。

administrateをカスタムする

まず先にadministrate内部のコード、カスタムするであろう部分をローカルで表示できるようにします。administrateのドキュメントを参考に、dashbordのコントローラ, views, fieldを追加していきます。

$ rails generate administrate:dashboard User
$ rails generate administrate:views User
$ rails generate administrate:field refile

次に、dashbordの profile_image_id: Field::String, となっているところを次のように書き換えます。参考;http://administrate-prototype.herokuapp.com/adding_custom_field_types

app/dashboards/user_dashboard.rb
  ATTRIBUTE_TYPES = {
   ~省略~
   profile_image_id: RefileField,
  }.freeze

次にフォームのviewsをテキストボックスからファイルを選択に変更します.

app/views/fields/refile_field/_form.html.erb
<div class="field-unit__label">
  <%= f.label field.attribute %>
</div>
<div class="field-unit__field">
  <%= f.attachment_field :profile_image, direct: field.direct, presigned: field.presigned, multiple: field.multiple %>
</div>

次に, app/fields/refile_field.rbを次のように書き換えます.

app/fields/refile_field.rb
require "administrate/field/base"

class RefileField < Administrate::Field::Base
  def to_s
    data
  end

  def direct
    options.fetch(:direct, false)
  end

  def presigned
    options.fetch(:presigned, false)
  end

  def multiple
    options.fetch(:multiple, false)
  end
end

このままだと、Unpermitted parameters:という赤い文字がコンソールに表示されて画像を設定できないと思うので、以下で許可します。(~~~には, 登録する要素を入れておく。抜けがあると、ターミナルに赤く表示されるので、その都度確認して追加する)

def resource_params
  params.require(:user).permit(:profile_image,:~~~,:~~~,:~~~)
end

恐らくこれでadministrateから画像データを登録できるようになると思います。

パスワードとかも設定できるようにしたい、もっと登録画面を見やすくしたいという人は、以下を参考にしてみてください。
http://blog.319ring.net/2016/05/14/custom_view_administrate/


役に立ったら是非LGTMボタンをポチッと押していただけると嬉しいです:raised_hand_tone1:

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

【Rails】本番環境でのデータベースをリセット

はじめに

【前提】

・Railsを使用してアプリケーションを開発
・AWSを使用
・EC2(AWSが提供する仮想サーバ)にてWebサーバを作成

既存のデータベースをリセット

今まで私自身ローカル環境にて開発を行うにあたり、DBをリセットしたいことがあった場合
rails db:risetを使用し、DBの再作成を行なっておりました。

今回デプロイ後ですが、マイグレーションファイル等に変更を色々と加えたため、
一度本番環境でもリセットし、再構築しようと考えました。

本番環境でのデータベースリセット

RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rails db:drop

このコマンドで本番時でのDBをリセットすることができます
(本番環境でのDBリセットは、実務ではありえるのでしょうか・・)

再度データベースの内容を反映させる

rails db:migrate RAILS_ENV=production

おそらくデータベースが無いですよ?とエラーが帰ってくる方がいると思いますので、再度createしてあげます。

   rails db:create RAILS_ENV=production
   rails db:migrate RAILS_ENV=production
   rails db:seed RAILS_ENV=production

※シードに情報を記載していない方は、最後の1行は不要です
上記コマンドを使用すれば本番環境へ無事変更点等が反映されているはず・・・

上記記載内容では、こういったリスクがあるのでは等の改善案や提案がございましたら、
コメント等にてお伝えいただければありがたいです。

以上、ご参考になれば幸いです。

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

AWSを使ってアプリケーションを公開する方法(3)EC2インスタンスの環境構築

はじめに

AWSを使ってアプリケーションを公開する手順を記載していく。
この記事ではEC2インスタンスの環境構築を行う。

必要なツールのインストール

EC2インスタンスの環境構築を行うために様々なツールをインストールする。

「.ssh」ディレクトリに移動する

下記コマンドを実行し「.ssh」ディレクトリに移動する。

cd ~/.ssh/

ssh接続

以下のコマンドを実行してEC2インスタンスにsshでアクセスする。
(ダウンロードしたpemファイル名が「xxx.pem」、ElasticIPが123.456.789の場合)

ssh -i xxx.pem ec2-user@123.456.789

yumコマンドを実行しパッケージをアップデート

以下のコマンドを実行してパッケージをアップデートする。

sudo yum -y update

パッケージとは

  • パッケージはLinuxOSの動作に必要な各種プログラムやファイルをまとめたもの。
  • バイナリ型式のプログラムおよびそのプログラムを動作させるのに必要なライブラリや設定ファイル、手順書のようなドキュメントで構成される。

yumコマンドとは

  • CentOSなどのLinuxディストリビューションでは、基本的にRPM(Red Hat Package Manager)と呼ばれるパッケージ管理システムが使われている。RPMパッケージであれば、「rpm」コマンドで手軽にインストールしたり、アップデートやアンインストールしたりすることができる。
  • 「あるソフトウェアを使うにはこのソフトウェアが必要」といった依存関係をチェックして必要なソフトウェアを自動でインストールするなど、RPMをより便利に活用できるようにしたのがYUM(Yellowdog Updater Modified)で、YUMのパッケージ操作はyumコマンドで行う。
  • yumコマンドで使えるコマンドやオプションには様々なものがあり、上で行っている「yum -y update」は全ての問い合わせに「yes」で応答しシステムのパッケージを更新するというコマンドである。

yumコマンドを実行し各種パッケージをインストール

以下のコマンドを実行し、その他環境構築に必要なパッケージを諸々インストールする。

sudo yum -y install git make gcc-c++ patch libyaml-devel libffi-devel
sudo yum -y install libicu-devel zlib-devel readline-devel libxml2-devel
sudo yum -y install libxslt-devel ImageMagick ImageMagick-devel
sudo yum -y install openssl-devel libcurl libcurl-devel curl

Node.jsをインストール

Node.jsとは、サーバサイドでJavascriptを動かすためのもの。詳しくは別の記事にまとめたい。
今後の作業でcssや画像を圧縮するためにインストールする。

以下のコマンドを実行し、Node.jsをインストールする。

sudo curl -sL https://rpm.nodesource.com/setup_6.x | sudo bash -
sudo yum -y install nodejs

curlコマンドとは

サーバとデータのやりとりを行うコマンド。
よく使うオプションは以下らしい。こちらもあとで詳しくまとめたい。

-L -- リダイレクトがあったらリダイレクト先の情報を取る
-s -- 余計な出力をしない
-o -- レスポンスボディの出力先を指定する

bashコマンドとは

bashとはシェルの一種で、ユーザの入力をコンピュータに伝えるもの。
ここではbashを起動している。

rbenvとruby-buildをインストールする

以下はRubyを使ってサーバサイドを実装する場合に行う。

以下のコマンドを実行してgitからrbenvをクローンする。

#rbenvのインストール
git clone https://github.com/sstephenson/rbenv.git ~/.rbenv 

以下のコマンドを実行してパスを通す。
パスを通すとは、どのディレクトリからもアプリケーションを呼び出せる状態にすること。

#パスを通す
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile 

#rbenvを呼び出すための記述
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile

以下のコマンドを実行して設定したパスを読み込む。

#.bash_profileの読み込み
source .bash_profile

以下のコマンドを実行してgitからruby-buildをクローンする。

#ruby-buildのインストール
git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build

以下のコマンドを実行して、rehashを行う。
つまり、rubyやgemをインストールして使えるコマンド(irb, gem, rake, rails, rubyなど)をバージョン毎に振り分け、使用できるようにする。

#rehashを行う
rbenv rehash 

Rubyをインストールする

今回はバージョン2.5.1をインストールする。

以下のコマンドを実行して、Rubyの2.5.1のバージョンをインストールする。

rbenv install 2.5.1

以下のコマンドを実行して、EC2インスタンス内で使用するRubyのバージョンを決める。

rbenv global 2.5.1

以下のコマンドを実行して、サイドrehashを行う。

#rehashを行う
rbenv rehash

最後に以下のコマンドできちんとインストールができているか確認する。

#バージョンを確認
ruby -v

参考

yumコマンドとは
パッケージとは
Node.js公式
Node.js解説
curlコマンド

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

ブラウザに表示させるための事前処理(コンパイラ)

複数の静的ファイルがブラウザに適用されるまでの仕組みをまとめました。
大まかに言うと、
ブラウザでは認識できる言語が決まっており、どんな言語も認識できる言語に翻訳する必要があります。
この翻訳作業をコンパイルといい、コンパイルできないものは事前に処理をする必要があります。この事前処理はプリコンパイルと呼ばれ、細かい機能1つ1つをモジュールという処理のまとまりにし、コンパイルしてブラウザに返すといった流れです。

前提
プログラミング初学者(2ヶ月)が学んだ内容をまとめたものです。
実際の現場では通用しないことや間違った情報がある可能性があります。
お気づきの方はコメントにてご指摘いただければ幸いです。

ブラウザは認識できる言語が決まっている

ブラウザはHTML,CSS,JavaScript,WebAssemblyという言語のみを認識することができます。
サーバーサイドでどんな記述をしていたとしても、最終的にはこの4つでブラウザに返されています。
これ以外の言語ではブラウザはページを描画することができません。

開発を便利にする言語

ブラウザでは、上記4つだけを認識してくれますが、開発ではより書きやすく読みやすいように設計されたプログラミング言語があります。
このプログラミング言語を高級言語といいます。

高級言語

機械よりも人間が理解しやすいように設計されたプログラミング言語
機械が認識しやすい言語は低級言語と呼ばれます。
高級言語の例
CSS:SCSS、SASS
JavaScript:TypeScript、CoffeeScript

ブラウザが認識できる言語に変換する仕組み

開発に便利な言語をブラウザが認識できるように翻訳する作業をコンパイルといいます。

コンパイル
プログラミング言語を動作する機械が理解できるように翻訳する作業
コンパイルはコンパイラというプログラムによって行われる。
コンパイラで認識できない言語がある場合は予めプリコンパイルしておく必要があります。

プリコンパイル
コンパイラが翻訳できない言語を翻訳できるようにする事前コンパイルのこと
メインとなる処理に対して行う前処理のこと
このプリコンパイルを行うための手法として、アセットパイプラインという仕組みがある。

アセットパイプライン
JavaScriptやCSSなどのアセットと呼ばれる静的ファイルを小さくまとめてくれる機能
アセットパイプラインの処理は、プリコンパイル→連結→圧縮→配置の流れで行われます。
複数の静的ファイルをプリコンパイルして連結したのち、圧縮して軽量化したものをpublicディレクトリに配置して、ブラウザへ渡せるようにします。
プリコンパイルはモジュールバンドラを使って行われます。

モジュールバンドラ
モジュールバンドラはJavaScriptのモジュールの依存関係を考慮しながら管理するツール
モジュールは機能を1つずつ分けて他のファイルから読み込めるようにした処理のまとまりのこと
機能のまとまりをモジュール、この1つ1つの機能が依存関係にある場合にモジュールバンドラはこれらを考慮しながら管理してくれます。
モジュールで管理せず、機能をファイルごとに分割してしまうと、最終的に1つのファイルにまとめるときに不具合が生じるためモジュールパンドラが使われています。

webpack
モジュールバンドラの中で主流なツール
webpackが行うことは大きく4つ
・Entry
依存関係を解決するためにどのファイルを基準(エントリーポイント)とするかを決める。
・Output
エントリーポイントにされ、webpackによってまとめられたファイルをどこへどのような名前で出力(アウトプット)するのか指定する。
・Loaders
JavaScript以外のCSSやHTMLなどのファイルをモジュールに変換する方法を読み込み(ロード)、指定した処理を行う。
・Plugins
圧縮などのファイルをまとめる以外でローダーが実行できないタスクを導入し、拡張(プラグイン)する。

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

deviseを用いたユーザー認証機能の実装(1)

今回はdeviseを用いたユーザー認証機能の実装方法について書いていきます。

できるようになること

・ユーザー登録
・ログイン
・ログアウト

Webサービスにおいては必須の機能ですね。
では、早速いきましょう。


まずrailsアプリケーションのディレクトリにあるGemfileの一番下に

Gemfile
gem 'devise'

と記述します。 その後、ターミナルで

% bundle install

を行います。 bundle install した後はサーバーの再起動が必要なので、

% rails s

を行います。
※サーバーが起動していた場合は一旦 Ctrl + C でサーバーを停止させてから実行してください。


次にdeviseの設定ファイルをrailsアプリケーションにインストールするため、

% rails g devise:install 

を行います。
このコマンドにより、deviseの設定関連に使用するファイルを自動で生成します。


次にユーザーを管理するモデルを作成します。
通常のモデル作成の手順とはコマンドが異なります。

% rails g devise user

と実行しましょう。
これでdeviseで使用するモデルファイルやマイグレーションファイルが生成されます。


次に生成されたマイグレーションファイルに必要なカラムを追記しましょう。
※emailとencripted_passwordのみデフォルトで記載されています。

必要なカラムを記載したら、ターミナルで

% rails db:create

でデータベースを作成し、

% rails db:migrate

でマイグレーションを行いましょう。

これでひとまずはUserモデルの作成が完了です。

(2)へ続く。

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

Rubyのクラスとインスタンスを用いてコードを書く

はじめに

プログラミング学習中の者です。初投稿です。
学習したことを書くことで、自分の中により定着させることができればと思っています。
よろしくお願い致します。

早速本題に入ります。

本題

某プログラミングスクールで出たRubyの問題について、解答に少し手間取ってしまったので忘れないためにメモします。
すごく初歩的な問題ではあるのですが・・
問題と正解は下記の通り(問題の内容がそのままだと宜しくないのでちょっとだけ変えてます)。

問題

class Article

  def initialize(author, title, content)
    @author = author
    @title = title
    @content = content
  end

end

# クラスとインスタンスを使用して、上記のコードに追加を行い、以下の出力結果を得られるようにする

書いた人: 鈴木
タイトル: はじめまして
本文: 初投稿です

正解

class Article

  def initialize(author, title, content) #3行目
    @author = author
    @title = title
    @content = content
  end

  def author #9行目
    @author
  end

  def title #13行目
    @title
  end

  def content #17行目
    @content
  end

end

article1 = Article.new("鈴木", "はじめまして", "初投稿です") #23行目

puts "書いた人: #{article1.author}" #25行目
puts "タイトル: #{article1.title}"
puts "本文: #{article1.content}"

自分のために解説してみる

まず最初に・・
* インスタンスメソッドはクラスの中で定義する
* initializeメソッドもインスタンスメソッドの1つ
* インスタンス変数は、そのクラス内すべてのインスタンスメソッドで使える(スコープ)

それぞれの行について
(3〜7行目)
initializeメソッドの中で定義したインスタンス変数に、引数で受け取った、鈴木はじめまして初投稿ですという3つの値をそれぞれのインスタンス変数に代入する。

(9〜19行目)
インスタンス変数の値を返すためのインスタンスメソッドをそれぞれ定義する。
当然ながら、例えば、これらのインスタンスメソッドを定義しない状態で、25行目をarticle1.authorではなく@authorと書いたところで鈴木とは出力されない。

(23行目)
Articleクラスのインスタンスを生成し、変数article1に代入。
その際に実引数として鈴木はじめまして初投稿ですという3つの値を、仮引数authortitlecontentにそれぞれ渡している。

(25〜27行目)
9〜19行目で定義したインスタンスメソッドを呼び出す。
インスタンスメソッドはインスタンス名.メソッド名で呼び出せる。

おわり

この記事を書いたおかげで、クラスとインスタンスに関することを忘れないようにできたのではないかと思います。
いい機会になりました。また何かあったら書いてみます。

ここまでお付き合いしてくださった方ありがとうございました。

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

(ギリ)20代の地方公務員がRailsチュートリアルに取り組みます【第5章】

前提

・Railsチュートリアルは第4版
・今回の学習は3周目(9章以降は2周目)
・著者はProgate一通りやったぐらいの初学者

基本方針

・読んだら分かることは端折る。
・意味がわからない用語は調べてまとめる(記事最下段・用語集)。
・理解できない内容を掘り下げる。
・演習はすべて取り組む。
・コードコピペは極力しない。

さて第5章。こっから本格的な開発フェーズですね。
本日の一曲はこちら。
Luby Sparks "Pop.1979"
ルビー違い。この初期衝動たっぷりのサウンドがたまらん。

第1章で予告した通り、ProgateのSassコースやってなかったので、さくっと終わらせてきました。コードの重複を無くして記述を楽に、そして変更にも対応しやすくするための記法ですね。

 

【5.1.1 ナビゲーション メモと演習】

 Bootstrapが登場しました。概要はこの記事が分かりやすいかも。
要するに、あらかじめ動作が定義されたものを呼び出すことで、Web開発を楽にするためのもの。レスポンシブデザインにも難なく対応。

1. Webページと言ったらネコ画像、というぐらいにはWebにはネコ画像が溢れていますよね。リスト 5.4のコマンドを使って、図 5.3のネコ画像をダウンロードしてきましょう8 。
→ 下記コマンドでダウンロードするだけ。(猫かわいいですよね)

$ curl -OL cdn.learnenough.com/kitten.jpg

 
2. mvコマンドを使って、ダウンロードしたkitten.jpgファイルを適切なアセットディレクトリに移動してください (参考: 5.2.1)。
→ 下記コマンドでimagesディレクトリへ

$ mv kitten.jpg app/assets/images

3. image_tagを使って、kitten.jpg画像を表示してみてください (図 5.4)。
→ 下記をhomeの一番最後に追記すればOK。

home.html.erb
<%= link_to image_tag("kitten.jpg", alt: "cute kitten") %>

 

【5.1.2 BootstrapとカスタムCSS メモと演習】

 まだまだCSSもおぼつかないので、時間がかかるけど、一つ入力するごとに動作を確認していこう。全コピしていちいちコメントアウトするよりこっちのが楽でしょ。
 そして、うっとおしいのでこの時点でネストできるもんはしていきます。たしかこの後どっかでネストしてたと思うけど。先にやっといて、あとで答え合わせと行こうか。

1. リスト 5.10を参考にして、5.1.1.1で使ったネコ画像をコメントアウトしてみてください。また、ブラウザのHTMLインスペクタ機能を使って、コメントアウトするとHTMLのソースからも消えていることを確認してみてください。
→ 指示通りやるだけ。消えろキトゥン!!!!!

 
2. リスト 5.11のコードをcustom.scssに追加し、すべての画像を非表示にしてみてください。うまくいけば、Railsのロゴ画像がHomeページから消えるはずです。先ほどと同様にインスペクタ機能を使って、今度はHTMLのソースコードは残ったままで、画像だけが表示されなくなっていることを確認してみてください。
→ そらっそすよね。CSSで非表示にしてるだけだし。これもやるだけなので細かいことは割愛。

 

【5.1.3 パーシャル(partial) メモと演習】

 パーシャル=部分的な といった意味。冷蔵庫のパーシャルをイメージすると分かりやすいかも。あそこにガシャコンと分けて収納するイメージ。他のページでも使う部分や、すべてのページで共通して使うものを、切り出して個別保存すると。必要に応じて呼び出す(render)わけか。

1. Railsがデフォルトで生成するheadタグの部分を、リスト 5.18のようにrenderに置き換えてみてください。ヒント: 単純に削除してしまうと後でパーシャルを1から書き直す必要が出てくるので、削除する前にどこかに退避しておきましょう。
→ とりあえず適当に避難しといて指示どおり記入。

 
2. リスト 5.18のようなパーシャルはまだ作っていないので、現時点ではテストは redになっているはずです。実際にテストを実行して確認してみましょう。
→ そらREDです。

 
3. layoutsディレクトリにheadタグ用のパーシャルを作成し、先ほど退避しておいたコードを書き込み、最後にテストが green に戻ることを確認しましょう。
→ 下記ファイルを作成してパーシャル!(テストもGREENです)

_rails_default.html.erb
<%= csrf_meta_tags %>
<%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

 

【5.2.1 アセットパイプライン メモ】

今一つ分かりにくいなあと思うアセットパイプライン。重要なのはこの一文か。「『開発効率と読み込み時間のどちらを重視するか』という問題について悩む必要がなくなります。開発環境ではプログラマにとって読みやすいように整理しておき、本番環境ではAsset Pipelineを使ってファイルを最小化すればよいのです。」
 要は、散らばったものを一つの細い管に通してやるイメージでしょうか。その過程でファイルを整理して無駄なものを省き、最小化してくれると。

 

【5.2.2 素晴らしい構文を備えたスタイルシート メモと演習】

 LESS変数一覧をみると、@gray-lightが#777じゃなくて、lighten(@gray-base, 46.7%)になってるんですが、バージョンの違いでしょうか?実際の色は変わっていないようにみえるのでいいんですが(他の既定の色も同じか)

1. 5.2.2で提案したように、footerのCSSを手作業で変換してみましょう。具体的には、リスト 5.17の内容を1つずつ変換していき、リスト 5.20のようにしてみてください。
→ はじめに書いたときから実践済みです。合ってました。(見やすいかと思ったのでネストに改行入れてます)

custom.scss
/* footer */

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid $gray-medium-light;
  color: $gray-light;

  a {
    color: $gray;

    &:hover {
      color: $gray-darker;
    }
  }

  small {
    float: left;
  }

  ul {
    float: right;

    li {
      float: left;
      margin-left: 15px;
    }
  }
}

 

【5.3 レイアウトとリンク】

 最初、名前付きルート(〇〇_path)ってのがしっくりこなかったけど、慣習としてこう書くっていうのと、そういう機能がrailsに備わっていて、変更にも対応しやすいからこうしましょってことと理解。ルーティングを設定することで使えるようになると。

 

【5.3.2 RailsのルートURL メモと演習】

〇〇_path:ルートURL以下の文字列を返す。基本はこっちを使用
〇〇_url :完全なURLの文字列を返す。リダイレクトの場合のみ使用。

1. 実は名前付きルートは、as:オプションを使って変更することができます。有名なFar Sideの漫画に倣って、Helpページの名前付きルートをhelfに変更してみてください (リスト 5.29)。
→ リスト5.29のとおりas: 'helf'を追記。

 
2. 先ほどの変更により、テストが redになっていることを確認してください。リスト 5.28を参考にルーティングを更新して、テストを greenにして見てください。
→ 当然RED。テストをhelf_pathに変更するとGREENになります。

 
3. 比較演算子==を使って、上記2つの課題で作ったそれぞれのオブジェクトが同じであることを確認してみてください。
→ 戻しときましょう。

 

【5.3.3 名前付きルート 演習】

1. リスト 5.29のようにhelfルーティングを作成し、レイアウトのリンクを更新してみてください。
2. 前回の演習と同様に、エディタのUndo機能を使ってこの演習で行った変更を元に戻してみてください。
→ helf好きやな。ルートにas: 'helf'つけて、ヘッダーのhelp_pathをhelf_pathにして終わり!そして戻す!(この演習要る?)

 

【5.3.4 リンクのテスト メモと演習】

 統合テスト(integration test)が登場。「統合テストを使うと、アプリケーションの動作を端から端まで (end-to-end) シミュレートしてテストすることができます。」ということです。動作の流れを考えながら書きましょう。
 テストについては、Railsドキュメントが分かりやすいかも?(リンク先はRails6のものです)
 assert_selectは柔軟な機能があるが、レイアウト内で頻繁に変更されるHTML要素 (リンクなど) をテストするぐらいに抑えておく方が賢明と。第3章の用語集でも取り上げてましたね。
 おっと、ここでエラーが?調べると、リンク先を'root_path'のようにしていました。〇〇_pathはメソッドなので''は不要とのこと。単純な見落としでした。

1. footerパーシャルのabout_pathをcontact_pathに変更してみて、テストが正しくエラーを捕まえてくれるかどうか確認してみてください。
→ /aboutにマッチする要素が一つもないよーと教えてくれます。

 
2. リスト 5.35で示すように、Applicationヘルパーで使っているfull_titleヘルパーを、test環境でも使えるようにすると便利です。こうしておくと、リスト 5.36のようなコードを使って、正しいタイトルをテストすることができます。ただし、これは完璧なテストではありません。例えばベースタイトルに「Ruby on Rails Tutoial」といった誤字があったとしても、このテストでは発見することができないでしょう。この問題を解決するためには、full_titleヘルパーに対するテストを書く必要があります。そこで、Applicationヘルパーをテストするファイルを作成し、リスト 5.37のFILL_INの部分を適切なコードに置き換えてみてください。ヒント: リスト 5.37ではassert_equal <期待される値>, <実際の値>といった形で使っていましたが、内部では==演算子で期待される値と実際の値を比較し、正しいかどうかのテストをしています。
→ 3周目でやっと内容についてこれた感があります。まずtest環境でApplicationヘルパー(のfull_titleメソッド)を使えるようにincludeする。そうすっとレイアウトの統合テストでページタイトルが合ってるかどうかテストできる。ただ誤字とかは発見できないから、Applicationヘルパー自体をテストしてやろうというわけか。FILL_INの部分は下記のとおり。テストはGREENです。

test/helpers/application_helper_test.rb
require 'test_helper'

class ApplicationHelperTest < ActionView::TestCase
  test "full title helper" do
    assert_equal full_title,         "Ruby on Rails Tutorial Sample App"
    assert_equal full_title("Help"), "Help | Ruby on Rails Tutorial Sample App"
  end
end

 

【5.4.1 Usersコントローラ 演習】

1. 表 5.1を参考にしながらリスト 5.41を変更し、users_new_urlではなくsignup_pathを使えるようにしてみてください。
2. 先ほどの変更を加えたことにより、テストが redになったことを確認してください。なお、この演習はテスト駆動開発 (コラム 3.3) で説明した red/green のリズムを作ることを目的としています。このテストは次の5.4.2で greenになるよう修正します。
→ users_controller_test.rbのusers_new_urlをsignup_pathに変更するだけ。当然REDになります。
 フライングですが、signup_pathを使えるようにするにはどうするか。ルーティングを設定する必要がありますね。以下で合ってるはず。テストはGREENになりました。

routes.rb
Rails.application.routes.draw do
  get 'users/new'

  root 'static_pages#home'
  get '/help',    to: 'static_pages#help'
  get '/about',   to: 'static_pages#about'
  get '/contact', to: 'static_pages#contact'
  get '/signup',  to: 'users#new'
end

 次読むと、合ってましたね。

 

【5.4.2 ユーザー登録用URL 演習】

1. もしまだ5.4.1.1の演習に取り掛かっていなければ、まずはリスト 5.41のように変更し、名前付きルートsignup_pathを使えるようにしてください。また、リスト 5.43で名前付きルートが使えるようになったので、現時点でテストが greenになっていることを確認してください。
→ さっきやりました。

 
2. 先ほどのテストが正しく動いていることを確認するため、signupルートの部分をコメントアウトし、テスト redになることを確認してください。確認できたら、コメントアウトを解除して greenの状態に戻してください。
→ 試すだけー。

 
3. リスト 5.32の統合テストにsignupページにアクセスするコードを追加してください (getメソッドを使います)。コードを追加したら実際にテストを実行し、結果が正しいことを確認してください。ヒント: リスト 5.36で紹介したfull_titleヘルパーを使ってみてください。
→ たぶんいらんけど、ついでにSing upリンクがあるか確かめるテストも追記してみました。自分の知識を確かめるために要らんこともしていきます。

site_layout_test.rb
require 'test_helper'

class SiteLayoutTest < ActionDispatch::IntegrationTest

  test "layout links" do
    get root_path
    assert_template 'static_pages/home'
    assert_select "a[href=?]", root_path, count: 2
    assert_select "a[href=?]", help_path
    assert_select "a[href=?]", about_path
    assert_select "a[href=?]", contact_path
    assert_select "a[href=?]", signup_path
    get contact_path
    assert_select "title", full_title("Contact")
    get signup_path
    assert_select "title", full_title("Sign up")
  end

end

 

第5章まとめ

・Bootstrapは便利やけど、使われすぎておもんないとも聞く。
・猫は万国共通でかわいい。
・Sassは便利ですね。コードがスッキリする。
・パーシャルでまとめて見た目スッキリ。
・Asset Pipelineが勝手にassets(画像とかCSSとかJSとか)を最適化してくれる。
・ルーティングを設定すると〇〇_pathと〇〇_urlが使えるようになる。
・統合テストはページ間移動とかの動作をテストできる。動作をシミュレートしよう。

 
第5章はわりとさっくり終了。3周目にして内容がやっと掴めるようになってきました。嬉しい。第6章ではユーザーモデルを作成していきます。

 

第4章はこちら
学習にあたっての前提・著者ステータスはこちら
 

なんとなくイメージを掴む用語集

・条件付きコメント
 Microsoft Internet Explorerに対して、コードを渡したり隠したりするのに使用できるHTMLソースコード中にある条件付きのステートメントのこと。IE10以降は廃止されている。

・レスポンシブ(ウェブ)デザイン
 表示する端末・ブラウザによって表示形式が変わるデザインのこと。同じwebサイトでも、スマホとPCでは文字やコンテンツの大きさが変わったりするアレ。レスポンシブ対応とかよく言われるやつ。

・assert_template
 そのアクションで指定されたテンプレートが描写されているかをテスト。

・assert_equal
 assert_equal <期待される値>, <実際の値> の形で、両者の値が等しいかテスト。

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

ERROR: In file ./.env: environment variable name 'THOR_SILENCE_DEPRECATION ' may not contain whitespace. への対処法

タイトルの通りですが、今回は

.env
ERROR: In file ./.env: environment variable name 'THOR_SILENCE_DEPRECATION ' may not contain whitespace.

というエラーが出た際の対処法について、私の環境下でのソリューションを共有したいと思います。

私はRuby on Railsでプログラミング学習を開始して1ヶ月ほどの初学者ですので、至らない点や説明不足の点等もあるかと思います。
お気づきの点等ございましたらご指摘いただけると幸いです。

※できるだけ初学者の方にも分かりやすく説明する事を心がけているため、やや冗長に感じる部分もあるかもしれませんがご了承くださいませ。

環境

・Ruby 2.6.5
・Rails 5.2.3
・MySQL 5.7
・Docker
・Dokcer-compose version: '3'

まずは解決した方法から

早速ですが解決方法から述べたいと思います。

私の環境下では、ルートディレクトリ
(DockerfileやGemfile等のファイルと同じ階層)
に存在する.envファイルの記述が間違っていた事が原因でした。

.env
#こちらがエラーが出てしまう記述
THOR_SILENCE_DEPRECATION = true


#以下の記述に修正すればエラーは解消される
THOR_SILENCE_DEPRECATION=true

エラーが出てしまう記述では、余計な空白が入ってしまっていますね。
空白を除去する事で、上記エラーは解消されます。

エラーの深掘り

エラーの内容を再度見てみましょう。

.env
ERROR: In file ./.env: environment variable name 'THOR_SILENCE_DEPRECATION ' may not contain whitespace.

こちらの文章をGoogle翻訳で日本語に修正してみると、

エラー:ファイル./.env:環境変数名 'THOR_SILENCE_DEPRECATION'に空白が含まれていない可能性があります。

と変換されます。

日本語としては若干分かりづらいのですが、なんとなく

「空白の関係でエラーが出ているのだな」

とアタリをつける事ができます。

エラー文本文でググってみると、完全に同じエラー文が出てきた方はいらっしゃいませんでしたが、解決にあたっては以下のブログを参考にさせていただきました。

Composerで.env内のスペースはクォートで囲む必要があるエラーが発生
https://awesome-linus.com/2019/04/07/composer-install-error-need-quotes/

エラー文でググったところ情報が少なかったため、おそらくそう頻繁に出るエラーではないのだと予測できますが、初学者の方がエラーに遭遇した際の一つのソリューションとして参考になれば幸いです。

エラーが起きた背景を詳しく

エラーが起きた背景について、もう少し詳しく記述していきます。

このエラーは「.envファイル」の記述が間違えているのが原因なのですが、そもそも.envファイルを触った事がないという方もいらっしゃるかも知れません。

私も約1ヶ月ほどRailsを勉強してきた中で、.envファイルを触る機会というのは一度もなかったのですが、ログイン機能を実装するGem「sorcery」を導入する過程で、以下のエラーを解決するために.envファイルを触る必要が出てきました。

Deprecation warning: Expected string default value for '--test-framework'; got false (boolean).
This will be rejected in the future unless you explicitly pass the options `check_default_type: false` or call `allow_incompatible_default_type!` in your code
You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION.

こちらのエラーは、

・Gemfileにsorceryを追加
・bundle install
・sorceryを使用するためのコマンド「rails g sorcery:install」を実行

という過程の中で発生したものです。

エラーの詳細については私もよく分かってはいないのですが、どうやらシェルスクリプトを生成するためのGemからエラーが出ているらしいとのこと。

このエラーを解決するための方法として、.envファイルに

.env
#正しい記述
THOR_SILENCE_DEPRECATION=true

の記述を追加する必要があり、
誤って以下のコードを記述。

.env
#不要な空白があるためエラーが出る記述
THOR_SILENCE_DEPRECATION = true

そして、rails g controller ~~
を実行しようとしたところ、タイトルのエラーが発生したという経緯です。

ちなみに、「Deprecation warning〜〜〜」のエラーに関しては、以下の記事

[Ruby on Rails]環境変数の設定方法(.bash_profile、Dotenv-rails)
https://qiita.com/yuichir43705457/items/7cfcae6546876086b849

RSpecを導入する
https://qiita.com/d0ne1s/items/1ecd114b33e80058215f

を参考に解決する事ができました。
ありがとうございました。

おしまい

以上が、今回のエラーの解決方法と周辺情報です。
あまり情報が多くないエラーでしたので、もし遭遇して困っている方は参考にしていただけると幸いです。
また、説明が分かりづらい点等があれば、ご指摘いただければと思います。

それでは、最後までお付き合いいただきありがとうございました。

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

JSON でdiff をとった

JSON matcher を使って rspec を書いてたけど JSON の要素が多くてどこが違うかわからなかった

a.rb
#!/usr/bin/env ruby

def main
  ARGF.each do |line|
    line.match(/expected (.*) to be JSON matching (.*)/) do |m|
      e, r = m[1, 2]
      r.gsub!(/(At=>)([^"](?:[^,]+|,[^ ]|, [^:])+)(, :)/) do
        "#{$1}\"#{$2}\"#{$3}"
      end
      r.gsub!(/(\(an instance of String\))/, "\"\\1\"")
      r = eval(r)
      puts e
      puts r.to_json
    end
  end
end

main
$ rspec aaa_spec.rb:xxx >! out
$ ./a.rb out | jq -s '.[0]' >! b
$ ./a.rb out | jq -s '.[1]' >! c
$ vimdiff b c

みたいに書いてみたけど,結局見つけたのは部分部分ケズってみて原因を特定したりした.

記事を書いて供養しておこう.

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

JSON でdiff を取った

JSON matcher を使って rspec を書いてたけど JSON の要素が多くてどこが違うかわからなかった

a.rb
#!/usr/bin/env ruby

def main
  ARGF.each do |line|
    line.match(/expected (.*) to be JSON matching (.*)/) do |m|
      e, r = m[1, 2]
      r.gsub!(/(At=>)([^"](?:[^,]+|,[^ ]|, [^:])+)(, :)/) do
        "#{$1}\"#{$2}\"#{$3}"
      end
      r.gsub!(/(\(an instance of String\))/, "\"\\1\"")
      r = eval(r)
      puts e
      puts r.to_json
    end
  end
end

main
$ rspec aaa_spec.rb:xxx >! out
$ ./a.rb out | jq -s '.[0]' >! b
$ ./a.rb out | jq -s '.[1]' >! c
$ vimdiff b c

みたいに書いてみたけど,結局見つけたのは部分部分ケズってみて原因を特定したりした.

記事を書いて供養しておこう.

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

ELBを経由したリクエストでCSRF対策エラーが起こったのでデバッグと解決まで

背景

AWSで Proxy ELB -> Nginx -> ELB -> Taget Group -> ECS でリクエストを飛ばしてRailsのサービスを動かしたところ、 CSRFトークン対策でエラーになったのでそのデバッグと解決策までの道のり。

CSRFトークン対策でエラーになる

エラー概要

起こっていたエラーはActionController::InvalidAuthenticityToken

CSRFトークン対策とは

https://railsguides.jp/security.html#クロスサイトリクエストフォージェリ-csrf
Railsが標準搭載しているセキュリティ対策です。
セッションに保存されてるtokenとPOST時の authencity_token が一致しているかを検証し、一致していない場合にエラーを吐く。

解決策

nginx.confにproxy_set_header X-Forwarded-SSL on;を追加する。

nginx.conf
# もっと本当は書いてあるけど省略
server {
    listen       80;
    server_name  hoge.jp;

    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    # これを追加する
    proxy_set_header X-Forwarded-SSL on;
}

エラー検証

tokenが異なっている?

セッションに保存されてるtokenとPOST時の authencity_token が一致しているかを検証し、一致していない場合にエラーを吐く

ならセッションに保存されているtokenとauthencity_tokenが異なっているのか、通常の操作でそうなることがあるのだろうか、ということで実際に検証しているRailsのコードをみてみた。

rails/actionpack/lib/action_controller/metal/request_forgery_protection.rb
def verified_request? # :doc:
!protect_against_forgery? || request.get? || request.head? ||
  (valid_request_origin? && any_authenticity_token_valid?)
end

https://github.com/rails/rails/blob/98a4c0c76938e46009cca668da9c3b584a9e9e74/actionpack/lib/action_controller/metal/request_forgery_protection.rb#L289-L292

この verified_request? がfalseの時に InvalidAuthenticityToken のエラーが投げられる。
tokenが異なるということは any_authenticity_token_valid? がfalseということになるので、その予想でデバッグをしてみた。が、any_authenticity_token_valid? はtrueだった。

valid_request_origin? がfalseになっている?

上のコードを見ると、valid_request_origin? がfalseの時にもverified_request?がfalseになる可能性があるので、確認してみた。
すると確かに、valid_request_origin? がfalseだった。

valid_request_origin?の中身を見てみる。

rails/actionpack/lib/action_controller/metal/request_forgery_protection.rb
def valid_request_origin? # :doc:
    if forgery_protection_origin_check
      # We accept blank origin headers because some user agents don't send it.
      raise InvalidAuthenticityToken, NULL_ORIGIN_MESSAGE if request.origin == "null"
      request.origin.nil? || request.origin == request.base_url
    else
      true
    end
end

https://github.com/rails/rails/blob/98a4c0c76938e46009cca668da9c3b584a9e9e74/actionpack/lib/action_controller/metal/request_forgery_protection.rb#L455-L463

valid_request_origin? がfalseになるには request.originrequest.base_url の中身がわかれば理由が分りそうなので出力してみた。
するとrequest.originhttps://〜 なのに対し request.base_urlhttp://〜 となっていた。

つまり上のコードの request.origin == request.base_url の検証部分でfalseになっていることがわかった。

NginxのconfでX-Forwarded-Protoを設定する?

この時点でいろいろ調べると、「NginxからRailsにリクエストが渡される時にHTTPSでNginxにアクセスしてもHTTPとしてRailsに渡されてしまうらしく、これを防ぐために Nginxのconfで X-Forwarded-Protoを使ってRailsにHTTPSであることを知らせる」、という方法がすぐ出てくる。やってみた。

nginx.conf
# もっと本当は書いてあるけど省略
server {
    listen       80;
    server_name  hoge.jp;

    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    # これを追加
    proxy_set_header X-Forwarded-Proto https;
}

けどダメだった。
試しにRailsでリクエストヘッダを出力してみると "X-Forwarded-Proto": "http"となっていた。

どこかでhttpsからhttpに上書きされている?

その通りで、これはELBの性質上でした。
https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/userguide/how-elastic-load-balancing-works.html
image.png

今回、ProxyとなるELBから動かしているRailsのサービスに紐づくELBに対してリクエストが送られてくるが、ここはHTTPで送られてくる。

Application Load Balancer および クラシックロードバランサー は、クライアントに返信する応答のプロキシの後のクライアントの入力リクエストからの接続ヘッダーを優先します

とのことで、NginxからELB間のHTTP通信が優先されてリクエストヘッダの X-Forwarded-ForX-Forwarded-ProtoX-Forwarded-Portが書き換えられてしまっていた。

じゃあどうする

requestオブジェクトはRackで作られているらしいのでそこのコードを見てみた。

rack/lib/rack/request.rb
def scheme
if get_header(HTTPS) == 'on'
  'https'
elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
  'https'
elsif forwarded_scheme
  forwarded_scheme
    else
      get_header(RACK_URL_SCHEME)
    end
end

# 省略

def base_url
    "#{scheme}://#{host_with_port}"
end

https://github.com/rack/rack/blob/649c72bab9e7b50d657b5b432d0c205c95c2be07/lib/rack/request.rb

base_url の作られ方から、 schemehttps になれば良い。
schemahttps になるにはいくつか条件があるけれど 、今回はget_header(HTTP_X_FORWARDED_SSL) == 'on' になるようにすればいけそう!
(HTTP_X_FORWARDED_SSL はELBに書き換えられる心配もない)
ということで、Nginxのリクエストヘッダに X_Forwarded_SSL を追加してみた。

nginx.conf
# もっと本当は書いてあるけど省略
server {
    listen       80;
    server_name  hoge.jp;

    proxy_set_header Host $host;
    # この下2つはELBに書き換えられちゃう
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    # これを追加する
    proxy_set_header X-Forwarded-SSL on;
}

結果

エラーが出なくなった!
デバッグしてみたらちゃんとリクエストヘッダにX_Forwarded_SSLが追加されて schemehttpsになっていた。
image.png

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

【Rails DM】DMが送信された時の通知機能を作ろう!

【Rails DM】通知機能を作ろう!

ステップ

1:DM機能を実装しよう

この記事を参考に作ってみよう!

2:通知機能を実装しよう

2−1:モデルを作成しよう

ruby
rails g model Notification visitor_id:integer visited_id:integer room_id:integer message_id:integer action:string checked:boolean

2−2:作成した通知モデルを、User、Post、Commentと紐付け

UserモデルとNotificationモデルとの関連付け

app/models/user.rb
has_many :active_notifications, class_name: 'Notification', foreign_key: 'visitor_id', dependent: :destroy
has_many :passive_notifications, class_name: 'Notification', foreign_key: 'visited_id', dependent: :destroy

RoomモデルとNotificationモデルとの関連付け

app/models/room.rb
has_many :notifications, dependent: :destroy

MessageモデルとNotificationモデルとの関連付け

app/models/message.rb
has_many :notifications, dependent: :destroy

NotificationモデルとUser,Room,Messageモデルとの関連付け

app/models/notification.rb
  default_scope -> { order(created_at: :desc) }

  belongs_to :room, optional: true
  belongs_to :message, optional: true

  belongs_to :visitor, class_name: 'User', foreign_key: 'visitor_id', optional: true
  belongs_to :visited, class_name: 'User', foreign_key: 'visited_id', optional: true

2−3:DM通知の作成メソッド

messages_controller.rb
class MessagesController < ApplicationController
    def create
        if Entry.where(user_id: current_user.id, room_id: params[:message][:room_id]).present?
          @message = Message.new(message_params)
          # ここから
          @room=@message.room
          # ここまでを追加
          if @message.save

            # ここから
            @roommembernotme=Entry.where(room_id: @room.id).where.not(user_id: current_user.id)
            @theid=@roommembernotme.find_by(room_id: @room.id)
            notification = current_user.active_notifications.new(
                room_id: @room.id,
                message_id: @message.id,
                visited_id: @theid.user_id,
                visitor_id: current_user.id,
                action: 'dm'
            )
            # 自分の投稿に対するコメントの場合は、通知済みとする
            if notification.visitor_id == notification.visited_id
                notification.checked = true
            end
            notification.save if notification.valid?
            # ここまでを追加

            redirect_to "/rooms/#{@message.room_id}"
          end
        else
          redirect_back(fallback_location: root_path)
        end
    end

    private 
      def message_params
          params.require(:message).permit(:user_id, :body, :room_id).merge(user_id: current_user.id)
      end
end

2−4:通知の一覧画面の作成

terminal
rails g controller notifications index
controller/notifications_controller.rb
class NotificationsController < ApplicationController
  def index
    @notifications = current_user.passive_notifications
    @notifications.where(checked: false).each do |notification|
      notification.update_attributes(checked: true)
    end
  end
end
views/notifications/index.html.erb
<% notifications = @notifications.where.not(visitor_id: current_user.id) %>
<% if notifications.exists? %>
    <%= render notifications %>
<% else %>
    <p>通知はございません</p>
<% end %>
views/notifications/_notificastion.html.erb
<% visitor = notification.visitor %>
<% visited = notification.visited %>
<div>
 <%= link_to user_path(visitor) do %>
     <%= visitor.name %>さんが
 <% end %>
 <% if notification.action=='dm' %>
    あなたにDMを送りました
 <% end %>
</div>

参考記事

【Rails】通知機能を誰でも実装できるように解説する【いいね、コメント、フォロー】

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

【Ruby on Rails】ブックマーク(お気に入り登録)機能

目標

投稿に対して、ブックマーク(お気に入り登録)機能を実装します。
bookmark.gif

開発環境

ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina

前提

※ ▶◯◯ を選択すると、説明等が出てきますので、
  よくわからない場合の参考にしていただければと思います。

モデルの作成

ターミナル
$ rails g model Bookmark user:references post:references

null: falseを追加し、データベースへの空の保存を防ぐ。

db/migate/xxxxxxxxxxxxxx_create_bookmarks.rb
class CreateBookmarks < ActiveRecord::Migration[5.2]
  def change
    create_table :bookmarks do |t|
      t.references :user, foreign_key: true, null: false
      t.references :post, foreign_key: true, null: false

      t.timestamps
    end
  end
end
ターミナル
$ rails db:migrate

各モデルにそれぞれ追加。

app/models/bookmark.rb
validates :user_id, uniqueness: { scope: :post_id }

補足
上記validatesを追加することで、重複しての登録を防ぎます。
具体的には、ロード中に2度以上連続で登録しようとすることを防ぎます。

app/models/post.rb
  has_many :bookmarks, dependent: :destroy

  def bookmarked_by?(user)
    bookmarks.where(user_id: user).exists?
  end
app/models/user.rb
has_many :bookmarks, dependent: :destroy

補足
bookmarked_by?(user)を追加することで、
既にブックマークしているかを検証します。

コントローラーの作成

ターミナル
$ rails g controller bookmarks
app/controllers/bookmarks_controller.rb
class BookmarksController < ApplicationController
  before_action :authenticate_user!

  def create
    @post = Post.find(params[:post_id])
    bookmark = @post.bookmarks.new(user_id: current_user.id)
    if bookmark.save
      redirect_to request.referer
    else
      redirect_to request.referer
    end
  end

  def destroy
    @post = Post.find(params[:post_id])
    bookmark = @post.bookmarks.find_by(user_id: current_user.id)
    if bookmark.present?
        bookmark.destroy
        redirect_to request.referer
    else
        redirect_to request.referer
    end
  end
end

補足
まずはpost_idを取得し、その後user_idにcurrent_userを紐付けています。
bookmark.present?を挟んでいるのは、2度押しのエラーを回避するためです。

config/routes.rb
  resources :posts, except: [:index] do
    resource :bookmarks, only: [:create, :destroy]
  end

補足
postにネストさせています。

viewの修正

app/views/posts/show.html.erb
<tbody>
  <tr>
    <td><%= @post.user.name %></td>
    <td><%= @post.title %></td>
    <td><%= @post.body %></td>
    <td><%= link_to "編集", edit_post_path(@post) %></td>
    <% if @post.bookmarked_by?(current_user) %>
      <td><%= link_to "ブックマークを外す", post_bookmarks_path(@post), method: :delete %></td>
    <% else %>
      <td><%= link_to "ブックマーク", post_bookmarks_path(@post), method: :post %></td>
    <% end %>
  </tr>
</tbody>

一覧表示させるためには

表示したい場所のコントローラーで下記を記述。
今回はapp/views/homes/mypage.html.erbに表示することとする。

app/views/homes/mypage.html.erb
<table>
  <caption>ブックマーク一覧</caption>
  <thead>
    <tr>
      <th>投稿者名</th>
      <th>タイトル</th>
      <th>本文</th>
    </tr>
  </thead>
  <tbody>
    <% @bookmarks.each do |bookmark| %>
      <tr>
        <td><%= bookmark.post.user.name %></td>
        <td>
          <%= link_to post_path(bookmark.post) do %>
            <%= bookmark.post.title %>
          <% end %>
        </td>
        <td><%= bookmark.post.body %></td>
      </tr>
    <% end %>
  </tbody>
</table>
app/controllers/homes_controller.rb
def mypage
  @bookmarks = Bookmark.where(user_id: current_user.id)
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyの古いgemで自動ファイルアップロード Watirでの対処法

本稿について

WatirでWebサイトに対してファイルアップロードするスクリプトを書いていましたが、
古い環境で情報が見つけづらく困ったところ解決したので備忘録です。

環境

 CentOS Linux 7
 Ruby 2.0.0p648
 仕様gemはWatir
 ブラウザはFirefoxを使っていますが、Chromeなど、別のブラウザでも

やりたいこと

あるWebサイトに対して、Watirをつかって自動スクリプト起動でファイルアップロードさせたい。
アップロード画面ではSelect file というボタンを押して、ファイル選択画面が出た際にそこからファイルを選んでアップロードさせる仕様

躓いたこと

アップロード画面からファイル選択をする挙動ができておりません。
OS標準のファイル選択画面が開いた時点で、ブラウザ外の動作になってしまうのかスクリプトの操作が及びません。

試したこと

  1. watir内部でjavascriptを実行できるので、 クリップボードなどを使ってコピペができないか など試しましたがそもそも画面の仕様がスクリプトでのクリップボード経由の ペーストを受け付けていないでいるらしく、ペーストがエラーも吐かずに静かに何も起こらず終了してしまいます。
  2. 強引にキーイベントをjavascriptから発動させる。 下記のようなコマンドを実行して強制的にEngerキーやTabキーなど打つことできないか試しましたが、 ブラウザ自体にフォーカスがあたってしまっている?ようで、ファイル選択画面がキーコマンドを受け付けないでいます。
    browser.execute_script("
                document.dispatchEvent( new KeyboardEvent( 'keydown', { keyCode: 86 , ctrlKey: true , key: 'V' }) );
    ")

(参考:https://ameblo.jp/personwritep/entry-12456996738.html)
などなど試しましたが、対象のファイルを読みに行くなどの動作は行ってくれません。

Webサイトの仕様・画面イメージ

ファイルアップロード用の画面が下記で、Select fileボタン押す事ができます。

image.png

すると下記のようなOS標準のファイル選択画面が出て、そこからファイルをアップロードするような作りになっています。
image.png

人の手で操作する際はSelect fileボタンを押してからファイルを選択するか、ドラッグ&ドロップでファイルアップロードすることも可能です。

出来た

結果、これでできてしまいました。

$browser.file_field(:id, //).set("filepath\/...\/file.png")

参考にした記事

https://stackoverflow.com/questions/15163816/automating-a-file-upload-with-watir-in-chrome-on-osx
https://www.rubydoc.info/gems/watir/1.8.1/Watir%2FContainer:file_field

最後に一言

いやWatirて。

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

[rails]gem 'payjp'の実装手順

1.gem 'payjp'をインストールする

2.orderコントローラー作成記述する

  def index
    @order = Order.new
  end

  def create
    @order = Order.new(price: order_params[:price])
    #@orderにprice="1000"値段が入る
    if @order.valid?
      # 保存するための条件(空欄じゃない)をクリアすれば支払、データー保存される
      pay_item#支払をする
      @order.save#データーを保存する
      return redirect_to root_path
    else
      render 'index'
    end
  end

  private
  def order_params
    params.permit(:price, :token)#データー保存を許可したカラム
  end
  def pay_item
    Payjp.api_key = ENV["PAYJP_SECRET_KEY"]  # PAY.JPテスト秘密鍵
    Payjp::Charge.create(
      amount: order_params[:price],  # 商品の値段
      card: order_params[:token],    # カードトークン
      currency:'jpy'                 # 通貨の種類(日本円)
    )
  end
end

・token(カード情報)はorderテーブルに保存しない。
なので@orderにはprice: order_params[:price]で
ユーザーが入力したprice値段の情報が代入される

・pay_itemにより支払完了し
@order.saveでprice:1000、値段の情報をテーブルに保存する

3モデルにバリデーションの情報を記述する

class Order < ApplicationRecord
validates :price, presence: true
end

・バリデーションとは?データーをテーブルに保存する際の条件

・今回はpriceの入力欄が空の状態で送信すると
データーを保存させないようにしている。

・token(クレジットカード情報)はテーブルに保存しないので、バリデーション
保存の条件を記述しない。

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

PG::ConnectionBad: could not connect to server: No such file or directory

PG::ConnectionBad: could not connect to server: No such file or directoryというエラーがでた

解決法

1,ターミナルでdesktopの前に行く。

2,
$ cd /usr/local/var/log/

3,
$ cat postgres.log

そうすると以下のようなエラーがでる
dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.62.dylib
Referenced from: /usr/local/bin/postgres

4,以下の記事を参考に実行
https://qiita.com/eightfoursix/items/bf11693b085eced95e29

4-1
$ brew upgrade

4-2
$ brew postgresql-upgrade-database

自分の場合は、これで解決しました。

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

【RSpec】 Factory Botを使いこなそう

 FactoryBotの基本のキ

bin/rails g factory_bot:model user

のようにFactoryBotでデータを作成するファイルが生成されます。今回はspec/factories/users.rbに以下のようなファイルが生成されると思います。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
  end
end

このなかに作りたいデータを詰め込んでいきます。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name {"佐藤"}
    age {20}
    height {170}
  end
end

 実際に使ってみよう。

spec/models/user.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  it "is valid with a name, age and height" do
    expect(FactoryBot.build(:user)).to be_valid
  end
end

こんな感じで簡潔に書くことができます。ちなみに、FactoryBotを使わない場合は下記のようになります。

spec/models/user.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  it "is valid with a name, age and height" do
    user = User.new(
      name: "佐藤",
      age: 20,
      height: 170,
    )
    expect(user).to be_valid
  end
end

こんな感じになってしまい、行数が多くなってしまいます。

とはいえ読みやすくなるとはいえ、FactoryBot内にデータの内容が隠されてしまうため、ユースケースによって使うか使わないか選択して行きましょう。

 オーバーライド

スペックを書くファイルの中で、データをオーバーライド(上書き)することができます。

spec/models/user.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  it "is invalid without a name" do
    user = FactoryBot.build(:user, name: nil)
    user.vaild?
    expect(user.errors[:name]).to include("can't be blank")
  end
end

FactoryBotの内容に上書きして、namenilにしています。

 シーケンスを使ってユニークなデータを

例えば、「メアド」はユニークな値であるべきです。しかし、FactoryBotをそのまま使ってしまうと毎回同じ値が入るため、バリデーションに引っかかってしまいます。(意図せず。)

その問題に対処するために、「シーケンス」が用意されています。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name {"佐藤"}
    age {20}
    height {170}
    #emailにシーケンスが使われています。
    sequence(:email) {|n| "test#{n}@example.com"}
  end
end

こうするとで新しいユーザーが作成されるたびにtest1@example.com,test2@example.comと常にユニークな値を入れることができます。

 ファクトリーをもっと多様化する

例えば、高齢者の場合をテストしたい、身長が高い人をテストしたい、みたいなことが出たとします。その細かい問題をファクトリー内で吸収することができます。主に二つの方法があります。

 継承を使う

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name {"佐藤"}
    age {20}
    height {170}
    sequence(:email) {|n| "test#{n}@example.com"}

    # 高齢者を対象とするデータ
    factory :senior do
      age {75}
    end

    # 高身長が対象とするデータ
    factory :tall do
      height {190}
    end
  end
end

FactoryBot.build(:user, :senior)FactoryBot.build(:user, :tall)みたいに使うことができます。

 traitを使う。

僕はこっちの方が好きです。理由はFactoryBot内に書くことで短縮できる,何が「違う」のかわかるからです。とにかく見て行きましょう!

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name {"佐藤"}
    age {20}
    height {170}
    sequence(:email) {|n| "test#{n}@example.com"}


    trait :senior do
      age {75}
    end

    trait :tall do
      height {190}
    end
  end
end

FactoryBot.build(:user, :senior)のように書くことで呼び出すことができます。

こうすることで、そのデータの特徴が明示されているので、可読性が高いですよね。

 コールバック

FactoryBotでは動的な動きも表現することができます。下記のコードをみてください。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name {"佐藤"}
    age {20}
    height {170}
    sequence(:email) {|n| "test#{n}@example.com"}

    trait :with_tasks do
      after(:create) { |user| create_list(:task, 5, user: user) }
    end
  end
end

FactoryBot.build(:user, :with_tasks)とすることでuserに関連したtaskを作ることができます。

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

rails tutorial 第6章

はじめに

独学でrails tutorialを進めていく過程を投稿していきます。

進めていく上でわからなかった単語、詰まったエラーなどに触れています。

個人の学習のアウトプットなので間違いなどあればご指摘ください。

初めての投稿なので読みにくいところも多々あるかと思いますがご容赦ください。

第6章 ユーザーのモデルを作成する

6.2.2 存在性を検証する

存在性の検証は:presenceで行うようです。
tutorialではよく省略されたコードが示されていますのでしっかり理解するまでは自分でフォローもしておこうかと思います。

validates :name, presence: true
#すべて括弧をつけると
validates(:name, {presence: true}) #第一引数には検証するカラム名を、第二引数には検証する内容

問題発生!!
リスト6.13のtestを実行すると

RuntimeError: RuntimeError: database is locked

と表示されエラーになってしまう。

解決のために試したこと

参考記事1
https://qiita.com/kambe0331/items/1eaf2383b39c721e7283
こちらの記事を参考にし、dbファイル下のtest.sqlite3というファイルの名前を一旦変更し再度元の名前に戻しました。

結果
上手くいかず、、、

次に

参考記事2
https://stackoverflow.com/questions/7154664/ruby-sqlite3busyexception-database-is-locked/62730905#62730905
こちらの記事を参考にし、DB Browser for SQlite、サーバー、プロンプトなどを一度全て終了して、改めて起動。

結果
テストをクリア出来ました。

余談
一時的にテストをクリアできましたが、どうやら根本的には解決出来ていなかったようで、こちらのエラーこれからも頻発します。

エラーの解決方法は別の記事にまとめました。
https://qiita.com/shun_study_p/items/fbb4cb2d4c392063c9a9

6.2.3 長さを検証する

長さの検証は:lengthで行うようです。

validates :name,  presence: true, length: { maximum: 50 }
#わかりやすく括弧をつけると
validates(:name,  {presence: true, length: { maximum: 50 }})

先程と同様、慣れるまでは括弧を自分でフォローしておきます。

6.2.5 一意性を検証する

一意性の検証は:uniquenessで行うようです。
:case_sensitiveというオプションを使うことで、大文字小文字を区別するか指定が出来るようです。

case_sensitive: false

とすることで:uniquenessの値が一意かどうかの検証で大文字と小文字を区別しないというオプションを追加しています。


問題発生!!
最後にrails testを実行したところ

Migrations are pending. To resolve this issue, run:
      bin/rails db:migrate RAILS_ENV=test

とのエラーが発生しました。
素直に表示されたコマンド実行するとmigrateできないとのエラーが、、、

解決
以下の記事を参考にし
http://kzlog.picoaccel.com/post-995/

rails db:rollback RAILS_ENV=test
rails db:migrate RAILS_ENV=test

上記のコマンドを実行すると正常に動作しました。

終わりに

今回は少しエラーに躓きました。
しかし、6章の内容はしっかり理解が出来ました。

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

Rails 6で認証認可入り掲示板APIを構築する #3 RSpec, FactoryBot導入しpostモデルを作る

Rails 6で認証認可入り掲示板APIを構築する #2 gitとrubocop導入

RSpec, FactoryBotのインストール

前回の続きから。
テストに使うRSpecとFactoryBotを入れます。

Gemfile
 group :development, :test do
+  gem "rspec-rails"
+  gem "factory_bot_rails"
 end
$ bundle

インストールできたので初期化をします。

$ rails g rspec:install
...
Running via Spring preloader in process 6770
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

今後modelやcontrollerを生成した時に、一緒に自動生成されるRSpecの制御をします。
最低限のテストに収めるためmodelとrequestのみでいこうと思うので、それ以外を使わないよう設定します。

config/application.rb
  class Application < Rails::Application
...

+   config.generators do |g|
+     g.test_framework :rspec, 
+       view_specs: false, 
+       helper_specs: false, 
+       controller_specs: false, 
+       routing_specs: false
+   end
  end
...

ついでにFactoryBotをRSpecの中でクラスを書かなくてもメソッドが使える設定をします。

spec/rails_helper.rb
 RSpec.configure do |config|
+  config.include FactoryBot::Syntax::Methods
...

参考:RailsアプリへのRspecとFactory_botの導入手順

ここまでいったらrubocop動かしてエラー潰し、エラーがゼロになったらgit commitしておきましょう。
なおrubocop -aの-aは自動修正可能なものを自動修正するコマンドなので、1回目大量のエラーが出て場合、もう1回動かすと自動修正後で数が一気に減るはずです。

postモデルの生成

前準備が非常に長かったですが、これでようやく準備が整ったのでmodelを作っていきます。

$ rails g model Post subject:string body:text

こちらのコマンドを実行するとmodel, migration, spec, factory_botの4ファイルが
生成されます。

$ rails db:migrate

もし実行時に以下エラーが出たら、postgresが止まっているので起動します

rails aborted!
PG::ConnectionBad: could not connect to server: No such file or directory
        Is the server running locally and accepting
        connections on Unix domain socket "/var/run/postgresql
...

$ sudo service postgresql95 start

pryを入れる

rails consoleコマンドを実行時、標準のirbよりもpryの方ができることが増えます。

Gemfile
group :development, :test do
+   gem "pry-rails"
+   gem "pry-byebug"
...
end
$ bundle

rails consoleでpostの保存ができるか試す

controllerまで実装して動作確認だと手間がかかるので、rails consoleでmodelがDBに保存・読み込みができるか試します。

$ rails c
...
[1] pry(main)> Post.create!(subject: "hoge", body: "fuga")
[2] pry(main)> posts = Post.all
  Post Load (0.6ms)  SELECT "posts".* FROM "posts"
=> [#<Post:0x0000000006e89850
  id: 1,
  subject: "hoge",
  body: "fuga",
  created_at: Sat, 05 Sep 2020 13:50:01 UTC +00:00,
  updated_at: Sat, 05 Sep 2020 13:50:01 UTC +00:00>]

[1]でpostを1件保存し、[2]で全件取得しました。
どうやら正常にcreate, readができていそうですね。

続きは次回。

連載目次へ

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

Ruby on RailsにおけるServiceクラスのススメ

こんにちは、@hairgaiです。
今回は、賛否両論あるServiceクラスについての自分的な使い方を書いていこうと思います。

Serviceクラスとは

DDD(ドメイン駆動設計)でのサービスから派生している(と勝手に認識している)、ある一つの機能を記述するクラス郡です。
詳しくは説明している人がたくさんいらっしゃるので割愛しますが、ビジネスロジックをモデルとコントローラーの中間でキレイに書けるので、僕はよく使っています。
今回は(僕の使い方は間違ってるかもしれませんが)、自分的な使い方及びそのメリットと思われる部分を書いていきます。

基本的な使い方

まず、基本的な使い方を、コード例と共に紹介しようかなと思います。(これが正解かどうかは正直わかりませんが、見やすいのでいいかなと思ってます)
例えば、SNSなどで「フォローをする」という機能をService層として1ファイルに記述すると、こんな感じにになるかと思います。
※重ねていいますが、これが正解とかじゃないです。

class FollowService
  attr_reader :user, :target_user
  attr_accessor :follow

  def initialize(user, target_user)
    @user = user
    @target_user = target_user
  end

  def perform
    check!

    create_follow!
    run_after_worker!
  end

  private

  def check!
    check_following!
    check_blocking!
  end

  def check_following!
    return true unless user.following?(target_user)

    raise ArgumentError, 'User following target user'
  end

  def check_blocking!
    return true unless user.blocking?(target_user)

    raise ArgumentError, 'User blocking target user'
  end

  def create_follow!
    self.follow = user.follows.create!(target_user: target_user)
  end

  def run_after_worker!
    AfterFollowWorker.perform_in(0.2.seconds, follow.id)
  end
end

ピュアなRubyのクラスで作る

基本的に何かGem等を使って作ることは、僕はしていないです。
ピュアRubyでの実装にすることで「実装の理解に対する障壁を下げる」効果を狙っています。
Serviceクラスは(重い機能になると)ロジックが複雑になりがちなので、なるべくシンプルに作成し、誰が見てもすぐに理解できるように心がけています。

クラス名を定める

命名に好みがあると思いますが、機能を象徴するクラスであるので[動詞]([目的語])Serviceで統一しています。
命名規則をつけることで機能が推論しやすくなるというメリットがあります。

publicなメソッドはperformのみにする

ここらへんも好みがあると思います(callとかにしたりする人も多いです)が、基本的にはpublicなメソッドを一つだけ生やし、それ以外は呼べないものとします。
これは、Serviceクラスは単一の機能を象徴するクラスであり、それを使用することで実現できる機能を単一のものと限定するためです。
この単一のpublicメソッドは結構いろいろな方が言っていますが、僕も設計時点において迷いが全くなくなり実装スピードが格段に上がったので採用しています。

publicなメソッドで呼ぶのはprivateメソッドのみ

これも見通しが良くなる + 1メソッドごとの責務が軽くなり、ガード節が使いやすくなったりするので採用しています。
ここらへんは好みの分かれるところだと思います。

後処理等へのアプローチを単一にする

上のFollowServiceでも書いていますが、業務で使用する以上はユーザさんに対するレスポンスを一番に考えます。
そういった場合のアプローチとしては「1レスポンス中には必要な処理のみを行う」というものがあり、後続処理などはジョブとしてキューに格納し、ジョブサーバ等に処理させることになります。
その際に、ControllerやModel等色々な場所にジョブをコールする処理が散らばると、プロジェクト全体での見通しが悪くなります。

そこで、HogeServiceの後処理のジョブはAfterHogeJobにする、というような命名規則にし、Serviceクラスでのみコールするという決まりにすることで、全体の見通しを良くすることができます。

Controllerの肥大化の解消

言うまでもないですね。

class FollowsController < ApplicationController
  def create
    target_user = user.find(params[:target_user_id])
    service = FollowService.new(current_user, target_user)
    service.perform

    redirect_to user_path(target_user)
  end
end

Modelの肥大化の解消

こちらも言うまでもないですね。
Modelに書いていたものを全部Serviceクラスに持っていき、モデルがデータの関連付けやバリデーション、その他単一モデルに関するメソッド等のみになります。
そのため、モデルからロジックの多いメソッドがなくなり、肥大化した見づらいモデルというものがなくなります。

あとがき

こうして改めて書いてみると、僕は色々と責任をServiceクラスに負わせている書き方をしているんだなぁ、と思います。
Serviceクラスは必要ない、等々議論の余地はあると思いますが、こういった設計等の話はあくまで手法の一つなので、自分たちのビジネスに合わせて適切に使用できると良いですね。

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

[ruby]引数をつけた際のメソッドの呼び出し

概要

bitcoinの自動売買ツールを作成している時に、メソッドに引数をつけて使うことがあったので、記録しておきます。

メソッドの定義

メソッドの定義は以下のような式になります。

def メソッド名(変数1, 変数2, ...)
  実行する処理
end

メソッドの呼び出し

メソッドを呼び出すときの式は以下のような形です。

メソッド名(引数1, 引数2, ...)

実際に見てみます

number.rb
def number(a,b)
  return a * b
end

puts number(3,4)
#実行結果は12になる

numberメソッドの引数として、aに3、bに4が呼び出されています。
結果、3×4が実行されて、12が出力されます。

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

Cを使ってRubyのメソッドを書く その1

目次

Cを使ってRubyのメソッドを書く その1 <- ここ
Cを使ってRubyのメソッドを書く その2 (Numo::NArray)

はじめに

C++でRubyの拡張ライブラリを書く方法は、「C++を使ってRubyのメソッドを書く」にありますので参考にしてください。C++さえ書ければほとんど苦労せずにライブラリが作成できますので、自分で一から書かれるのでしたらそちらをお勧めします。

ここでは、Cで書かれた関数を拡張ライブラリにする方法を紹介します。C++でもCと同様の書き方ができますので、すでにCで書かれたライブラリがある場合にも使うことができます。

  1. 2. はdouble, float, intなどの単純な変数に受け渡し、3.で文字列に受け渡し、4.で既存のライブラリを拡張ライブラリにする方法を紹介します。 配列を受け渡す方法は(その2)を参考にしてください。

1. コンパイラが提供する関数を呼び出す

SWIGインターフェース定義ファイルを書きます。
上の%{..%}の中にはヘッダファイルを読み込むか、ヘッダファイルに相当することを書きます。

test.i
%module test  // モジュール名 最初の文字は大文字に変換される
%{
#include <math.h>
%}

%include "typemaps.i"
extern double floor(double x);
// double modf(double value, double *iptr); ポインタで値を受け取る時は*OUTPUTに変更
extern double modf(double value, double *OUTPUT);

下の方にはRubyから呼び出す関数を書いています。math.hには多くの関数のヘッダが書かれていますが、その中からfloorとmodfをRubyから利用できるようにします。
modfに2番目の引数はポインタになっておりdouble型の値を返します。ポインタでは入力、出力、入出力が可能で、*INPUT, *OUTPUT, *INOUTと記述することでそれぞれの機能を持たせられます。

extconf.rb
require 'mkmf'
create_makefile("test") # モジュール名と同じに

Makefikeを作成するためのRubyプログラムです。

以上のファイルを同じフォルダの中に入れ、次のコマンドを実行します。

swig -ruby test.i
ruby extconf.rb
make

以上で、test.bundle(拡張子はOSにより異なります)ができあがります。
テスト用プログラムです。

test1.rb
require "./test"
p Test.floor(2.3)
p Test. modf(2.3)

2. 自作の関数を呼び出す

単純な変数を受け渡しする関数を書いてみました。ごく普通のCの関数です。2倍の値を返す関数です。ポインタは使っていません。

test2.c
double twicefold_d(double x){
  return(x*2.0);
}
test.i
%module test
%{
double twicefold_d(double x);
%}

double twicefold_d(double x);

ヘッダファイルは作っていませんので、%{..%}の中にヘッダファイルに相当すること書きます。
extconf.rbファイルとコマンドは上と同じものを使ってください。
フォルダの中に.cや.cppなどのプログラムファイルがありましたら、全部コンパイルてリンクしようとしますので、不必要なファイルは入れておかないようにしましょう。

テスト用プログラムです。

test2.rb
require "./test"
p Test.twicefold_d(3.4)

3. 自作の関数を呼び出す 文字列を渡す

文字列も関数に簡単に渡すことができます。

test3.c
#include "test3.h"
int string_length(char* str){
  return(strlen(str));
}
test3.h
#include <string.h>
int string_length(char* str);

ここではヘッダファイルを作っています。そう手間はかかりませんので、ヘッダファイルを作っておいた方が何かと便利なように思います。

test.i
%module test
%{
#include "test3.h"
%}

%include test3.h

同じくextconf.rbファイルとコマンドは上と同じものを使ってください。

test1.rb
require "./test"
p Test.string_length("abcd")

4. 既存ライブラリを利用できるようにする

GSLの関数gsl_hypotとgsl_hypot3を使えるようにします。GSLのヘッダファイルを読み込んで2つの関数のインターフェースを定義しています。2つの関数はヘッダファイルからそのままコピーしているだけで変更はしておりません。

test.i
%module test
%{
#include "gsl/gsl_math.h"
%}

extern double gsl_hypot(const double x, const double y);
extern double gsl_hypot3(const double x, const double y, const double z);
extconf.rb
require 'mkmf'
dir_config("gsl")
have_header("gsl/gsl_math.h")
have_library("gsl") # libgsl.soをリンクする
create_makefile("test")

GSLのヘッダファイル読み込みとlibgsl.soをリンクする必要がありますので、そのための3行を追加しています。コマンドは上と同じで大丈夫なことが多いのですが、ヘッダファイルとlibgsl.soの場所がわからわからない時には次のようにruby extconf.rbに後にパスを書きます。

ruby extconf.rb -- --with-gsl-dir=/path
--with-gsl-include=/path/include
--with-gsl-lib=/path/lib

/path/include/gsl/gsl_math.hと/path/lib/libgsl.soのような場所にファイルがある時には
--with-gsl-dir=/pathで両方いっぺんに指定できます。下2つは片方ずつ指定できます。

test4.rb
require "./test"
p Test.gsl_hypot(3, 4)
p Test.gsl_hypot3(3, 4, 5)

CentOS 8.2 Ruby 2.6.6
macOS 10.13.6 Ruby 2.7.1
SWIG 4.0.2

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

ゲッターについての備忘録

class Fruits

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

  def  name #ゲッター
    @name   #@nameの値を取得
  end

  def color #ゲッター
    @color  #@colorの値の取得。
  end

end

class InfoFruits

  def initialize(fruits)#仮引数、実引数を受け取っています。
    @fruits = fruits #実引数をインスタンス変数に代入することで定義を行います。
  end

  def fruits
    @fruits  #self.fruitsとは、インスタンス自身です。
  end        #下記において、インスタンスを利用するために、ここで一度インスタンスの値の取得を行なっています。

  def info_fruits
    self.fruits.each do |fruit|
      puts "#{fruit.name}#{fruit.color}です。"
            #配列fruitsには、インスタンスの値nameとcolorが含まれている。
    end
  end

end

fruits = []

fruit_name = "りんご"
fruit_color = "赤色"

fruits << Fruits.new(fruit_name, fruit_color)
#@nameの値と@colorの値を戻り値として配列に代入しています。

kind_fruit = InfoFruits.new(fruits)#その配列を実引数として渡しています。
kind_fruit.info_fruits #インスタンスメソッドinfo_fruitsの呼び出しです。
class Fruits

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

  def  name
    @name 
  end

  def color
    @color 
  end

end

class InfoFruits

  def initialize(fruits)
    @fruits = fruits
  end

  # def fruits
  #   @fruits
  # end

  def info_fruits
    @fruits.each do |fruit| #ゲッターを用いない場合、配列が代入されたインスタンス変数を置くと、出力結果は同じになります。
      puts "#{fruit.name}#{fruit.color}です。"
    end
  end

end

fruits = []

fruit_name = "りんご"
fruit_color = "赤色"

fruits << Fruits.new(fruit_name, fruit_color)

name_color_fruit = InfoFruits.new(fruits)
name_color_fruit.info_fruits

誤った認識をしている箇所、認識が不足している部分について、ご指摘頂けましたら、幸いに存じます。

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