20200104のRubyに関する記事は19件です。

form_withでデフォルトのデータ送信先を変更する[Rails]

ポートフォリオアプリを作っています。
作品の一覧と各作品の詳細は誰でも見られるようにしようと思いますが、作品の投稿と編集は管理者だけができるようにしたいと思います。

管理者ユーザーの作成は、過去の自分のこちらの記事を読んで実装できたのですが、

【初心者向け】管理者ユーザーと管理者用controllerの追加方法[Ruby, Rails]

その後、管理者ユーザーの投稿機能をつけ、submitボタンを押したときに、以下のようなエラーが出ました。

Image from Gyazo

何、/worksPOSTメソッドでつながるルーティングがないそうです。
確かに、rake routesで確認してみると、管理者ユーザーが画像を投稿するときのルーティングはadmin_works_pathで、URLも /admin/worksです。

      works GET    /works(.:format)  works#index                                                                    
       work GET    /works/:id(.:format)  works#show                                                                   
admin_works GET    /admin/works(.:format)  admin/works#index                                                                  
            POST   /admin/works(.:format)  admin/works#create                                                                  

ところが、form_withの記述を見直してみても、以下の通り。

= form_with model: @work, local: true do |f|
 # 中略
 =f.submit

そうでした、form_withは投稿先のメソッドに応じて、よしなにURLを変更してくれるのでした。ちなみに、これでPOST(create)にもPATCH(edit)にも対応してくれます。わー、便利。

ですが、今回はこちらのデフォルトのURLを変更しなければいけません。
form_withのAPIドキュメントを翻訳してくれた方がいたので、こちらの記事を参照しました。

Rails 5.1〜: ‘form_with’ APIドキュメント完全翻訳

こちらの記事によると、「ルーティングをadmin_post_urlのように名前空間化する場合は以下のようにします。」とのこと。

<%= form_with(model: [ :admin, @post ]) do |form| %>
  ...
<% end %>

こちらを参考に、hamlに合わせて、以下のように記述を変更しました。
第二引数にurlを追記しています。

= form_with model: [ :admin, @work ], local: true do |f|

・・・そしたら、、、動いた!!
以上、簡単ですが、form_withのデフォルトの送信先を変更する方法でした^^

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

Ruby on Rails にdeviseを使ってログイン機能を導入する

書きつくされているRuby on Railsにdeviseを導入する記事ですが、ひとそれぞれ目的が微妙に違うのか躓いてしまいます。

そこで僕の無知から来るミスも含めて共有し、同じ過ちを皆様に犯さないでいただけるのなら記事を書いたかいがあります。
また、よりよい方法があると思いますが、ご参考にしていただければと思います。

それではいきましょう!

環境

・macOS
・Ruby 2.7.0
・Ruby on Rails 6.0.2

もくじ

1.Railsのプロジェクトを作成する
2.簡単な画面遷移を作る
3.ログイン機能を実装する(ここでdeviseが登場)

1.Railsのプロジェクトを作成する

まずはアプリを開発するディレクトリを作成する。

$ mkdir app_name
$ cd app_name 

その後のプロジェクトの作成方法は以下を参照してください。
https://qiita.com/tom-ohiro/items/0030999b838bfe05006dak

2.簡単な画面遷移を作る(トップページの作成)

この工程があったために勝手に混乱することに。
まずはユーザーコントローラを作って、トップページを作成。
以下のコマンドでusers_controller.rbなどのファイル、ディレクトリが作成される。

$ rails g controller users
app/controllers/users_controller.rb
    def index
    end

ルートを設定する。

config/routes.rb
get  '/'  => 'users#index'

表示ページを作成する。inde.html.erbは新規ファイルを自分で作成する。

app/view/users/index.html.erb
Hello world! とプログラマっぽく書く

以下を実行してサーバーを起動する。

$ rails s

http://localhost:3000/
にアクセスするとトップページが表示される。

3.deviseを用いてログイン機能を実装する

ここからが本番。
コマンドの順番を間違えると初学者には非常に厄介に感じられるので、よく注意して進んでください。

1.Gemfileに以下を記入

gem 'devise'

2.以下のコマンドをターミナルで実行
順番を間違えないようにゆっくりやりましょう。

$ bundle install

以下のような画面が表示される。
スクリーンショット 2020-01-04 22.18.20.png

僕は以下のコマンドを忘れていて非常に困った。。。皆様ご注意ください。

$ rails g devise:install

うまくいくとこんな感じの画面が表示される。
スクリーンショット 2020-01-04 22.21.13.png

こちらの設定は1,2だけ別途やっておけば大丈夫。
1.はメールを送るときの設定の話。以下の記述を追記してください。
まぁ書いてあるとおりにやるだけなんですが、英語で表示されているだけでうわってなってしまいますよね。
これからこんな画面の連続なので、頑張って読みに行くようにしてみましょう。
読んで見ればこんなこと解説するまでもねーじゃねえかって思うようなことなんです。
でも、はじめのとっつきにくさは半端ないので少しでもハードルが下がればと思い解説させていただきました。

config/environments/development.rb
   config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

2.はトップページの表示の話。
以下の記述は上の方で書いた内容と同じことなので、以下の内容を上書きしていただければ大丈夫です。

config/routes.rb
  root to: "home#index"

次に以下のコマンドをターミナルで実行します。
これでログイン画面や新規登録画面などを用意してくれます。

$ rails g devise:views

続いてUserモデルの作成です。
migrationファイルなどが作成されます。
初めての場合は特にmigrationファイルの編集なしで連続して以下のコマンドを入力しても大丈夫でしょう。
あとから保存内容を変えることもできますし、今のうちに編集していただいてももちろん大丈夫です。

$ rails g devise User
$ rake db:migrate

続いて以下の画面。
僕はここで非常に困ってしまいました。
すでにusers_controllersを作ってしまっているので、名前がかぶってしまうのではないか?
ということでコントローラを消すという作業をしてしまいました。
deviceでよく使うusers_controllerと混同してしまいますが、実行しても問題ありません。今後は先を見越して先にdeviceの導入からしたほうがいいでしょう。

$ rails g devise:controllers users

以下の記述はログインする前にページにアクセスしようとすると、ログイン画面に遷移させるというものです。

app/controllers/application_controller.rb
  before_action :authenticate_user!

これでdeviceの導入は完了です。
トップページにアクセスしようとするとログイン画面に遷移すると思います。

おわりに

deviceはとっても便利な機能である一方でわけわからなくても使えてしまうgemでもあります。
便利さと引き換えに使いこなしまでのレベルになるにはもう何段階かレベルアップする必要がありますが、まずは使える技を少しずつ増やして自信をつけることが大切だと思います。
共に頑張っていきましょう。
もし、この方法でうまくいかないとか、このやり方はおかしい。このやり方の方が適当ではないかと言う場合はコメントいただけると大変うれしく思います。
最後までお読みいただきありがとうございました。

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

【Rails】Rspec何言ってるか解読してみた件。コントローラー#index編

今回は、写真とタイトルを投稿するアプリケーションのtweetsコントローラーのindexアクション(トップページを表示する)が問題なく動くかどうか、Rspecという言語を使ってテスト。

テストする内容は2つ。

  1. tweets_controller.rb内でindexアクションの定義文で生成した@tweetsに、意図したものが入っているかな?

    tweets_controller.rb
    def index
    @tweets = Tweet.includes(:user).page(params[:page]).per(5).order("created_at DESC")
    end
    
  2. テスト環境上で、indexアクションを起こすリクエストを飛ばして、indexアクションに対応したビューが返ってくるかな?

コントローラーのテストの際は、2つの点に注意。

①tweets_controller.rbにおいて、テストしたいアクションに対し、before_actionが定義されていないこと。
②gem rails-controller-testingをインストールしよう。

テストで使うダミーデータの生成

また、トップページはこれまで投稿されたツイートが一覧表示される仕様。テスト環境上でのindexアクションで持ってくる、これまでに投稿されたツイートデータを、ダミーでテスト用に生成。gemのfactory_botを利用しよう。ツイート投稿の際、そのツイートは誰が作ったのかという情報も含まれるため、ダミーユーザーも作成しダミーツイートにその情報を含ませよう。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    nickname              {"mamama"} 
    email                 {Faker::Internet.email}
    password              {"00000000"}
    password_confirmation {"00000000"}
  end
end
spec/factories/tweets.rb
FactoryBot.define do
  factory :tweet do
    text  {"I love animals"}
    image {"animals.png"}
    created_at { Faker::Time.between(from: DateTime.now - 1, to: DateTime.now) }
    user  
  end
end

email{Faker::Internet.email}
tweets_controller.rb内indexアクション定義文のインスタンス変数には、複数のツイートデータが入る。テストで、インスタンス変数に意図したデータが入っているかを調べる際、factory_bot :tweetで生成したツイートデータを複数個一旦登録しておく。そして、テスト環境上で起こすindexアクションで、登録したデータ達を持って来て、インスタンス変数にそれらが入っているか確かめる。しかしemail {mmmm@e.mail}などでは、テスト上で登録するツイート達の投稿者(user)のemailが全て被ってしまう。今回アプリの仕様上、同じメールアドレスは登録できないようにしてあるので、Fakerというgemをインストールしてemailを自動でランダムに設定させよう。

created_at { Faker::Time.between(from: DateTime.now - 1, to: DateTime.now) }
tweets_controller.rb内indexアクション定義文に、order("created_at DESC”)とある。ツイートが投稿された日時(created_at)を大きい方から小さい方へ(DESC)、つまり新しいツイート順に並べる(order)という意味。何も指定しないと、ダミーツイートデータのcreated_atは全て同じになるので、意図した順番になっているかテストできない。バラバラな日時を設定する必要があるので、Fakerというgemをインストールし、現在の日時の1日前(DateTime.now - 1)から現在の日時(DateTime.now)の間で、created_atを自動でランダムに設定させよう。

user
factory_botを利用して作った :userのこと。

それでは、見ていきましょう。

テストコードを解読。

tweets_controller_spec.rb
require 'rails_helper'

describe TweetsController, type: :controller do
  describe 'GET #new' do
    #「【Rails】Rspec何言ってるか解読してみた件。コントローラー#new編」を参照。
  end

  describe 'GET #edit' do
    #「【Rails】Rspec何言ってるか解読してみた件。コントローラー#edit編」を参照。
  end

  describe 'GET #index' do
    # 1. tweets_controller.rb内でindexアクションの定義文で生成した@tweetsに、意図したものが入っているかな?
    it "populates an array of tweets ordered by created_at DESC" do
      dummyTweets = create_list(:tweet, 3)
      get :index
      expect(assigns(:tweets)).to match(dummyTweets.sort{|a, b| b.created_at <=> a.created_at })
    end

    # 2. テスト環境上で、indexアクションを起こすリクエストを飛ばして、indexアクションに対応したビューが返ってくるかな?
    it "renders the :index template" do
      get :index
      expect(response).to render_template :index
    end
  end
end

1. tweets_controller.rb内でindexアクションの定義文で生成した@tweetsに、意図したものが入っているかな?

require 'rails_helper'
RailsでRspecを利用する時の共通の設定が記載されているrails_helper.rbを読み込む。これがなきゃ始まらない。

describe TweetsController, type: :controller do end
tweetsコントローラーのテストをするよ。 type: :controllerこれは書かなきゃいけないみたいですね。

describe 'GET #index' do end
GETメソッドのindexアクションに関するテストだよ。

it "populates an array of tweets ordered by created_at DESC" do end
do - endの間に書かれている、実際に動くテストコードの説明。投稿日時で降順(created_at DESC)となっているtweetsの配列(an array of)があるか(populates)と言っている。日本語でも良いらしい。ちなみに、it do endのセット1つで、1exampleと呼ぶ。

dummyTweets = create_list(:tweet, 3)
factory_botで生成したダミーツイートを3つ登録するという意味。それをdummyTweetに入れる。ちなみにこれら3つのダミーツイートの作成者はemailだけが異なっている。

get :index 
テスト環境上でリクエストを起こすコード。get :indexでアクション名indexをそのリクエストに情報として渡す。

expect(assigns(:tweets)).to match(dummyTweets.sort{|a, b| b.created_at <=> a.created_at })
インスタンス変数に意図したものが入っているかな?というテストコード。assignsは、インスタンス変数の中身をチェックしてくれるやつ。今回は、tweets_controller.rb内で定義した@tweetsが対象なので、(:tweets)と書こう。コードの意味は、@tweetsの中身は、一旦登録させてindexアクションで持ってこさせたdummyTweetsと一緒でかつ並びもcreated_atの降順(.sort{|a, b| b.created_at <=> a.created_at })である(.to match)と期待する(expect)。

tweets_controller_spec.rb
  #省略
  describe 'GET #index' do
    # 1. tweets_controller.rb内でindexアクションの定義文で生成した@tweetsに、意図したものが入っているかな?
    it "populates an array of tweets ordered by created_at DESC" do
      dummyTweets = create_list(:tweet, 3)
      get :index
      expect(assigns(:tweets)).to match(dummyTweets.sort{|a, b| b.created_at <=> a.created_at })
    end

    # 2. テスト環境上で、indexアクションを起こすリクエストを飛ばして、indexアクションに対応したビューが返ってくるかな?
    it "renders the :index template" do
      get :index
      expect(response).to render_template :index
    end
  end
  #省略

2. テスト環境上で、indexアクションを起こすリクエストを飛ばして、indexアクションに対応したビューが返ってくるかな?

it "renders the :index template" do end
do - endの間に書かれている、実際に動くテストコードの説明。indexアクションに対応したビューファイル(template)が呼び出される(renders)ことをテストしますと言っている。

get :index 
テスト環境上でリクエストを起こすコード。get :indexでアクション名indexをそのリクエストに情報として渡す。

expect(response).to render_template :index
ちゃんと期待するビューが返ってくるかな?というテストコード。resposeが、indexが起きた時に呼ばれるrender_template(ビューである)と期待する(expect)。

テストを実施。

ターミナルにて
% bundle exec rspec spec/controllers/tweets_controller_spec.rb
を実行しよう。


初学者の視点からRailsアプリケーションのRspecを使ったテストについてまとめてみました。指摘やご意見お待ちしてます。モデルや、コントローラーのその他アクションについても、まとめていきたいと思います。

最後までご覧いただきありがとうございました!

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

Ruby on railsとDjangoの違いまとめ~基礎編~

1. はじめに

 プログラミングを始めて4ヶ月の初心者です!
Ruby on railsとDjangoを勉強中なので、
アウトプット(備忘録)として、その基礎的なコードの違いを書いていきたいと思います!!:pencil2::beginner:
railsから学習を始めていることから、
基本rails目線で、Djangoの場合はどうか?という目線で書いております:smile:
少しでも、ご参考になりましたら幸いです。
(勉強中なので、随時追記・修正していく予定です!)

2. MVCモデル(Rails)MTVモデル(Django)

"View"の名前が被っているので、ややこしい感じがしますが、大まかな流れは同じです。
自分のイメージを以下に示します。
スクリーンショット 2020-01-04 20.46.48.png

3. ルーティング編

(Railsの場合)
routes.rbに記載していきます。
ターミナル上で"rake routes"を叩けば、URLパターンやprefixを確認できます!

config/routes.rb
root 'products#index'
get 'index', to: 'products#index'

(Djangoの場合)
urls.pyに記載していきます。
path()関数を使用し、これは引数にroute,view,name,kwargsの4つの引数を取ります。
ここでは、ルートURL('')に、viewのindex関数を対応させ、indexという名称で他のファイル(templateなど)から呼び出せるようにしています。
viewにおいて汎用クラスを呼び出す場合は、"as_view"を使用します。

app/urls.py
from django.urls import path
from . import views

app_name = "アプリ名"

urlpatterns = [
    path("", views.index, name="index"),
    path("products/", views.ProductListView.as_view(), name="products_list"),
]

4. コントローラー/ビュー編

4-1. htmlファイルの指定方法

(Railsの場合)
controllerのアクション名と同じ名前のviewが呼び出されるというルールがありますので、
特にコードは必要なく、アクション名と同名のviewファイルを準備することで、そのview(html)ファイルが呼び出されます。

controllers/products_controller.rb
def index
end
views/index.html.erb
.title
  haml等でhtmlを記載していきます

(Djangoの場合)
関数を使用するか、クラスを使用するかによって、
それぞれ、renderメソッドかtemplate_nameメソッドを使用します(他にもいっぱいあるようです)

app/views.py
# 関数を使用する場合
def index(request):
    return render(request, "index.html")

# クラスを使用する場合
class ModelListView(ListView):
  model = Model
  template_name = "index.html" 

4-2.インスタンス変数のhtmlファイルへの渡し方

(Railsの場合)
htmlファイルにおいて、変数を使用するために、@をつけて、インスタンス変数を定義します。

products_controller.rb
def index
  @product = Product.where(id: 1..10)
end

(Djangoの場合)
変数がないのにhtmlファイルでデータが扱える、というのが衝撃的です!

views.rb
class XxxListView(LoginRequiredMixin, ListView):
  model = Xxx
  template_name = "xxx.html"
  # templateにおいて、xxx_listで配列としてデータが使用できる

(とはいっても、get_context_dataやget_querysetで変数の定義やデータの選択が可能です)

5. ビュー/テンプレート編

・部分テンプレートの書き方

(Railsの場合)
renderメソッドを使用します。

index.html.erb
<%= render '(フォルダ名/)yyy.html.erb' %>
-# render先は'_yyy.html.erb'という名称とします

(Djangoの場合)

base.html
{% block content %}
  # ここに他のhtmlファイルを差し込みます
{% endblock %}
index.html
{% extends "base.html" %}
{% block content %}
  # この部分をbase.htmlに差し込みます(逆かもしれません)
{% endblock %}

ここはrailsの方が自由度が高いかと思います。

6. モデル編

・マイグレーション

(Railsの場合)
モデルの作成 (rails g model Xxx) or マイグレーションファイルの作成 (rails g migration)
->マイグレーションファイルの記入
->マイグレーションの実行(rake db:migrate)

Migrationファイル
##一例です
  t.string :name,               null: false

(Djangoの場合)
models.pyへの記入
->マイグレーションファイルの作成(python manage.py makemigrations)
->マイグレーションの実行(python manage.py migrate)

models.py
class Xxx(models.Model):
  xxx = models.CharField(max_length=200)
  user = models.ForeignKey(User, on_delete=models.CASCADE)

  def __str__(self):
    return self.title

7. 感想

触っている時間が長いせいもあるかと思いますが、
railsの方がファイルの関係性も簡潔であり、
erbファイルにロジックが書けたりと、コードの自由度が高く、狙い通りのアプリを作成しやすい気がします。
一方で、Djangoは、やや構成に癖があるような気がします。
ただ、記述量が少なく、慣れたら速そうです。
さらに、pythonのメリットは豊富なライブラリにあると考えているので、
その豊富なライブラリをアプリで活かせるかどうか、が、ポイントのような気がしています。
結局はどんなものを作りたいか、によるのではないでしょうか。
(もっと勉強して、随時修正予定です!)
読んでいただきありがとうございました:bow_tone3:

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

RailsでiOSアプリのURI スキームに対応する

image.png

一瞬「あれっ...」ってなったので備忘録

TL; DR

  • routes.rb内でダイレクトルーティングで定義すればOK

バージョン

  • Ruby:2.6.1
  • Rails:5.2.3

タイトルにiOSと書いてますがAndroidも対応できます。ただのRoutingの話なので。

やりたいこと

  • RailsがWebとモバイルアプリの両方のサーバサイドの役割を担っている
  • モバイルアプリ内で、特定の画面に遷移させるURIスキームを使いたい
    • メニューなどで表示されていないページへの遷移など
  • 上記の例としてInstagramに「運営からのコメント」があり、チャットで操作説明を受ける画面に遷移させたいような目的があったとする
    • ヘルパーリンク:qa_chat
    • URIスキーム:instagram://qa_chat

解決策

routes.rb

routes.rbで、以下のようなダイレクトルーティングを設定してあげれば良いです。

routes.rb
direct :qa_chat do
    "instagram://qa_chat"
end

ヘルパーリンク

Railsと同一のViewでヘルパーリンクを利用する場合は、以下のように記述します。

sample.erb
<%= link_to qa_chat_url %>

ちなみにダイレクトルーティングについて、Railsガイドでは以下の部分に記載があります。
(このURIスキームについての記載はありませんが、あくまで参考までに)
image.png

小ネタ

外部URLもroutes.rbで変数として定義できる

ダイレクトルーティングといえば、こちらの使い方のほうが一般的です。

よく使う外部URLを変数定義して、Railsアプリケーション内で使うことができます。
サービスLPのURLを記述する時は、個別にではなく、routes.rbにまとめて記述するほうが管理しやすいです。

example.rb
direct :homepage do
    "http://www.rubyonrails.org"
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FizzBuzzの問題を外部入力した数値から数えてアプリケーションを作ってみた

通常FizzBuzzの問題は、1~100までを数えるものが多いみたいですが、今回はgets.to_iを使用し、入力した数を(含めて)から30数えるアプリケーションを作ってみる

目的

 ・学習のため
 ・入力した数値からFizzBuzzできたら面白そうだと思ったから

下記条件を満たすように作る

 ・外部入力させる
 ・入力させた数値を含めて、そこから30数える
 ・3の倍数の場合はFizzと表示させる
 ・5の倍数の場合はBuzzと表示させる
 ・15の倍数の場合はFizzBuzzと表示させる
 ・「数値を入力してください」と表示させた後に、入力させる
 ・数値を入力させた後は、改行させるか、文字をいれる(ターミナル上で見栄え良くするため)

擬似コードを書く

puts "数え始める開始の数値を入力してください"
x = 数字を入力させる
puts "下記に入力した数値を含めて30数えます"
y = 30数えるため、外部入力させた数値に30を足す

  xから(30数える=y)each do|i|
※ここから先はいつものFizzBuzzと一緒
if ○○なら
 puts ○○と表示する

elsif
   ○○なら
 puts ○○と表示する

elsif
   ○○なら 
 puts ○○と表示する
else
  それ以外なら
 puts ○○と表示する

できたアプリケーションがこちら

puts "数え始める開始の数値を入力してください"

x = gets.to_i
puts "下記に入力した数値を含めて30数えます"
y = x + 30

  (x..y).each do|i|

  if i%15==0
      puts "FizzBuzz"
  elsif
      i%3==0
      puts "Fizz"
  elsif
      i%5==0
      puts "Buzz"
  else
      puts i
  end
end

動作確認しよう。今回は、100から数えてみる。

数え始める開始の数値を入力してください
100
下記に入力した数値を含めて30数えます
Buzz
101
Fizz
103
104
FizzBuzz
106
107
Fizz
109
Buzz
Fizz
112
113
Fizz
Buzz
116
Fizz
118
119
FizzBuzz
121
122
Fizz
124
Buzz
Fizz
127
128
Fizz
Buzz

あっさり出来た。
感覚的にできるもんすね・・・・・。

たぶん間違っていないかと思われる。
他に良い方法ありそうでしたら、ご指摘いただけますと幸いです。

参考:FizzBuzz基本問題
https://qiita.com/pontarou194/items/6cf17868a8346613fddf

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

FizzBuzzの問題を外部入力した数値から数えるアプリケーションを作ってみた

通常FizzBuzzの問題は、1~100までを数えるものが多いみたいですが、今回はgets.to_iを使用し、入力した数を(含めて)から30数えるアプリケーションを作ってみる

目的

 ・学習のため
 ・入力した数値からFizzBuzzできたら面白そうだと思ったから

下記条件を満たすように作る

 ・外部入力させる
 ・入力させた数値を含めて、そこから30数える
 ・3の倍数の場合はFizzと表示させる
 ・5の倍数の場合はBuzzと表示させる
 ・15の倍数の場合はFizzBuzzと表示させる
 ・「数値を入力してください」と表示させた後に、入力させる
 ・数値を入力させた後は、改行させるか、文字をいれる(ターミナル上で見栄え良くするため)

擬似コードを書く

puts "数え始める開始の数値を入力してください"
x = 数字を入力させる
puts "下記に入力した数値を含めて30数えます"
y = 30数えるため、外部入力させた数値に30を足す

  xから(30数える=y)each do|i|
※ここから先はいつものFizzBuzzと一緒
if ○○なら
 puts ○○と表示する

elsif
   ○○なら
 puts ○○と表示する

elsif
   ○○なら 
 puts ○○と表示する
else
  それ以外なら
 puts ○○と表示する

できたアプリケーションがこちら

puts "数え始める開始の数値を入力してください"

x = gets.to_i
puts "下記に入力した数値を含めて30数えます"
y = x + 30

  (x..y).each do|i|

  if i%15==0
      puts "FizzBuzz"
  elsif
      i%3==0
      puts "Fizz"
  elsif
      i%5==0
      puts "Buzz"
  else
      puts i
  end
end

動作確認しよう。今回は、100から数えてみる。

数え始める開始の数値を入力してください
100
下記に入力した数値を含めて30数えます
Buzz
101
Fizz
103
104
FizzBuzz
106
107
Fizz
109
Buzz
Fizz
112
113
Fizz
Buzz
116
Fizz
118
119
FizzBuzz
121
122
Fizz
124
Buzz
Fizz
127
128
Fizz
Buzz

あっさり出来た。
感覚的にできるもんすね・・・・・。

たぶん間違っていないかと思われる。
他に良い方法ありそうでしたら、ご指摘いただけますと幸いです。

参考:FizzBuzz基本問題
https://qiita.com/pontarou194/items/6cf17868a8346613fddf

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

「文言」ruby出力に挑戦

はじめに

漢文風プログラミング言語「文言/wenyan-lang」は、初期commitが2019年12月という新しい言語で、現在進行形で開発が進んでいます。その美しいレンダリングは漢字圏に住む我々日本人としても目の離せない言語です。

初期時はハードルが高めでしたが、現在はnpmからインストールし簡単に使い始められます。まだの方はこちらの記事を参考に今すぐインストールしてみて下さい

この記事では「文言/wenyan-lang」で書かれたプログラムからrubyソースに変換し、実行してみます

動作確認

こちらの記事の確認環境は以下の通りです

npm v6.13.4
wenyan v0.2.0

実行

公式を見ると
Screen Shot 2020-01-04 at 1.24.14.png

ruby出力に対応しているとの事、
usageを確認します

$ wenyan
,_ ,_
 \/ ==
 /\ []

WENYAN LANG 文言 Compiler v0.2.0

Usage: wenyan [options] [files...]

Options:
  -v, --version        Output the version
  -l, --lang <lang>    Target language, can be "js", "py" or "rb" (default: 
                       "js")
  -c, --compile        Output the compiled code instead of executing it
  -e, --eval <code>    Evaluate script
  -i, --interactive    Interactive REPL
  -o, --output [file]  Output compiled code or executing result to file
  -r, --render         Outputs renderings
  --roman [method]     Romanize identifiers. The method can be "pinyin", 
                       "baxter" or "unicode"
  --outputHanzi        Convert output to hanzi (default: true)
  --log <file>         Save log to file
  --title <title>      Override title in rendering
  -h, --help           Display help

--lang rb で行けそうです。
実行してみると

$ wenyan -l rb examples/helloworld+.wy 
Target language "rb" is not supported for direct executing. Please use --compile option instead.

替わりに--compileオプションを使えとの事、実行してみます

$ wenyan --compile rb examples/helloworld+.wy 
ENOENT: no such file or directory, open '/xxxxxxx/wenyan-lang/rb'

--compileのオプションの指定方法がいまいちのようです
言語指定してみましょう

$ wenyan --compile -l rb examples/helloworld+.wy 
# encoding: UTF-8
require 'forwardable'
<略 45行>
#####=3
甲.times do |_rand1|
    _ans1="問天地好在。"
    p([_ans1].join)
end 

なんか50行を超える行が出力されましたが、最後の5行は公式にあった出力です。やった:v:

ruby出力方法がわかったので、公式にある「エラトステネスの篩」を実行してみます
Screen Shot 2020-01-04 at 2.56.37.png

また、その実行結果を保存し、rubyで実行してみます

$ wenyan --compile  -l rb -o sieve.rb examples/sieve.wy 
$ tail -n 42 sieve.rb 
def 埃氏篩()
    =Ctnr.new
    .times do |_rand1|
        .push(true)
    end 
    _ans1=/2;
    甲半=_ans1;   =2
    while true do
        if ==甲半
            break
        end 
        =2
        while true do
            if ==甲半
                break
            end 
            _ans2=*;
            =_ans2;          if <=
                [-1]=false
            else
                break
            end 
            _ans3=1+;            =_ans3
        end 
        _ans4=1+;        =_ans4
    end 
    諸素=Ctnr.new
    =2
    while true do
        if ==.length
            break
        end 
_ans5=[-1];
        素耶=_ans5;       if 素耶
            諸素.push()
        end 
        _ans6=1+;        =_ans6
    end 
    return 諸素
end

_ans7=埃氏篩(100);p([_ans7].join)
$ ruby sieve.rb
"[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]"

rubyで100までの素数が求められました:ok_hand:

おわりに

美しいレンダリングとruby出力がしたくて、「文言」言語の存在を知った12月19日その日に「文言」アドベントカレンダーを作成しました。

はじめは環境も作れず参加者も増えず、無謀なのかと思いましたが、開発が進み簡単に実行できるようになりました。みなさんも是非美しいレンダリング、漢字でプログラム出来る新感覚、体験してみて下さい

入力がちょっと大変なのですが、リファレンススニペットも活用し楽しんで下さい

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

Ruby 2.4.4 から 2.7.0 までの主要な変更点をざっくりと知る

はじめに

  • rubyのバージョン 2.4.4から 2.7.0 までの、 比較的ビギナー層にも影響のある変更のまとめ です。細かな変更点や、変更内容の詳細などは割愛しています。
  • 2.4.4 からスタートなのは、私が使っているrubyが2.4.3 で長らく止まっているため、それ以降の変更を一気にキャッチアップしたいがためです。
  • 各マイナーバージョンは 2020/01/02 時点の最新版まで

2.4.4

主に脆弱性対応のみ

2.4.5

主に脆弱性対応のみ

2.4.6

2.4系の通常メンテナンスフェースが終了

2.4.7

主に脆弱性対応のみ

2.4.8

主に脆弱性対応のみ

2.4.9

2.4.8の再パッケージで変更なし

2.5.0

2.5系最初の安定版

rescue/else/ensuredo/end ブロック内にも直接書けるように

def f
  [1].each do
    2 / 0
  rescue StandardError
    p '例外だよ!!'
  end
end
f
これまで
$ ruby 2_5_0.rb
2_5_0.rb:6: syntax error, unexpected keyword_rescue, expecting keyword_end
  rescue StandardError
        ^
2_5_0.rb:9: syntax error, unexpected keyword_end, expecting end-of-input
これから
$ ruby 2_5_0.rb
"例外だよ!!"

Kernel#yield_self の追加

自身を受け渡すブロックを実行し、ブロック全体の評価値を戻り値にします。

p 1.yield_self { |n| n + 1 }
これから
$ ruby 2_5_0.rb
2

※ このメソッドには2.6.0でthenというエイリアスが追加されます

Hash#slice の追加

Railsではお馴染みなので新機能感ないですが、Ruby標準サポートされました。

p ({ a: :Hoge, b: :Fuga, c: :Foo, d: :Bar }.slice(:b, :c))
これまで
$ ruby 2_5_0.rb
2_5_0.rb:3:in `<main>': undefined method `slice' for {:a=>:Hoge, :b=>:Fuga, :c=>:Foo, :d=>:Bar}:Hash (NoMethodError)
これから
$ ruby 2_5_0.rb
{:b=>:Fuga, :c=>:Foo}

Hash#transform_keys の追加

Hashのキーをブロックに渡して任意の変換を行う。

hash = { hoge: 1, fuga: 2, foo: 3, bar: 4 }
p hash.transform_keys { |key| key.upcase }
# または p hash.transform_keys(&:upcase)
これから
$ ruby 2_5_0.rb
{:HOGE=>1, :FUGA=>2, :FOO=>3, :BAR=>4}

transform_keys! もあるよ!

トップレベルに定義した定数の参照ルールの変更

以下のコードのように、任意の名前空間(Hoge)を経由してトップレベルの定数(Fuga)を参照した場合、これまでは警告付きではあるものの参照できていたのが参照不可になります。

class Hoge end
class Fuga end

p Hoge::Fuga
これまで
$ ruby 2_5_0.rb
2_5_0.rb:6: warning: toplevel constant Fuga referenced by Hoge::Fuga
Fuga
これから
$ ruby 2_5_0.rb
Traceback (most recent call last):
2_5_0.rb:6:in `<main>': uninitialized constant Hoge::Fuga (NameError)
Did you mean?  Fuga

みんな大好き pp が自動ロードされる

うれピーピー

obj = {
  hoge: [1, 2, 3, 4, 5],
  fuga: {
    a: '1',
    b: '2'
  }
}

pp obj
これまで
$ ruby 2_5_0.rb
2_5_0.rb:11:in `<main>': undefined method `pp' for main:Object (NoMethodError)
これから
$ ruby 2_5_0.rb
{:hoge=>[1, 2, 3, 4, 5], :fuga=>{:a=>"1", :b=>"2"}}

バックトレース、エラーメッセージの表示順の変更

def f2
  hoge
end

def f1
  f2
end

f1
これまで(下から上に読んでいく)
$ ruby 2_5_0.rb
2_5_0.rb:12:in `f3': undefined local variable or method `hoge' for main:Object (NameError)
    from 2_5_0.rb:8:in `f2'
    from 2_5_0.rb:4:in `f1'
    from 2_5_0.rb:15:in `<main>'
これから(上から下に読んでいく)
$ ruby 2_5_0.rb
Traceback (most recent call last):
    3: from 2_5_0.rb:15:in `<main>'
    2: from 2_5_0.rb:4:in `f1'
    1: from 2_5_0.rb:8:in `f2'
2_5_0.rb:12:in `f3': undefined local variable or method `hoge' for main:Object (NameError)

トレースバックがむっちゃ長いとき、直近の原因が末尾に表示されるのでわかりやすい(?)

String#delete_prefix String#delete_suffix の追加

先頭あるいは末尾の任意文字列を削除できる

str = 'hogefuga'
p str.delete_prefix('hoge')
p str.delete_suffix('fuga')
これから
$ ruby 2_5_0.rb
"fuga"
"hoge"

参考

その他の変更内容や詳細については以下ご参照ください。

2.5.1

主に脆弱性対応のみ

2.5.2

主に脆弱性対応のみ

2.5.3

2.5.2の再パッケージで変更なし

2.5.4

主に脆弱性対応のみ

2.5.5

Pumaなどでデッドロックが発生する問題の修正

2.5.6

多数のバグ修正と脆弱性対応

2.5.7

主に脆弱性対応のみ

2.6.0

2.6系最初の安定版

Kernel#yield_self の別名 then の追加

2.5.0で追加されたメソッドを呼びやすい感じに。こっちのほうが値をどんどん加工してる感じがして使いやすい。

puts 1.then { |n| n + 1 }
      .then { |n| n * 2 }
      .then(&:to_s)
これから
$ ruby 2_6_0.rb
4

終端のないRangeを扱えるように

p '123456789'[2..]
これまで
$ ruby 2_6_0.rb
2_6_0.rb:3: syntax error, unexpected ']'
p '123456789'[2..]
これから
$ ruby 2_6_0.rb
"3456789"

無限リストとしても扱えるように

(2..).each do |i|
  break if i > 10
  p i
end
これから
$ ruby 2_6_0.rb
2
3
4
5
6
7
8
9
10

合成関数を作る Proc#<< Proc#>> の追加

f = proc { |x| x**2 }
g = proc { |x| x * 10 }

fg = f << g # ≒ f(g(2))
gf = f >> g # ≒ g(f(2))

p fg.call(2)
p gf.call(2)
これまで
$ ruby 2_6_0.rb
Traceback (most recent call last):
2_6_0.rb:6:in `<main>': undefined method `<<' for #<Proc:0x0000557816119660@2_6_0.rb:3> (NoMethodError)
これから
$ ruby 2_6_0.rb
400
40

binding.source_location の追加

現在行のファイル名と行番号を教えてくれる

# frozen_string_literal: true

hoge = 1
p binding.source_location
fuga = 2
これから
$ ruby 2_6_0.rb
["2_6_0.rb", 4]

Array#to_h でブロックを渡せるように

ブロックではArray内の値を受け取れるので、 [key, val]の形式になるArrayを戻すことで、それを元に新たなHashを生成します。

hash = %w[hoge fuga foo bar].to_h do |val|
  [val, val.upcase]
end
pp hash
これから
$ ruby 2_6_0.rb
{"hoge"=>"HOGE", "fuga"=>"FUGA", "foo"=>"FOO", "bar"=>"BAR"}

Enumerable#chain で繰り返し可能オブジェクトを連鎖できるように

Arrayなど、Enumerable なオブジェクトを連鎖させた Enumerator::Chainオブジェクトを生成できます。

chained = [1, 2, 3].chain([4, 5, 6], [7, 8, 9])
p chained.class
p chained.to_a

これから
$ ruby 2_6_0.rb
Enumerator::Chain
[1, 2, 3, 4, 5, 6, 7, 8, 9]

EnumerableであればRangeとかでももちろん可能

chained = (1..3).chain((90..))
chained.each do |i|
  break if i > 100
  p i
end
これから
$ ruby 2_6_0.rb
1
2
3
90
91
92
93
94
95
96
97
98
99
100

Enumerator#+Enumerator 同士を連結

前項と同じようなものだけど、前項は Enumerable で、本項は Enumerator なので注意。

chained = [1, 2, 3].each + [4, 5, 6].each
p chained.reduce(&:+)
これから
$ ruby 2_6_0.rb
21

和集合を取る Array#union の追加

p [1, 2, 3, 4, 5].union [4, 5, 6, 7, 8]
これから
$ ruby 2_6_0.rb
[1, 2, 3, 4, 5, 6, 7, 8]

実は | でも同じことが以前からできてたけど、 || の間違いじゃないんですか〜??」 というコードレビューでもコメントを何度も頂いた実績があるのでこっちのほうが助かる。

差集合を取る Array#difference の追加

基本は union と一緒

p [1, 2, 3, 4, 5].difference [4, 5, 6, 7, 8]
これから
$ ruby 2_6_0.rb
[1, 2, 3]

Hash#merge で複数のハッシュを渡せるように

機能としては今まで通りだけど、今までは引数を複数指定できなくて、mergeメソッドをチェインしていたのが、1回で済むようになった。

hash = { hoge: 'HOGE' }.merge({ fuga: 'FUGA' }, foo: 'FOO')
p hash
いままで
$ ruby 2_6_0.rb
Traceback (most recent call last):
    1: from 2_6_0.rb:3:in `<main>'
2_6_0.rb:3:in `merge': wrong number of arguments (given 2, expected 1) (ArgumentError)
これから
$ ruby 2_6_0.rb
{:hoge=>"HOGE", :fuga=>"FUGA", :foo=>"FOO"}

もちろん merge! も同様

参考

その他の変更内容や詳細については以下ご参照ください。

2.6.1

主に不具合修正のみ

2.6.2

RubyGemsの脆弱性対応と不具合修正

2.6.3

令和のサポートと不具合修正

2.6.4

主に脆弱性対応のみ

2.6.5

主に脆弱性対応のみ

2.7.0

2.7系最初の安定版

パターンマッチの導入(実験版)

渡されたオブジェクトの構造がパターンと一致するかどうかを調べ、一致した場合にその値を評価する仕組み

case [0, 1, 2, { hoge: 'HOGE', fuga: 'FUGA' }]
in [0, 1, n, { hoge: 'HOGE', fuga: fuga }]
  puts "n = #{n}"
  puts "fuga = #{fuga}"
else
  puts 'no match'
end
これから
$ ruby 2_7_0.rb
2_7_0.rb:3: warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!
n = 2
fuga = FUGA

パターンにマッチした部分の変数にその値が代入されていることがわかります。

また、実行結果に警告が出ている通り、パターンマッチは2.7では実験段階で、将来的に挙動が変わる可能性もあるため、プロダクトコードでの私用は控えたほうが良いように思えます。

パターンマッチの機能は非常に強力で柔軟なので、ここでは割愛しますが詳細が気になる方は参考記事を見てみてください。

irbの強化

標準REPLであるirbが強化され、複数行コードを記述する時にインデントが自動で挿入されたり、シンタックスハイライトがされたりと改善されています。pryを使ってたら関係ないですが。

いままで
スクリーンショット 2020-01-02 23.11.24.png

これから
スクリーンショット 2020-01-02 23.11.07.png

キーワード引数を通常の引数から分離

こちらは非推奨で警告が出るようになりますが、ruby3では廃止される予定なので、警告を見つけ次第修正していくべきです。

ハッシュオブジェクトを用いてキーワード引数を指定することが非推奨になります。

def f(x: 10)
  x * 2
end

hash = { x: 20 }
p f(hash)
これから
$ ruby 2_7_0.rb
2_7_0.rb:8: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
2_7_0.rb:3: warning: The called method `f' is defined here
40
def f(**params)
  pp params
end

hash = { x: 20 }
f(hash)
これから
$ ruby 2_7_0.rb
2_7_0.rb:8: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
2_7_0.rb:3: warning: The called method `f' is defined here
{:x=>20}

** をつけて、明示的にキーワード引数とすることで、これを回避できます。このコードはRuby3でも動くそうです。

def f(**params)
  pp params
end

hash = { x: 20 }
f(**hash)
これから
$ ruby 2_7_0.rb
{:x=>20}

他、キーワード引数と通常引数を組み合わせた場合に警告が出るバターンが増えているのでご注意を。

(リリースノート引用)
def foo(h, **kw); end; foo(key: 42)      # warned
def foo(h, key: 42); end; foo(key: 42)   # warned
def foo(h, **kw); end; foo({key: 42})    # OK
def foo(h, key: 42); end; foo({key: 42}) # OK

def foo(h={}, key: 42); end; foo("key" => 43, key: 42)   # warned
def foo(h={}, key: 42); end; foo({"key" => 43, key: 42}) # warned
def foo(h={}, key: 42); end; foo({"key" => 43}, key: 42) # OK

def foo(opt={});  end; foo( key: 42 )   # OK

ブロックの仮引数を番号指定で参照できるように

%w[hoge fuga foo bar].each_with_index do
  puts "str = #{_1}"
  puts "index = #{_2}"
end
これから
$ ruby 2_7_0.rb
str = hoge
index = 0
str = fuga
index = 1
str = foo
index = 2
str = bar
index = 3

ブロック引数を定義せずとも、何番目に渡される引数かだけで参照できるようになりました。

全ての引数を別のメソッドに渡す構文の追加

def f1(...)
  f2(...)
end

def f2(x, y, z, hoge:)
  puts x, y, z
  puts hoge
end

f1(1, 2, 3, hoge: 'HOGE')
これから
$ ruby 2_7_0.rb
1
2
3
HOGE

なんだろ、デリゲーションしたりかな。

積集合を取る Array#intersection の追加

p [1, 2, 3].intersection([2, 3, 4], [3, 4, 5])
これから
$ ruby 2_7_0.rb
[3]
  • これまでも & を使って同じことはできました
  • && の間違いじゃないんですか〜??」 という誤解のレビューを受けずに済みます
  • 和集合(union)と差集合(difference)は2.6.0のほうで追加されてます。

selectとfilterを同時に行うEnumerable#filter_mapの追加

使い方は概ねmapと同じですが、ブロックの戻り値が真の値だけが取り出されます。

p [-2, -1, 0, 1, 2].filter_map { |n| n * 2 if n.positive? }
これから
$ ruby 2_7_0.rb
[2, 4]

もうcompactとかチェインする必要は無い。

要素ごとの個数をカウントする Enumerable#tally の追加

p %w[HOGE FUGA HOGE HOGE FUGA HOGE FOO FUGA FUGA FUAG].tally
これから
$ ruby 2_7_0.rb
{"HOGE"=>4, "FUGA"=>4, "FOO"=>1, "FUAG"=>1}

実にかゆいところに手が届くメソッド。

任意のシーケンスを生成する Enumerator.produce の追加

数列の規則のようにブロックを定義することで、無限リストになるシーケンスを簡易的に生成できるように。

enum = Enumerator.produce(2) { |n| n * 2 }
p enum.take(10)
これから
$ ruby 2_7_0.rb
[2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]

工夫次第でこんなことも

require 'date'
enum = Enumerator.produce(Date.today, &:next_day)
pp enum.take(10)
これから
$ ruby 2_7_0.rb
[#<Date: 2020-01-02 ((2458851j,0s,0n),+0s,2299161j)>,
 #<Date: 2020-01-03 ((2458852j,0s,0n),+0s,2299161j)>,
 #<Date: 2020-01-04 ((2458853j,0s,0n),+0s,2299161j)>,
 #<Date: 2020-01-05 ((2458854j,0s,0n),+0s,2299161j)>,
 #<Date: 2020-01-06 ((2458855j,0s,0n),+0s,2299161j)>,
 #<Date: 2020-01-07 ((2458856j,0s,0n),+0s,2299161j)>,
 #<Date: 2020-01-08 ((2458857j,0s,0n),+0s,2299161j)>,
 #<Date: 2020-01-09 ((2458858j,0s,0n),+0s,2299161j)>,
 #<Date: 2020-01-10 ((2458859j,0s,0n),+0s,2299161j)>,
 #<Date: 2020-01-11 ((2458860j,0s,0n),+0s,2299161j)>]

Method#inspect がより詳細に

メソッドオブジェクトの情報を取得する Method#inspectメソッドにて、引数情報や定義元が出るようになってデバッグしやすい感じに

def f(x, y, z, hoge:)
  pp [x, y, z, hoge]
end

p method(:f).inspect
これまで
$ ruby 2_7_0.rb
"#<Method: main.f>"
これから
$ ruby 2_7_0.rb
"#<Method: main.f(x, y, z, hoge:) 2_7_0.rb:3>"

参考

その他の変更内容や詳細については以下ご参照ください。

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

FizzBuzzの問題で3の時はFizzと表示し、5の時Buzzと表示、15の時FizzBuzzと表示する

RubyのFizzBuzz練習問題を解く(今回はeach doを使用)

・1から30まで数える
・3の時はFizzと表示する
・5の時はBuzzと表示する
・15の時はFizzBuzzと表示する

まず、擬似コードを考える前に、情報を整理
どのようなコードが必要そうかを並べてみる。

①1から30まで数える
②もし3だったらFizzと表示する
③もし5だったらBuzzと表示する
④もし15だったらFizzBuzzと表示する

以上。

では、擬似コードっぽいやつ書いていく。

1から30まで数える(今回は、each do を使って)

if ○○なら
 puts ○○と表示する

elsif
   ○○なら
 puts ○○と表示する

elsif
   ○○なら 
 puts ○○と表示する
else
  それ以外なら
 puts ○○と表示する

ここにコードをあてていくと、考えがまとまりやすくなり、コードがすっきりしそう。
もし、仮説と違ったら擬似コードの考え方を実際の答えと比べてみる。

(1..30).each do|i|

    if i%15==0
        puts "FizzBuzz"
    elsif
        i%3==0
        puts "Fizz"
    elsif
        i%5==0
        puts "Buzz"
    else
        puts i
    end
end
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz

このように表示されていればOKです。

応用編:数字の横にFizzとBuzzをそれぞれつけてみる。

考え方はほとんど一緒
表示の仕方だけ変えればいいかな?っと仮説を立ててみる。
合ってたら自分を褒める。

(1..30).each do|i|

    if i%15==0
        puts "#{i} FizzBuzz"
    elsif
        i%3==0
        puts "#{i} Fizz"
    elsif
        i%5==0
        puts "#{i} Buzz"
    else
        puts i
    end
end

見やすくするため、一応数字と文字の間にスペースいれてます。

うまくいくと、以下のようになります。

1
2
3 Fizz
4
5 Buzz
6 Fizz
7
8
9 Fizz
10 Buzz
11
12 Fizz
13
14
15 FizzBuzz
16
17
18 Fizz
19
20 Buzz
21 Fizz
22
23
24 Fizz
25 Buzz
26
27 Fizz
28
29
30 FizzBuzz

以上となります。
ご参考いただけますと幸いです。

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

技術ブログはじめました

みなさん、はじめまして!
音楽とアニメとハイボールが大好きな Web エンジニアの yasu と申します!

このたび、超個人的嗜好で厳選したお気に入りの音楽を Apple Music のプレイリスト形式で紹介していく音楽ブログ「Fav Music」の構築・運用過程で得た技術的知見を qiita にアウトプットさせていただくことにしました。

僕がここでアウトプットした技術情報がいつか誰かの助けになったら嬉しいです :)

今回はご挨拶がわりに僕が Fav Music を構築・運用するために使用している技術や製品をご紹介させていただきます。

なお、Fav Music の全てのソースコードは GitHub でも公開してますので興味のある方は合わせてご参照ください。
<(*_ _)>

Fav Music を構築するために使用している技術や製品

使用した技術や製品をいろいろ列挙しましたけど、重要なのは Jekyll だけですね笑

今流行りの静的サイトジェネレーターってやつですw

最初は WordPress でサイトを構築したんですけど、静的サイトジェネレーターをいろいろ試してみて最終的に Jekyll にしました。

Jekyll の他にも

を試してみましたが、Jekyll を採用した決め手は、タグの一覧ページのページング機能が割と簡単に実現できたことです(各静的サイトジェネレーターのデフォルトの機能では意外と実現するのが難しい部分です)。

静的サイトジェネレーターで生成した静的サイトは WordPress で構築した動的サイトと比較してページングと検索の機能が弱い(というか自分で実装しないといけない)ので、これから静的サイトジェネレーターを使おうと思っている方はまず最初にその辺の使い勝手を徹底的に調査しておいた方がいいかもしれません。

ちなみに、Fav Music ではまだ検索機能は実装してません?

Adobe XD はキャッチアイ画像を作成するのに使ってるだけです。僕はデザインツールには詳しくないのでアレですが、Adobe XD はサクサク動いて使ってて気持ちいいですv

Visual Studio Code はコード(HTML, CSS, JavaScript, YAML, Ruby, Liquid)と記事(Markdown)を編集するのに使ってるのと、Git とか Shell コマンドも内臓ターミナルから叩いてるので、ぶっちゃけほとんどこの中にいますw

Google Chrome は、もちろん表示の確認とデバッグ(スタイルの調整)に使ってます。

Fav Music を運用するために使用している技術や製品

最初に WordPress でサイトを構築したときは EC2RDSALB を使ってガチで半年ほど運用してましたが、そもそも静的なコンテンツしかないんだから S3 で十分じゃね?ということに気付き、先日 S3 に切り替えました (笑)

これで万が一 Yahoo 砲が来ても耐えれますw

WordPress から S3 の静的 Web サイトホスティングに切り替える時にちょっと困ったことは、S3 ではページ URL の拡張子(.html)を省略できないっていうことでした(ディレクトリ URL の index.html は省略できます)。

対策はいくつかあるみたいですが、僕は単純に内部リンクは拡張子(.html)付きで書いて、WordPress の時に使われていた拡張子なしの URL は新しい URL に Rewrite (S3 側に Redirection rules として登録) するようにしました。

このやり方が一番良いやり方だったとは思えないので、もう少し調べて別の記事にまとめたいと思ってます。

S3CloudFront でブログを運用した場合の実際のコスパについてもみなさん気になるところだと思うので近々公開したいと思ってますが、幸か不幸か全くアクセスがないサイトなので (恥)、参考になる数字が出せる日が来るかどうかは怪しいものです (笑)

まとめ

以上、僕が Fav Music を構築・運用するために使用している技術や製品をご紹介させていただきましたがいかがだったでしょうか。

今回は技術情報としてはほとんど価値のない記事だったと思うので、次回はもう少し役に立つ情報をアウトプットしたいと思います?

ではまた ( ⸝⸝•ᴗ•⸝⸝ )੭⁾⁾

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

【第7章】Railsチュートリアル 5.1(第4版) ユーザー登録

大まかな流れの自己整理が目的のため、不足・誤り等あれば追記&訂正していきますのでご指摘頂けますと幸いです:bow_tone1:
なお、筆者はYassLabさんの動画版で学んでいるため、本記事は「チュートリアル sample_app」+「他補足」個人的に「電子ページ以上に分かりやすい!」と感じた解説部分+参考記事を整理してみようと試みた劣化の内容寄りになってます。

7.1 ユーザーを表示する

params

Railsで送られてきた値を受け取るためのメソッド(その後Railsが保存を行う)。中身はハッシュ。
主に、以下の2つ
・投稿フォームなどPOSTで送信されたデータ
・検索フォームなどGETで送信されURLにクエリとして入るデータ

ex.params[:id] → ユーザなどのid

<参考>
【Rails】paramsについて徹底解説!
【Rails入門】params使い方まとめ

debug(メソッド)

paramsの中身(表示画面)を出力してくれる。putsのような扱い。
  

本番環境に展開したアプリケーションではデバッグ情報を表示したくないので、後置if文でデベロッパー確認を追加する。
(後置if文は1行で済むときによく使用される)

app/views/layouts/application.html.erb 内に下記追加

<%= debug(params) if Rails.env.development? %>

補足

Railsにはテスト環境 (test)、開発環境 (development)、そして本番環境 (production) の3つの環境がデフォルトで装備されている。Rails consoleのデフォルトの環境はdevelopment。
 

app/assets/stylesheets/custom.scssに
デバッグ表示を整形するための追加と、Sassのミックスインを追加する(ここでは省略)
  
/users/1 のURLを有効にするため、routes(るーつ、らうつ)ファイルsignup下に追加

resources :users

 
確認(今回注目するのはnewアクションとshowアクション)

$ rails routes
           Prefix Verb   URI Pattern                  Controller#Action
             root GET    /                            static_pages#home
static_pages_home GET    /static_pages/home(.:format) static_pages#home
             help GET    /help(.:format)              static_pages#help
            about GET    /about(.:format)             static_pages#about
          contact GET    /contact(.:format)           static_pages#contact
           sighup GET    /sighup(.:format)            users#new
            users GET    /users(.:format)             users#index
                  POST   /users(.:format)             users#create
         new_user GET    /users/new(.:format)         users#new
        edit_user GET    /users/:id/edit(.:format)    users#edit
             user GET    /users/:id(.:format)         users#show
                  PATCH  /users/:id(.:format)         users#update
                  PUT    /users/:id(.:format)         users#update
                  DELETE /users/:id(.:format)         users#destroy

putリクエスト  → 全部(旧残り)
patchリクエスト → 欲しい情報の一部(現在主流)

 
Usersコントローラにshowアクションを追加する(showアクションはGET /users/:id と連動)。挿入場所(順番)は可読性を考慮し出来ればRESTfulなルート順(表)が望ましい。
@userはインスタンス変数とされ、showアクション外(例えばviewであるshowテンプレート: => app/views/users/show.html.erb)からも呼び出すことが出来るため、「paramsで持ってきたユーザid(リクエスト値)を@userに格納する」ような流れ。

def show
  @user = User.find(params[:id])
end

 

HTTPリクエスト URL アクション 名前付きルート 用途
GET /users index users_path すべてのユーザーを一覧するページ
GET /users/1 show user_path(user) 特定のユーザーを表示するページ
GET /users/new new new_user_path ユーザーを新規作成するページ (ユーザー登録)
POST /users create users_path ユーザーを作成するアクション
GET /users/1/edit edit edit_user_path(user) id=1のユーザーを編集するページ
PATCH /users/1 update user_path(user) ユーザーを更新するアクション
DELETE /users/1 destroy user_path(user) ユーザーを削除するアクション

 
サンプルアプリケーションを既にHeroku上にデプロイしている場合は、heroku run rails consoleというコマンドを打つことで、本番環境を確認することができます。

  $ heroku run rails console
  >> Rails.env
  => "production"
  >> Rails.env.production?
  => true

デバッガー(debugger)

追記して稼働させるとゆっくり読み込み中になる

def show
    @user = User.find(params[:id])
    # => app/views/users/show.html.erb
    debugger
  end

rails サーバを立ち上げると、止まってる場所がわかる

def show
    4:     @user = User.find(params[:id])
    5:     # => app/views/users/show.html.erb
    6:     debugger
=>  7:   end
    8:   
    9:   def new
   10:   end
   11: end
(byebug)

コマンドはlist,nextなど。exit、ctrl+cで終了。
使い終わったらコメントアウト。
  
 
show.html.erbにgravatar_forヘルパーメソッドを使い、WordPress系アバター画像のGravatarの画像を利用できるようにする。

<% provide(:title, @user.name) %>
<h1>
  <%= gravatar_for @user %>
  <%= @user.name %>
</h1>

ヘルパーメソッドとしてusers_helper.rbに追加する。
最後の行のimage_tag(url、alt&class属性付与)がgravatar_forに返される。
なお、gravatar_forメソッドでのemailは(has_secure_passwordのように)Digest::MD5によってハッシュ化されている。

module UsersHelper
  # 引数で与えられたユーザーのGravatar画像を返す
  def gravatar_for(user)
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end
end

image.png

この時点でデフォルト画像のまま表示されるが、show.html.erbにasideタグ(サイドバーなど補足として付け加えたいときに便利なもの)を付け加える。

<% provide(:title, @user.name) %>
<div class="row">
  <aside class="col-md-4">
    <section class="user_info">
      <h1>
        <%= gravatar_for @user %>
        <%= @user.name %>
      </h1>
    </section>
  </aside>
</div>

image.png

saasに追記してフォーマット等調整。

/* sidebar */

aside {
  section.user_info {
    margin-top: 20px;
  }
  section {
    padding: 10px 0;
    margin-top: 20px;
    &:first-child {
      border: 0;
      padding-top: 0;
    }
    span {
      display: block;
      margin-bottom: 3px;
      line-height: 1;
    }
    h1 {
      font-size: 1.4em;
      text-align: left;
      letter-spacing: -1px;
      margin-bottom: 3px;
      margin-top: 0px;
    }
  }
}

.gravatar {
  float: left;
  margin-right: 10px;
}

.gravatar_edit {
  margin-top: 15px;
}

スクリーンショット 2020-01-03 13.53.45.png
  

7.2 ユーザー登録フォーム

コントローラのnewアクションに空のオブジェクトを作成して@user変数を追加する

  def new
    @user = User.new
  end

  
view画面のnewに追記。
form_forはブロック付き引数で、ブロック内部では例として「email」というキーの中にユーザのバリューが入るイメージ。
「f.label」はラベルのなので削っても問題ないが、あるとユーザ視点で何を入力していいか分かりやすい。(変更イメージの参考:[rails]ActiveModelを使ったフォームのラベル名を変更する
「f.submit」は"Create my account"というボタンからこれらのデータをnewアクション→createアクションへ送る(次のアクションへ)。

<h1>Users#new</h1>
<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= f.label :name %>
      <%= f.text_field :name %>

      <%= f.label :email %>
      <%= f.email_field :email %>

      <%= f.label :password %>
      <%= f.password_field :password %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

formのscss追記

/* forms */

input, textarea, select, .uneditable-input {
  border: 1px solid #bbb;
  width: 100%;
  margin-bottom: 15px;
  @include box_sizing;
}

input {
  height: auto !important;
}

スクリーンショット 2020-01-03 16.29.08.png

そのまま作るとエラーが起きるが、「newアクション移行→createアクションがないよ」という表示なので正常。
image.png

createアクション追加&renderでビュー画面newに戻り、登録情報確認(試験的にfoobar入れてます)

  def create
    @user = User.new
    render 'new'
  end

image.png

7.3 ユーザー登録失敗

ユーザー登録の失敗に対応できるcreateアクション

オプション引数について

ハッシュのハッシュなので、userの中に[:name],[:email],[:password]があるので:userで省略。

  def create
    # オプション引数はキーワードにシンボルが入ってバリューに値が入ってる集合体
    #  → User.new(name: ..., email:, ...)
    # @user.name = params[:user][:name]
    # @user.user = params[:user][:email]
    # @user.password = params[:user][:password]
    # 1行で完結
    @user = User.new(params[:user]) 
    if @user.save #=> Validation
      # Sucess
    else
      #Failure
      render 'new'
    end
  end

image.png

エラー登場。createの直後のparamsはユーザが送る情報なのでいろいろな情報を送る(いじる)ことができるので、今後adminなどを入れていく過程で悪意あるユーザからDB書き換えたれるなどのマスアサインメント脆弱性を回避するRails4.0移行実装の機能。paramsハッシュでは:user属性を必須とし、名前、メールアドレス、パスワード、パスワードの確認の属性をそれぞれ許可し、それ以外を許可しないようにしたいので、requireなどを追加したuser_paramsメソッドをコントローラに追加してメソッドで対応する。

users_controller.rb
   def create
    @user = User.new(user_params)
    if @user.save #=> Validation
      # Sucess
    else
      #Failure
      render 'new'
    end
  end

  def user_params
    params.require(:user).permit(:name, :email, :password, :password_confirmation)
  end

Rails consoleからメソッド「.errors.full_messages」でエラー詳細表示

空のユーザ作成後save失敗から.full_messagesメソッドでエラーの詳細表示ができる。
(今回はいろいろ空だったことが原因)

2.6.3 :002 > @user = User.new
 => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil> 
2.6.3 :003 > @user.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" IS NULL LIMIT ?  [["LIMIT", 1]]
 => false 
2.6.3 :004 > @user.save
   (0.1ms)  begin transaction
  User Exists (0.4ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" IS NULL LIMIT ?  [["LIMIT", 1]]
   (0.1ms)  rollback transaction
 => false 
2.6.3 :005 > @user.errors.full_messages
 => ["Name can't be blank", "Email can't be blank", "Email is invalid", "Password can't be blank", "Password can't be blank", "Password is too short (minimum is 6 characters)"] 

ユーザ登録時にエラーメッセージが出るよう追記する。sharedディレクトリもviewディレクトリ下に作る。

<h1>Users#new</h1>
<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

 
パーシャル(Viewでの似たようなコードを一つでまとめてるもの)を作るがアンダーバーなので注意

$ cd app/views/
$ mkdir shared
$ c9 open _error_messages.html.erb

 
「.errors.eny?」 → save,validなど何か実行されてエラーがなければfalse(何も表示しない)、「.count」あるので1個以上あればtrueを返す。pluralizeは"error"を個数に応じて単数・複数形で判断してくれる。

_error_messages.html.erb
<% if @user.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(@user.errors.count, "error") %>.
    </div>
    <ul>
    <% @user.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

image.png

「Password can't be blank」が2つ出る理由は、「has_secure_password」と「validates presence」で引っかかっているためのバグ。
応急処置として、has_secure_password内に下記を追記するなどがある。

 allow_nil: true

 
エラー表示のSCSS追加

#error_explanation {
  color: red;
  ul {
    color: red;
    margin: 0 0 30px 0;
  }
}

.field_with_errors {
  @extend .has-error;
  .form-control {
    color: $state-danger-text;
  }
}

   
Chromeの検証でエリア選択すると
スクリーンショット 2020-01-03 20.28.58.png

<form class="new_user" id="new_user" action="/users" accept-charset="UTF-8" method="post">

とあり、
アクションが「/users」、メソッドが「post」から、「postリクエストを/usersに送りつける」ことが分かる。
ポストリクエストの流れは下記から確認でき、
「POST」リクエストが「/users」に送られると、「users」コントローラの「create」アクションが反応する。

$ rails routes
           Prefix Verb   URI Pattern                  Controller#Action
             root GET    /                            static_pages#home
static_pages_home GET    /static_pages/home(.:format) static_pages#home
             help GET    /help(.:format)              static_pages#help
            about GET    /about(.:format)             static_pages#about
          contact GET    /contact(.:format)           static_pages#contact
           signup GET    /signup(.:format)            users#new
            users GET    /users(.:format)             users#index
                  POST   /users(.:format)             users#create
         new_user GET    /users/new(.:format)         users#new
        edit_user GET    /users/:id/edit(.:format)    users#edit
             user GET    /users/:id(.:format)         users#show
                  PATCH  /users/:id(.:format)         users#update
                  PUT    /users/:id(.:format)         users#update
                  DELETE /users/:id(.:format)         users#destroy

「assert_no_difference」を使った、失敗時のテスト

インテグレーションテスト(結合テスト)から

$ rails generate integration_test users_signup
Running via Spring preloader in process 4226
      invoke  test_unit
      create    test/integration/users_signup_test.rb

 

自動生成されたものに追記。「signup_path」にgetリクエストを送りつける。
postリクエストをusers_pathに送り、その時にパラメータ:{ user: { name:,email:,password:,password_confirmation}}(オプション引数)を送りつける。
assert_no_differenceは呼び出す前後で値に違いがないことを主張するテストで、引数に(User.count)が入っていることから、「ユーザ数を覚えた後にデータを投稿してみて、ユーザ数が変わらないかどうかを検証するテスト」になる(公式より)。

users_signup_test.rb
require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest
   test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post users_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_template 'users/new'
  end
end

この時点でテスト(通過)

$ rails t

補足(エラー 「undefined local variable or method `signup_path'」)

筆者の場合、この時点で下記のようなエラーが出ました。

$ rails t
Running via Spring preloader in process 4176
Run options: --seed 61612

# Running:

.E..........

Finished in 0.618448s, 19.4034 runs/s, 30.7221 assertions/s.

  1) Error:
UsersSignupTest#test_invalid_signup_information:
NameError: undefined local variable or method `signup_path' for #<UsersSignupTest:0x00000000065b4838>
    test/integration/users_signup_test.rb:6:in `block in <class:UsersSignupTest>'

12 runs, 19 assertions, 0 failures, 1 errors, 0 skips

 
「自分でアカウント追加して遊んだから講義とユーザ数ずれちゃったせいかな?」と考えたりしてましたが(このテストでは前後数のみで関係ない)
ルート(routes.rb)、ビュー(home.html.erb)でsignup_pathがsighup_pathにタイプミスしてました:innocent:

 

7.4 ユーザー登録成功

createアクションにリダイレクトを追加する(省略過程あり)。

def create
    @user = User.new(user_params)
     # Sucess
      # redirect_to user_path(@user.id)
      # user_pathの引数デフォルトがidなので「.id」省略可、
      # redirect_to user_path(@user)
      # さらにredirect_toのデフォルト挙動としてユーザオブジェクトを渡すとuser_pathになるので
      redirect_to @user
      #GETリクエスト(が右にいく) => "/users/#{@user.id}" => showアクションが動く
    else
      #Failure
      render 'new'
    end
  end

flash

登録完了後に表示されるページにメッセージを表示する (この場合は新規ユーザーへのウェルカムメッセージ)。
createアクション(if文直下)と、ビュー(yield直前)に追加

users_controller.rb
flash[:success] = "Welcome to the Sample App!"
application.html.erb
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>

↑(ビュー側のerb埋め込みruby)の補足

ハッシュはキー+バリューの仕組みになっていて、
eachメソッドで呼び出すと、message_type(キー:success)とmessage(バリュー:"Welcome to the Sample App!")の関係になっている。
また、classの中にmessageを埋め込んでいる。

  
 
登録動作を確認してみると、5番目のユーザで作成できてる(成功!)。
flashは一度だけなのでリロードすればメッセージ(緑色)は消える。

スクリーンショット 2020-01-04 13.42.55.png

成功時のテスト

成功時、ユーザのカウントが1増えてればokという内容。
基本的にはshowテンプレートが表示されていれば?‍♂️

users_signup_test.rb
  test "valid signup information" do
    get signup_path
    assert_difference 'User.count', 1 do
      post users_path, params: { user: { name:  "Example User",
                                         email: "user@example.com",
                                         password:              "password",
                                         password_confirmation: "password" } }
    end
    follow_redirect!
    assert_template 'users/show'
  end

問題がなければ、herokuにデプロイして本番環境で確認。
スクリーンショット 2020-01-04 13.58.58.png

URLを確認すると「https://〇〇.herokuapp.com/users/1」となっており、
本番環境(heroku)では1人目のユーザであることが分かる。

7.5 プロのデプロイ

本番環境でのSSL

herokuのサブドメイン「https://〇〇.herokuapp.com(2番目)/users/1」を使っている内はサービス上SSL化(?Secure)されている。(Googleなどではhttpより検索順位が上位になったりするので重要であり、将来的に独自ドメインを使っていく際は自分で証明書の発行が必要になる。)

https(SSL)通信を本番環境で強制するやり方として、production(本番環境).rbに下記を追記(コメントアウト解除してtrueに)する。
これにより、もしhttpでアクセスしても問答無用でhttpsに切り替わる。

config/environments/production.rb
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  config.force_ssl = true

 

本番環境に適したWebサーバーを構築する

heroku推奨の設定ファイルをpumaに書き換える。

config/environments/production.rb
workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['RAILS_MAX_THREADS'] || 5)
threads threads_count, threads_count

preload_app!

rackup      DefaultRackup
port        ENV['PORT']     || 3000
environment ENV['RACK_ENV'] || 'development'

on_worker_boot do
  # Worker specific setup for Rails 4.1+
  # See: https://devcenter.heroku.com/articles/
  # deploying-rails-applications-with-the-puma-web-server#on-worker-boot
  ActiveRecord::Base.establish_connection
end

  
Pumaがheroku上で使うようにプロックファイル(./Procfile)(はデフォルトでないので)作成し、定義する。

/sample_app $ c9 open Procfile
web: bundle exec puma -C config/puma.rb

一応テスト走らせた後、
再度herokuへデプロイ(コミットメッセージが分かるように)して終了。

$ git add -A
$ git commit -m "Use SSL and the Puma webserver in production"    

感想ほか

講義が非常に分かりやすく全く挫折することなく終えました(講師:安川さん、ありがとうございました!!)。
教材模写(受け身)が中心だったので、忘れた部分含め2周目自力で進めて公式ドキュメント漁って読み込むなどもっと頭を悩ませる必要はあるかなと。
最後までお読み頂きありがとうございました!!

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

openssl1.0x から openssl1.1x @ruby

この記事は

opensslのバージョンアップにより、各種アプリケーションが起動できなくなるエラーが起きているようです。

ssl関係は結構googleを始めプラットフォーマーが敏感で、バージョンアップをサボりにくいです。他のアプリケーションのバージョンアップでopenssl1.1xが要求されるケースも多いです。

っという事情か、'18年後半から現在まで「phpが動かないよー」とか「rubyが動かないよー」という記事がたくさん出ているみたいです。ぶっちゃけ対処法はそれらにも載っているのですが、如何せんスマートな方法を書いている記事がなく。

・他により良い対処法はないのか?
・なぜスマートな方法が見つからないのか?

ということで調べてみました。(phpの名前を出しておいてアレですが、rubyだけです悪しからず。)

この記事の対象

○Macユーザー
△その他Unix系ユーザー
×windowsユーザー

検証環境

Mac OS Mojave 10.14.6 (アップデートをサボっています)
rbenv 1.1.2
ruby 2.6.3
Rails 5.2.3

rubyが動かない現象

事の発端は久々にrailsでバックエンドを作ろうとした時のこと。とりあえずrails -vを打ったところ、やたら長いエラーが発生。もしかしてrails --versionじゃないとダメだったか?と思い、ロングオプションを打つも同じエラー。

仕方がないのでエラーをちゃんと読むとopenssl関係だと分かりました。
運がいいことに、前日postgresqlのバージョンアップでopenssl1.1.0を入れていたので、"openssl1.0.0"の文字からバージョン関係のエラーだと切り分ける事ができました。

ググる

rubyを再インストールして見たら動いたというパターンと、rubyを再インストールすればいいよという記事を見て再インストールしているケースが多数。なるほど最悪rubyを再インストールすればいいと分かりました。
でも明らかに参照先のopensslが違うだけのエラーなので、もうすこし簡単な方法があるはずでは?

間違ったopensslを探しに行っている犯人を探してみる

ruby2.6.3のなかに設定ファイルはないか?

結論→ない
grep -r "(opensslのパス)" .rbenv/versions/2.6.3 してもそれらしい設定ファイルは見つからず。

その代わり、
.rbenv/versions/2.6.3/lib/ruby/2.6.0/x86_64-darwin18/digest/md5.bundle
.rbenv/versions/2.6.3/lib/ruby/2.6.0/x86_64-darwin18/digest/sha2.bundle matches
.rbenv/versions/2.6.3/lib/ruby/2.6.0/x86_64-darwin18/digest/rmd160.bundle matches
.rbenv/versions/2.6.3/lib/ruby/2.6.0/x86_64-darwin18/digest/sha1.bundle matches
.rbenv/versions/2.6.3/lib/ruby/2.6.0/x86_64-darwin18/openssl.bundle matches
.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/extensions/x86_64-darwin-18/2.6.0-static/puma-3.12.1/puma/puma_http11.bundle
.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/puma-3.12.1/ext/puma_http11/puma_http11.bundle
.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/puma-3.12.1/lib/puma/puma_http11.bundle

がヒット。pumaはgemだけど、後のはどう見てもruby本体のライブラリ、嫌な感じしかしないですね。

もしかして:環境変数

結論→ない

printenvしても一切見つからず。
大抵こういうパターンでも新たに特定の変数名で環境変数を作ってやると解決するパターンがある可能性は高いですが、とりあえず「なくても動いている」=「他の参照元が有効である」ことは確実です。

ということは

rubyをビルドする時にインストール済みのopensslを探しに行ってライブラリに直書きするパターン?
だから再インストールで直るのかー。

rubyが正常に動く状態にする

シンボリックリンクを張る

ln -s libssl.1.1.dylib libssl.1.0.0.dylib
ln -s libcrypto.1.1.dylib libcrypto.1.0.0.dylib

私の環境ではopenssl絡みで参照されるライブラリはlibsslとlibcryptoだけでした。(grepすると分かります。)
おそらく一般的にそうなのではないかと思います。
コマンドの数字の部分は左側が実在のファイルと同じ、右側が最初のエラーで出たopensslのバージョンです。

動いた

まあそうですよね。動くに決まっています。

どのような対応がベストか?

シンボリックリンクを張るだけでうまいこと動いてくれました。
おそらく以降でopensslのバージョンを参照する場合は1.1で記憶してくれると期待しています。なので急に動かなくなるのはあまり心配していません。

ですが、opensslのバージョンをもう一度上げた場合はどうなるのでしょうか?おそらくシンボリックリンクのリンク切れが起きるか、もう一度シンボリックリンクを張り直すことになる気がします。

結局、今後opensslのバージョンアップで同じようなことが起こると予想されますので、ruby自体を再インストールしても問題が起きないように、bundler + GemfileでGemを管理して、グローバルインストールはしないのがベストかもしれませんね。

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

opensslバージョンアップ @ruby

この記事は

opensslのバージョンアップにより、各種アプリケーションが起動できなくなるエラーが起きているようです。

ssl関係はgoogleはじめプラットフォーマーが敏感なせいかサボりにくい部分です…。

'18年後半から現在まで「phpが動かないよー」とか「rubyが動かないよー」という記事がたくさん出ていて、ぶっちゃけ対処法はそれらにも載っているのですが、如何せんスマートな方法を書いている記事がなく。

・他により良い対処法はないのか?
・なぜスマートな方法が見つからないのか?

ということで調べてみました。(phpの名前を出しておいてアレですが、rubyだけです悪しからず。)

この記事の対象

○Macユーザー
△その他Unix系ユーザー
×windowsユーザー

検証環境

Mac OS Mojave 10.14.6 (アップデートをサボっています)
rbenv 1.1.2
ruby 2.6.3
Rails 5.2.3

rubyが動かない現象

事の発端は久々にrailsでバックエンドを作ろうとした時のこと。とりあえずrails -vを打ったところ、やたら長いエラーが発生。もしかしてrails --versionじゃないとダメだったか?と思い、ロングオプションを打つも同じエラー。

仕方がないのでエラーをちゃんと読むとopenssl関係だと分かりました。
運がいいことに、前日postgresqlのバージョンアップでopenssl1.1.0を入れていたので、"openssl1.0.0"の文字からバージョン関係のエラーだと切り分ける事ができました。

ググる

rubyを再インストールして見たら動いたというパターンと、rubyを再インストールすればいいよという記事を見て再インストールしているケースが多数。なるほど最悪rubyを再インストールすればいいと分かりました。
でも明らかに参照先のopensslが違うだけのエラーなので、もうすこし簡単な方法があるはずでは?

間違ったopensslを探しに行っている犯人を探してみる

ruby2.6.3のなかに設定ファイルはないか?

結論→ない
grep -r "(opensslのパス)" .rbenv/versions/2.6.3 してもそれらしい設定ファイルは見つからず。

その代わり、
.rbenv/versions/2.6.3/lib/ruby/2.6.0/x86_64-darwin18/digest/md5.bundle
.rbenv/versions/2.6.3/lib/ruby/2.6.0/x86_64-darwin18/digest/sha2.bundle
.rbenv/versions/2.6.3/lib/ruby/2.6.0/x86_64-darwin18/digest/rmd160.bundle
.rbenv/versions/2.6.3/lib/ruby/2.6.0/x86_64-darwin18/digest/sha1.bundle
.rbenv/versions/2.6.3/lib/ruby/2.6.0/x86_64-darwin18/openssl.bundle
.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/extensions/x86_64-darwin-18/2.6.0-static/puma-3.12.1/puma/puma_http11.bundle
.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/puma-3.12.1/ext/puma_http11/puma_http11.bundle
.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/puma-3.12.1/lib/puma/puma_http11.bundle

がヒット。pumaはgemだけど、後のはどう見てもruby本体のライブラリ、嫌な感じしかしないですね。

もしかして:環境変数

結論→ない

printenvしても一切見つからず。
大抵こういうパターンでも新たに特定の変数名で環境変数を作ってやると解決するパターンがある可能性は高いですが、とりあえず「なくても動いている」=「他の参照元が有効である」ことは確実です。

ということは

rubyをビルドする時にインストール済みのopensslを探しに行ってライブラリに直書きするパターン?
だから再インストールで直るのかー。

rubyが正常に動く状態にする

シンボリックリンクを張る

ln -s libssl.1.1.dylib libssl.1.0.0.dylib
ln -s libcrypto.1.1.dylib libcrypto.1.0.0.dylib

私の環境ではopenssl絡みで参照されるライブラリはlibsslとlibcryptoだけでした。(grepすると分かります。)
おそらく一般的にそうなのではないかと思います。
コマンドの数字の部分は左側が実在のファイルと同じ、右側が最初のエラーで出たopensslのバージョンです。

動いた

まあそうですよね。動くに決まっています。

どのような対応がベストか?

シンボリックリンクを張るだけでうまいこと動いてくれました。
ですが、またopensslのバージョンを変えた場合は同じことの繰り返しです。

それを考えると、どうしても今のrubyを維持したい場合はこの方法でも良いのですが、そうでなければ再インストールでもいいような気がしました。

(追記)そんな甘い話ではなかった

openssl1.0x以下とopenssl1.1xでは起動時にライブラリをロードする方法が違うとのことで、1.0xからバージョンアップした際、openssl.bundleがエラーを吐くことがわかりました。
1.0x以下の環境下でビルドされたrubyはopenssl1.1xの実行に必要なOPENSSL_init_sslを起動できず、
逆にopenssl1.1xに存在しない_SSL_library_initを呼び出そうとするためエラーになる模様。

検証内容

grep -r SSL_library_init /(path to 2.6.3's)/openssl.bundle
-> match
grep -r OPENSSL
init_ssl /(path to 2.6.3's)/openssl.bundle
-> no match

openssl1.1.0が存在する状態で
rbenv install 2.6.0
rbenv local 2.6.0
元が2.6.3なので、rubyのバージョンは下がっています。ですのでrubyのバージョンが左右する訳ではないはず。
grep -r SSL_library_init /(path to 2.6.0's)/openssl.bundle
-> no match
grep -r OPENSSL
init_ssl /(path to 2.6.0's)/openssl.bundle
-> match

呼び出し関数名が変わっているのがわかります。

gem install rails
rails new test-app
cd test-app
rails s
-> OK

今度は正常に動いてますね…。

つまり

やっぱり再インストールした方が無難です。

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

【Rails】Rspec何言ってるか解読してみた件。コントローラー#edit編

今回は、写真とタイトルを投稿するアプリケーションのtweetsコントローラーのeditアクション(投稿したツイートの編集画面を呼ぶ)が問題なく動くかどうか、Rspecという言語を使ってテスト。

テストする内容は以下の2つ。

  1. tweets_controller.rb内でeditアクションを定義した際に生成したインスタンス変数に期待する値が入っているかな?

    tweets_controller.rb
    def edit
    @tweet = Tweet.find(params[:id])
    end
    
  2. 擬似的にeditアクションを起こすリクエストを飛ばして、ちゃんとeditアクションに対応したビューが返ってくるかな?

コントローラーのテストをする際は、2つの点に注意。
①tweets_controller.rbに、テストしたいアクションに対して、before_actionが定義されていないこと。
②gem rails-controller-testingをインストールしよう。

また、factory_botを利用して、テストにおいてeditアクションで編集するダミーのツイートデータを作成しよう。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    nickname              {"mamama"}
    email                 {"mamama@mail.com"}
    password              {"00000000"}
    password_confirmation {"00000000"}
  end
end
spec/factories/tweets.rb
FactoryBot.define do
  factory :tweet do
    text  {" I love animals"}
    image {"animals.png"}
    user  #←factory_botを利用して作った :userのこと。ツイートを投稿した際、そのツイートは誰が作ったのかも情報に含まれるため、ダミーのユーザー情報も作成しダミーのツイートを作成する際に利用している。
  end
end

では、見ていきましょう。

tweets_controller_spec.rb
require 'rails_helper'

describe TweetsController, type: :controller do
  describe 'GET #new' do
    #「【Rails】Rspec何言ってるか解読してみた件。コントローラー#new編」を参照。
  end

  describe 'GET #edit' do
    # 1. tweets_controller.rb内でeditアクションを定義した際に生成したインスタンス変数に期待する値が入っているかな?
    it "assigns the requested tweet to @tweet" do
      dummyTweet = create(:tweet)
      get :edit, params: { id: dummyTweet }
      expect(assigns(:tweet)).to eq dummyTweet
    end

    # 2. 擬似的にeditアクションを起こすリクエストを飛ばして、ちゃんとnewアクションに対応したビューが返ってくるかな?
    it "renders the :edit template" do
      dummyTweet = create(:tweet)
      get :edit, params: { id: dummyTweet }
      expect(responce).to render_template :edit
    end
  end
end

1. tweets_controller.rb内でeditアクションを定義した際に生成したインスタンス変数に期待する値が入っているかな?

require 'rails_helper'
RailsでRspecを利用する時の共通の設定が記載されているrails_helper.rbを読み込む。これがなきゃ始まらない。

describe TweetsController, type: :controller do end
tweetsコントローラーのテストをするよ。 type: :controllerこれは書かなきゃいけないみたいですね。

describe 'GET #edit' do end
GETメソッドのeditアクションに関するテストだよ。

it "assigns the requested tweet to @tweet" do end
do - endの間に書かれている、実際に動くテストコードの説明。呼ばれたtweet(the requested tweet)が@tweetにアサインされているかをテストしますと言っている。日本語でも良いらしい。ちなみに、it do endのセット1つで、1exampleと呼ぶ。

dummyTweet = create(:tweet)
factory_botで生成したtweetをcreateアクションを起こさせて、一旦登録。それをdummyTweetに入れる。

get :edit, params: { id: dummyTweet } 
擬似的なリクエストを起こすコード。get :editでアクション名editを、params: { id: dummyTweet } でdummyTweetがTweetsテーブルの何番目のカラムにあるのかを示すidを、そのリクエストに情報として渡す。

expect(assigns(:tweet)).to eq dummyTweet
インスタンス変数に意図したものが入っているかな?というテストコード。assignsは、インスタンス変数の中身をチェックしてくれるやつ。今回は、tweets_controller.rb内で定義した@tweetが対象なので、(:tweet)と書こう。@tweetの中身は、一旦登録させてeditアクションで持ってこさせたdummyTweetと一緒だと(.to eq)と期待する(expect)。

tweets_controller_spec.rb
  #省略
  describe 'GET #edit' do
    # 1. tweets_controller.rb内でeditアクションを定義した際に生成したインスタンス変数に期待する値が入っているかな?
    it "assigns the requested tweet to @tweet" do
      dummyTweet = create(:tweet)
      get :edit, params: { id: dummyTweet }
      expect(assigns(:tweet)).to eq dummyTweet
    end

    # 2. 擬似的にeditアクションを起こすリクエストを飛ばして、ちゃんとnewアクションに対応したビューが返ってくるかな?
    it "renders the :edit template" do
      dummyTweet = create(:tweet)
      get :edit, params: { id: dummyTweet }
      expect(responce).to render_template :edit
    end
  end
  #省略

2. 擬似的にeditアクションを起こすリクエストを飛ばして、ちゃんとnewアクションに対応したビューが返ってくるかな?

it "renders the :edit template" do end
do - endの間に書かれている、実際に動くテストコードの説明。editアクションに対応したビューファイル(template)が呼び出される(renders)ことをテストしますと言っている。

get :edit, params: { id: dummyTweet } 
擬似的なリクエストを起こすコード。get :editでアクション名editを、params: { id: dummyTweet } でdummyTweetがTweetsテーブルの何番目のカラムにあるのかを示すidを、そのリクエストに情報として渡す。

expect(response).to render_template :edit
ちゃんと期待するビューが返ってくるかな?というテストコード。resposeが、editが起きた時に呼ばれるrender_template(ビューである)と期待する(expect)。


ターミナルにて
% bundle exec rspec spec/controllers/tweets_controller_spec.rb
を実行してテストを実施しよう。


初学者の視点からRailsアプリケーションのRspecを使ったテストについてまとめてみました。指摘やご意見お待ちしてます。モデルや、コントローラーのその他アクションについても、まとめていきたいと思います。

最後までご覧いただきありがとうございました!

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

rails: datetime_selectをカスタマイズする方法

やりたいこと

デフォルトでdatetime_selectを使うとデザイン的にあまり美しく無いので下記のようにカスタマイズしてみた。
デフォルトだと下記のような殺風景なデザインでUX的に美しくない。

before
image.png

after
image.png

どのようにしたらできるのか?

結論から言うと、datetime_separatorを弄ることでafter後のデザインに出来る。
初めは、文字しか書けないんだろうなと思ったらhtmlも書き込むことができた。

= f.datetime_select :date, {use_month_numbers: true, start_year: Date.today.year, datetime_separator: '<h4 class="fb-txt font-weight-bold my-2">時間</h4>'},{class: "form-control", id: 'ymd-date'}

ちなみに
datetime_separator以外にdate_separatortime_separatorもあるので暇なときにそっちについても記事にしていきたいと思う。
コメントで要望があればすぐに対応しますが、多分ググればそれっぽい記事があると思うのでまずはググってみてください。

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

【jQuery】RailsでValidation Pluginを使った動的なバリデーションチェックの実装その1

はじめに

本記事がQiitaでの初投稿となります。
プログラミングスクールで開発中のECサイトアプリでjQueryのプラグインを使って
動的なバリデーションチェック機能を実装しました。
実装手順とRailsで使用する場合の注意点をシェアします。

開発環境

Ruby 2.5.1
Rails 5.0.7.2
jQuery 3.4.1
jQuery Validation Plugin 1.19.1
Haml 5.1.2
Sass 3.7.4

jQuery Validation Pluginとは

jQuery Validation Pluginはバリデーションチェックが実装できるjQueryのプラグインです。

公式サイト:https://jqueryvalidation.org/

やりたいこと

  • 入力フォームにユーザー情報を入力する際にリアルタイムにバリデーションをチェック
  • バリデーションエラーがある場合はエラーをリアルタイムに表示
  • バリデーションがNGの場合は入力欄が赤色、OKの場合は緑色に変更

なぜjQuery Validation Pluginを選んだのか?

バリデーションチェックを実装するにあたり、実装方法を検討しました。
他にも以下の方法があります。
RailsのActive Record バリデーション
HTMLのフォーム検証機能
HTML5 + JavaScriptによるFormバリデーション

プラグインなどを有効活用でき、動的にバリデーションチェックできることを主軸に検討し、
jQueryプラグインでフロントエンドでの第1チェックを行い、Railsのバリデーションでバックエンド側の最終チェックを行う方法を選びました。

完成イメージ

 バリデーションNGの場合
f84fd46c54185fe33c48155f5c351a86.gif
 バリデーションNG→OKの場合
89c9be89713460808c27152edc4ec222.gif

 バリデーションOKの場合
e20855f932d4782f531643e312954f0a.gif

実装手順

1. プラグインの導入

CDNでも導入できるそうですが、上手くいかなかったため、下記手順で導入しました。

1.1 公式サイトにアクセス
スクリーンショット 2020-01-04 0.41.33.png
1.2 ダウンロードページへ
スクリーンショット 2020-01-04 0.42.38.png
1.3 最下段からzipファイルをダウンロード
スクリーンショット 2020-01-04 0.43.37.png
1.4 下記のJSファイルをアプリのディレクトリに保存

  • jquery-validation-1.19.1/dist/jquery.validate.min.js (※1)
  • jquery-validation-1.19.1/src/localization/messages_ja.js (※2)

※1 : 必須
※2 : 標準メッセージを日本語化したい場合のみ

2. HTMLファイルの作成

下記のように入力フォームのform_forとf.text_filedなどの各入力欄にidを割り振ります。
(例: id: "signup-form", id: "name" etc.)
BEMが長くて読みにくいですが、ご容赦ください。

Railsでの注意点:HTMLではname属性値を付けないようにしましょう。(理由は後述します。)

私は最初、name属性値を指定していてバリデーションチェックが動作しませんでした。

registration.html.haml
= form_for(@user, url: phone_signup_index_path, method: :get, html: {class: "registration__main__form", id: "signup-form"}) do |f|
  %div.registration__main__form__content
    %div.registration__main__form__content__group
      = f.label :name, "ニックネーム", class: "registration__main__form__content__group__label" 
      %span.registration__main__form__content__group__require 必須
      = f.text_field :name, class: "registration__main__form__content__group__input", placeholder:"例)テスト太郎", id: "name"
    %div.registration__main__form__content__group
      = f.label :email, "メールアドレス", class: "registration__main__form__content__group__label"
      %span.registration__main__form__content__group__require   必須
      = f.email_field :email, class:"registration__main__form__content__group__input", placeholder:"PC・携帯どちらでも可", id: "email"
    %div.registration__main__form__content__group
      = f.label :password, "パスワード", class: "registration__main__form__content__group__label"
      %span.registration__main__form__content__group__require  必須
      = f.password_field :password, class:"registration__main__form__content__group__input", placeholder:"7文字以上の半角英数字", id: "password"
      %p.registration__main__form__content__group__input__info  ※ 英字と数字の両方を含めて設定してください。
      %div.registration__main__form__content__group
        = f.label :password_confirmation, "パスワード(確認)", class: "registration__main__form__content__group__label"
        %span.registration__main__form__content__group__require  必須
        = f.password_field :password_confirmation, class:"registration__main__form__content__group__input", placeholder:"7文字以上の半角英数字", id: "password_confirmation"

3. Javascriptファイルの作成

次に下記のようにJavascriptファイルを作成します。
ファイル名は任意です。

jquery.validate.handler.user.js
$(function () {
  // メソッドの定義
  var methods = {
    email: function (value, element) { // メールアドレスの正規表現
      return this.optional(element) || /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/i.test(value);
    },
    password: function (value, element) { // パスワードの正規表現
      return this.optional(element) || /^(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,100}$/i.test(value);
    },
  }
  // メソッドの追加
  $.each(methods, function (key) {
    $.validator.addMethod(key, this);
  });
  // バリデーションの実行
  $("#signup-form").validate({
    // ルール設定
    rules: {
      "user[name]": {
        required: true // ニックネームの入力有無チェック
      },
      "user[email]": {
        required: true, // メールアドレスの入力有無チェック
        email: true // メールアドレスの正規表現チェック
      },
      "user[password]": {
        required: true, // パスワードの入力有無チェック
        password: true // メールアドレスの正規表現チェック
      },
    },
    // エラーメッセージの定義
    messages: {
      "user[name]": {
        required: "ニックネームを入力してください"
      },
      "user[email]": {
        required: "メールアドレスを入力してください",
        email: "フォーマットが不適切です"
      },
      "user[password]": {
        required: "パスワードを入力してください",
        password: "英字と数字両方を含むパスワードを入力してください"
      },
    },
    errorClass: "invalid", // バリデーションNGの場合に追加するクラス名の指定
    errorElement: "p", // エラーメッセージの要素種類の指定
    validClass: "valid", // バリデーションOKの場合に追加するクラス名の指定
    }
  });
  // 入力欄をフォーカスアウトしたときにバリデーションを実行
  $("#name, #email, #password").blur(function () {
    $(this).valid();
  });
});

基本的な記述方法は下記の記事を参考にさせていただきました。
【jQuery入門】validate()の使い方と独自ルールの設定方法!
jQuery Validation 簡単にフォームをチェック バリデーション
フォームの入力値を検証するjQuery Validation Pluginの使い方

メールアドレスとパスワードの正規表現はデフォルトのルールでも実装可能です。

Railsでの注意点

通常の使い方だとrules:の中にname属性値を指定しますが、下記の記事の通り、Railsの場合は少し通常と異なります。
[Rails]「jQueryValidationプラグイン」を使用してフォームのバリデーションを実装

ページのソースを確認するとname属性値は"user[name]"となっています。
このname属性値をrules:の中で指定するとバリデーションチェックが動作します。

<input class="クラス名" placeholder="例)メルカリ太郎" id="name" type="text" name="user[name]" />
<input class="クラス名" placeholder="PC・携帯どちらでも可" id="email" type="email" name="user[email]" />
<input class="クラス名" placeholder="7文字以上の半角英数字" id="password" type="password" name="user[password]" />

このRails特有のname属性値の指定の仕方についての記事が少なく途中で手詰まりました。
いろいろ調べたところ以下記事で明確な記載がありました。
JQuery validate in Rails applications

Please note that in rails, name of an element is created as form[name] instead of name only. If you are creating a form for creating a new user and fields in this form are Email first_name, last_name etc then name of these fields is being created as user[emal], user[first_name], user[last_name] resp. While using these name of an elements as key you have to keep in mind to use them in qoutes i.e
“user[email]” or your jquery will be unable to understand what this name belongs to.

'user[name]': {required : true}

一部を訳すとRailsではname属性値がuser[email]となっているため、"user[email]"としないとjQueryがそのname属性が何に属しているかを認識しないと記述がありました。

入力欄をフォーカスアウトした時に動作させる

通常の使い方ですとsubmitボタンが押されたタイミングでバリデーションチェックが動作します。
入力欄をフォーカスアウトした時などに動作させたい場合は、下記のように実行のタイミングを指定します。

  // 入力欄をフォーカスアウトしたときにバリデーションを実行
  $("#name, #email, #password").blur(function () {
    $(this).valid();
  });

下記の記事を参考させていただきました。
jquery.validate.jsでvalidateチェックタイミングを指定する

4. CSSファイル作成

バリデーションNGもしくはOKの時に追加されるクラス名を独自に指定できるので、
CSSで枠線を指定します。

    errorClass: "invalid", // バリデーションNGの場合に追加するクラス名の指定
    validClass: "valid", // バリデーションOKの場合に追加するクラス名の指定
registration.scss
.registration {
  &__main {
    // 〜省略〜
    &__form {
      // 〜省略〜
      &__content {
      // 〜省略〜
        &__group {
          // 〜省略〜
          &__input.invalid { // バリデーションNGの場合に枠線を赤色に変更
            border: #ea352d 1px solid;
          }
          &__input.valid {
            border: seagreen 1px solid; // バリデーションNGの場合に枠線を緑色に変更
          }
        }
      }
    }
  }
}
// エラーメッセージ
#name-error, #email-error, #password-error {
  color: #ea352d;
  line-height: 1.5;
  font-size: 14px;
  margin-top: 8px;
}
// 入力欄をクリック時に枠線を水色に変更
input[type=text]:focus,
input[type=email]:focus,
input[type=password]:focus {
  outline: none;
  border-color: #0098E8;
}

まとめ

jQuery Validation Pluginを用いて実行タイミングを指定すると動的なバリデーションチェックが実装可能です。

他にもselectタグのバリデーションなども実装しましたので別記事でまとめたいと思います。
記載内容に誤りなどございましたら、ご指摘いただけますと幸いです。

参考URL

https://jqueryvalidation.org/
https://www.sejuku.net/blog/44470
https://sys-guard.com/post-14579/
https://into-the-program.com/jquery-validation-plugin/
[Rails]「jQueryValidationプラグイン」を使用してフォームのバリデーションを実装
jquery.validate.jsでvalidateチェックタイミングを指定する

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

【Rails】Rspec何言ってるか解読してみた件。コントローラー#new編

今回は、写真とタイトルを投稿するアプリケーションのtweetsコントローラーのnewアクション(新規ツイート投稿画面を呼ぶ)が問題なく動くかどうか、Rspecという言語を使ってテスト。

spec/controllers/tweets_controller_spec.rbに、擬似的にnewアクションを起こすリクエストを飛ばして、ちゃんとnewアクションに対応したビューが返ってくるかな?というテストコードを記述。

コントローラーのテストをする際は、2つの点に注意。
①tweets_controller.rbに、テストしたいアクションに対して、before_actionが定義されていないこと。
②gem 'rails-controller-testing'をインストールしよう。

では、見ていきましょう。

tweets_controller_spec.rb
require rails_helper'

describe TweetsController, type: :controller do
  describe 'GET #new' do
    it "renders the :new template" do
      get :new
      expect(response).to render_template :new
    end
  end
end

require 'rails_helper'
RailsでRspecを利用する時の共通の設定が記載されているrails_helper.rbを読み込む。
これがなきゃ始まらない。

describe TweetsController, type: :controller do end
tweetsコントローラーのテストをするよ。 type: :controllerこれは書かなきゃいけないみたいですね。

describe 'GET #new' do end
GETメソッドのnewアクションに関するテストだよ。

it "renders the :new template" do end
do - endの間に書かれている、実際に動くテストコードの説明。
newアクションに対応したビューファイル(template)が呼び出される(renders)ことをテストしますと言っている。日本語でも良いらしい。ちなみに、it do endのセット1つで、1exampleと呼ぶ。

get :new 
擬似的なリクエストを起こすテストコード。そのリクエストに、アクション名newを情報として渡す。

expect(response).to render_template :new
ちゃんと期待するビューが返ってくるかな?というテストコード。
resposeが、newが起きた時に呼ばれるrender_template(ビューである)と期待する(expect)。

ターミナルにて
% bundle exec rspec spec/controllers/tweets_controller_spec.rb実行すると以下の表示が。

TweetsController
  GET #new
    renders the :new template

Finished in 0.13692 seconds (files took 3.21 seconds to load)
1 example, 0 failures

TweetsController
GET #new

describeに書いたテストの概要説明。

renders the :new template
itに書いた、実際に動くテストコードの説明。

1 example, 0 failures
1exampleあって、0個の失敗。

つまりテストは成功し、問題なかったということ。


初学者の視点からRailsアプリケーションのRspecを使ったテストについてまとめてみました。指摘やご意見お待ちしてます。モデルや、コントローラーのその他アクションについても、まとめていきたいと思います。

最後までご覧いただきありがとうございました!

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

github actions rspec

成果物

.github/workflows/ruby.yml
name: Ruby

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:10.3
        ports: ["5432:5432"]
        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
    steps:
    - uses: actions/checkout@v1
    - name: Set up Ruby 2.5
      uses: actions/setup-ruby@v1
      with:
        ruby-version: 2.5
    - name: Install PostgreSQL client
      run: |
        sudo apt-get -yqq install libpq-dev dialog apt-utils
    - name: Build App
      env:
        RAILS_ENV: test
        DATABASE_URL: postgresql://postgres@localhost:5432/postgres?encoding=utf8&pool=5&timeout=5000
      run: |
        gem install bundler:1.16.1
        cd docker/api/api
        bundle install --quiet --jobs 4 --retry 3
        bin/rails db:migrate:reset RAILS_ENV=test
    - name: Build and test with Rspc
      env:
        RAILS_ENV: test
        DATABASE_URL: postgresql://postgres@localhost:5432/postgres?encoding=utf8&pool=5&timeout=5000
      run: |
        cd docker/api/api
        bundle exec rspec
config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password:
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: myApi_development

test:
  <<: *default
  database: postgres_test
  url: <%= ENV['DATABASE_URL'].gsub('?', '_test?' ) %>

production:
  <<: *default
  database: myApi_production
  username: myApi
  password: <%= ENV['MYAPI_DATABASE_PASSWORD'] %>
  url: <%= ENV['DATABASE_URL'] %>

はまった

エラー1

PG::ConnectionBad: could not translate host name "postgres" to address: Temporary failure in name resolution

役に立った

https://stackoverflow.com/a/20722229

postgresql://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]

stackoverflowを見る限り、localhostにしておけばいいっぽい。
てことで、これでなおった

before
      env:
        RAILS_ENV: test
        DATABASE_URL: postgresql://postgres@postgres:5432/postgres?encoding=utf8&pool=5&timeout=5000
----------
after
      env:
        RAILS_ENV: test
        DATABASE_URL: postgresql://postgres@localhost:5432/postgres?encoding=utf8&pool=5&timeout=5000

image.png


エラー2

Could not locate Gemfile

解決策

Gemfileが存在するdirectoryに移動する

        cd docker/api/api

エラ-3


戦闘

image.png

エラーですぎ。うれしい。エラーでないと死ぬ
OSが本番環境と違うと思った。とりあえずOK
https://rocky-plateau-44026.herokuapp.com/
https://github.com/kajirikajiri/rails-api

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