20191020のRailsに関する記事は22件です。

【Rails】ユーザー登録機能が動作しない

開始タグ

ユーザー登録実装中に、データベースに登録できなくて詰まったのでメモ。

道中

色々な参考サイトを探してみるも明確な答えにたどり着けず、とりあえず「ry-byebug」のgemでデバッグして見た。

  def create
    @user = User.new(name: params[:name], pass: params[:pass])
    @user.save
    binding.pry
    redirect_to("/dashboard/index")
  end

すると以下のエラーが。

Started POST "/users/create" for 172.21.0.1 at 2019-10-20 13:31:02 +0000
Cannot render console from 172.21.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"AGAzaEa5VNEyv9sRn/kzQORFEDBs8CsBuxAPeH+7xB8ZM/qOfevgOKQLlmJH4mAHaDb6XEWhPWh2zHFPsMuVhA==", "name"=>"usertest", "pass"=>"passtest"}
Completed 500 Internal Server Error in 1ms (ActiveRecord: 0.0ms)



ActiveModel::UnknownAttributeError (unknown attribute 'username' for User.):

app/controllers/users_controller.rb:9:in `create'

usernameなんてないはずなのだが・・・と思い調べてみると、同じような現象が起きている方の投稿を発見。

ActiveModel::UnknownAttributeErrorの意味|teratail

もう一度db:migrateを行えば解決したようですので、試してみる。
※Dockerを使用しています。

docker-compose run web bundle exec rake db:migrate

再度登録作業を行ったら、無事データベースに登録されました。

試しにフォームのinputのname属性を変更して再度登録してみました。

user→username
pass→password

<%= form_tag("/users/create") do %>
  <input type="text" name="username" placeholder="ユーザーネーム" class="form_inp inp_text">
  <input type="text" name="password" placeholder="パスワード" class="form_inp inp_text">
  <div class="btn_area">
    <input type="submit" value="登録する" class="btn btn01 darkblue">
    <!-- /btn_area -->
  </div>
<% end %>

すると再度エラーが出ました。
そこでもう一度db:migrateすると、無事登録できるようになりました。

なるほど、そういえばname属性をusernameに変更していたな・・・。
一度送信を行ったあと(失敗しても)にinputのname属性を変更してしまうとエラーになってしまうようで、「name属性を変更してしまったこと」が原因だった。

閉じタグ

今後、name属性の変更はなるべく行わないようにしよう。
データベースへの登録に困った際は「db:migrate」を試してみよう。
時間かかってしもうた・・・。

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

インクリメンタルサーチについての復習

インクリメンタルサーチの復習

Ajaxの非同期通信を学習中で、前回はコメントの投稿をページの再読み込みなしで表示させました。
今回は文字を入力して検索する際、検索ボタンを押して再読み込みをさせずに非同期で検索結果を表示させるようにします。

インクリメンタルサーチとは

そもそもインクリメンタルサーチとは、
Incremental Search
Incremental : 増加、増大する、追加する
Search : 検索
と単語の意味で考えると正直分かりませんが、IT用語で「文字入力するたびに自動的に検索が行われる検索方法」を指します。

インクリメンタルサーチの実装

インクリメンタルサーチ実装の流れを復習していきます。
あくまでAjax、JavaScriptの流れを把握することが目的なので、HTMLなどの表記はある程度省略していきます。

search.js
$(function() {
  $(".search__form").on("keyup", function(){

    var input = $(".search__form").val();

    $.ajax({
      type: 'GET',
      url: '/products/search',
      data: { keyword: input },
      dataType: 'json'
    })

    .done(function(products) {
      $(".検索結果表示クラス").empty();
      if (products.length !==0) {
        $(".検索結果表示クラス").append("インクリメンタルサーチの結果を表示させる記述");
      }
      else {
        $(".検索結果表示クラス").append("検索結果がない旨を表示させる記述";
      }
    })
    .fail(function(){
      alert('映画検索に失敗しました');
    })

全体的な記述は上記のようになると思います。
細かい内容をおさらいしていきます。

$(".search__form").on("keyup", function(){

keyupイベントはキーボードのキーが上がった時に発火します。文字入力でキーを離したタイミングですね。
イベントの発火を検知する場所は、ここでは"search__form"クラスを持つ検索フォームが指定されています。

    var input = $(".search__form").val();

検索フォームに入力されている値をinputに代入します。

    $.ajax({
      type: 'GET',
      url: '/products/search',
      data: { keyword: input },
      dataType: 'json'
    })

Ajaxで非同期通信を行う際に指定する内容を記述します。
URL、data内のkey名(keyword)はルーティングやインスタンス変数の定義により変わるので一例です。
今回の記述では、{ keyword: input }データを/products/searchパスにGETメソッドで送り、結果をJSON形式で返してもらいます。

    .done(function(products) {
      $(".検索結果表示クラス").empty();
      if (products.length !==0) {
        $(".検索結果表示クラス").append("インクリメンタルサーチの結果を表示させる記述");
      }
      else {
        "検索結果がない旨を表示させる記述";
      }
    })
    .fail(function(){
      alert('映画検索に失敗しました');
    })

.doneメソッドと.failメソッドは対で記述しておきましょう。そして.failメソッドはalertで「エラーが発生しました」と表示される程度でいいかと思います。

今回の.doneメソッドでは、まず検索結果をproductsに代入します。
現在表示されている検索結果をemptyメソッドで消去します。
emptyメソッドは、その要素そのものではなく子要素を削除します。これで検索結果を表示させる箱は残しながら、中身を全て消してリセットしてしまいます。その後検索結果をリセットされた中身に追加して表示させるようにします。

ifを使って場合分けを行います。検索結果の個数が0でなければその結果を表示させ、検索結果の個数が0の場合は検索結果がない旨の記述が適用されます。

結果

検索フォームに文字入力をすると、キーから指を離すたびにフォーム内の文字列が拾われて都度その文字列で検索をかけられます。
文字を入力する度に検索が実行されているわけですから、PC本体やネット回線のスペックがそれなりにないと負荷がすごいことになりそうですね。
ブロードバンドが普及する前にインクリメンタルサーチが使われていたら、どうなっていたのでしょうか?

JavaScript(jQuery)の書き方にはまだ慣れませんが、書いている中身はなんとなく分かってきた気がします。一気に書くとさっぱり分かりませんが、一つ一つ読み解きながら進めていくとどうにか分かるようになってきました。

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

Rails初学者必見!クラスの作り方分かってないと色々やばい!

なぜ書こうと思ったか

Rails系プログラミングスクール卒業生に
クラスの定義に関して簡単な問題を出したところ、
絶望的にできなかった。

その問題というのはこちら

「User.new(first_name: '太郎').first_name」で「太郎」を取得できるよう、クラス「User」を作成してください。

また、「User.new(first_name: '太郎', last_name: '田中').full_name」で「田中太郎」が取得できるよう、インスタンスメソッド「full_name」を定義してください。

答え書いた方がいいですか?

答え書いちゃうと巷のプログラミングスクールとおんなじのマニュアルお化けになっちゃうので、書きたくないです。
書き方は一つじゃないですし・・・。
頑張って調べて挑戦してみてください。

終わりに

知ってたら5分かからないくらいで書けちゃうコードなんですよね。
Rails系のプログラミングスクールを卒業しても、戦力になっていない人が多いのは、
単純に、Rubyの最低限の基礎知識もわかってないからなんだろうなと、切に思います。
もし、あなたがプログラミングスクールを卒業しても、
この問題を5分足らずで書けないなら、Rubyの基礎をしっかり固める時期だと認識しましょう。

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

Rails チュートリアル 第7章 まとめ

RESTful
/user/1というurlは一致しているが、GetなりPatchなりの処理を変えていくことで、DBに行いたい処理を切り替える

<%= debug(params) if Rails.env.development? %>
paramsには、ブラウザからのリクエスト内容が入っている、それを画面に出力する
if以下は開発環境ならばという意味

resources :usersで、7つのルーティングが用意されるので、それに応じたアクションを作っていく
用意されたルーティングはrails routesで確認できる(patchとputで実際は8)

GET(urlを入力してenterを押すと発行されるもの) /user/id user#show
ローカル変数(user)とインスタンス変数(@user)ではスコープが違う
ローカル変数ではそのメソッド内、インスタンス変数はビューから呼び出したい時に呼び出すことができる

showアクション -user詳細ページ
GET users/:id => users/2
@user = User.find(params[:id) # params[:id] = 2
ルーティングで使われているidがparamsに格納される。そのidを使って、Userを把握してUser情報を取得し、@userに代入する。
Viewでは@user.nameと画像を表示


Newアクション -新規登録ページ
@user = User.new
新規登録で、新しいユーザーを作るのでUser.new

new.html.erb
% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3"> # bootstrapのデザイン上の設定
    <%= form_for(@user) do |f| %>
#form_for(@user)とすることで、ここで入力された内容が全て@userに入る
#それをcreateアクションで、paramsとして使う
      <%= f.label :name %> 
      # Name
      # Userに入力する内容を教える
      <%= f.text_field :name %> 
      #name入力用フォーム作成、入力された内容はnameカラムに代入される(?)
      # nameというkeyにユーザーの入力内容がvalueに

      <%= f.label :email %> # Email
      <%= f.email_field :email %> # email用フォーム作成

      <%= f.label :password %> # Password
      <%= f.password_field :password %> #伏せ字になるフォーム作成

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation %>
      # ボタン作成、Userが押すと全ての値がparamsに格納される
      # createアクションがparamsが使われる
      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

newアクションを実行すると、createアクションがないとErrorが出る
newからcreateへ移行する仕組みは整っているということを意味する
form_forを作ったことによって、Postリクエストが/usersに送られており、それでcreateアクションが呼ばれている

createアクション -入力された内容をDBへ保存
user情報は二重のハッシュ構造になっている
{user: ruby/hash-----}
のさらに、user:は
{name: ----} {email: ----}というハッシュを持っている

ダミーコード

 def create
    @user = User.new #newで作成したインスタンスをcreateでも作成する意味がわからない
    @user.name = params[:user][:name]
    # @user.nameは6章でconsoleを使ってu.name = "--"でnameが更新できたという知識から
    # 右辺は、paramsに格納されているuser情報は二重のハッシュ構造になっていることから
    : # email,passwordも同様

    if @user.save => Validationが実行されるとユーザーオブジェクトかfalseを返す
    # インスタンスをDBに保存
    # valiが成功したら、ユーザーオブジェクトが返る
    # rubyではfalseとnil以外はtrue、つまりユーザーオブジェクトもtrue
    else
       render "new" #登録失敗なら、signupページを
    end

  end

このコードはさらにシンプルに書くことができる
User.new(name: ..., email: ..., password: ... ,)という書き方が可能であることから
引数の変換は上記のuser:がハッシュ構造だということから分かる
params[:user]には全てのUser情報が入っている

def create
    @user = User.new(params[:user])

    if @user.save => Validationが実行されるとユーザーオブジェクトかfalseを返す
    # インスタンスをDBに保存
    # valiが成功したら、ユーザーオブジェクトが返る
    # rubyではfalseとnil以外はtrue、つまりユーザーオブジェクトもtrue
    else
       render "new" #登録失敗なら、signupページを
    end

  end

ただ、paramsの中身を悪意のあるUserに変更される恐れがあるので

params.require(:user).permit(:name, :email, :password, :password_confirmation)
# params[:user]からの変更

を使う
これを使うことで、paramsをチェックすることができ、指定した項目以外は無視する
メソッドとして独立させて使うと良い
Createアクションが今まで一番難解なので、要復習

エラーメッセージの表示
今後もフォーム作る時に使うので、Partial化(第5章)

<%= render 'shared/error_messages' %>

class="form-control"はデザイン上の話なので、考えないでいい

shared._error_messages
<% if @user.errors.any? %> #Errorがひとつ以上あるなら表示
  <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| %>
    # errors.full_messagesという配列にErrorが入っている
    # それをひとつずつ表示
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

form_forのHTMLを見ると、action属性には/user、method属性にはPOSTが指定されている
これでCreateアクションに移ることが分かる

IntegrationTest

test "invalid signup information" do
    get signup_path # signupページに行く、GETリクエストを送る
   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


    # users_path = /users  rails routesで分かる
    # /usersにPOSTリクエストを送る = createアクションへ
    # その時に次のparamsを送る、nameを空にしたりしてわざと失敗するようにしている(invalid)
    # assert_noーを使い、User.countで調べたUser数が登録に失敗して変わってないことを確認
    # 失敗したら、newに戻るかどうかチェック

ユーザー登録成功時

redirect_to "/users/#{@user.id}"
redirect_to user_path(@user.id)  #名前付きルートで書ける
redirect_to user_path(@user) 
#user_pathのデフォルトの挙動では、そのオブジェクトのid情報を渡すのでid要らない
redirect_to @user  => show
# redirectではデフォルトの挙動で、引数に@userを渡すとuser_path(@user)と同じと判断

デフォルトでid情報を渡すので@user.idとしなくてもいい
ここにGETリクエストを送ってくれという指示、今回ならshowページへ行くように設定されている
renderはビューの表示

flashという特殊な変数にキーと値を入れると一時的に表示される、実際はメソッド

<% flash.each do |message_type, message| %> 
# | key,value|
# 今回なら、|:success, Welcome to the sample - |
        <div class="alert alert-<%= message_type %>"><%= message %></div>
# alert-< でbootstrapで定義された色合いを出すことができる
      <% end %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

未経験がマッチングアプリを作ってみた話【Rails】

はじめに

未経験からのWeb系企業転職をするためのポートフォリオを作成しました。
マッチング機能を用いてエンジニアをつなぐサービスです。
8月から勉強をはじめ、10月から着手し完成まで至りました。(改修は随時行なっていきます。)

なぜ作った?

私は現在地方で働いています。
だいぶ田舎にいるため、東京都比べてしまうとコミュニティが少ないです。というかありません。
このような壁があり、エンジニアとの繋がりが少ないのです。

そこで、エンジニア向けのマッチングアプリがあればこの問題は解決できるのでは?というのが始まりです。
Twitterでよくない?という声が聞こえてきますが、Twitterではその人の得意な技術や好きな分野を調べなくてはなりません。
このサービスでは、「タグ機能」があり、その人の好きな分野をすぐに知ることができます。

 サービス紹介

スクリーンショット 2019-10-20 21.15.18.png

【リンク】
https://engineer-link.herokuapp.com

【Github】
https://github.com/kotaro-saitou/engineer-link-app

Engineer Linkは、エンジニアとエンジニアが気軽にメッセージを送りあえるサービスです。

主な機能

・画像付きユーザー登録機能
・(ログイン時)フォロー/アンフォロー機能
・記事の投稿機能
・タグ(Ruby、Pythonなどの自分の好きな技術など)の画像付き投稿機能
・相互フォローのみリアルタイムメッセージ機能
・記事、タグ、ユーザー一覧でのページネーション機能

技術

・Rails5.2.2
・Ruby2.5.4
・CircleCI
・Rspec
・Heroku
・AmazonS3(プロフィール画像、タグの画像)
・ActionCable(リアルタイムチャット)

工夫

・CircleCIを用いて自動デプロイを実装
・Githubでissue管理を行なった

参考にしたサイト

【Rails】Herokuで画像を投稿できるようにする方法(ActiveStorage + Amazon S3)

https://qiita.com/hmmrjn/items/479c9e9ce82771f1b6d7

Rails5.2から導入されたcredentials.yml.encを極める

https://qiita.com/yuuuking/items/53a37a2e998972be32b8

Rails 5 + ActionCableで作る!シンプルなチャットアプリ(DHH氏のデモ動画より)

https://qiita.com/jnchito/items/aec75fab42804287d71b

未経験がWeb系転職成功したいならgithubでissue管理して開発しよう

https://qiita.com/fukubaka0825/items/c7710b4e87d478c8ba3b

全てとても参考になりました!!
ありがとうございます!!!

思ったこと

このサービスを開発するにあたり、以下の技術は勉強しながら実装しました。

・Rspec
・ActionCable
・CircleCI

この辺りのことを実装するにあたり、「手を動かしながら学ぶことの重要性」を感じました。(特にRspec)

Rspecの勉強はEveryday Railsが本当におすすめです!!
https://leanpub.com/everydayrailsrspec-jp
翻訳してくださっている方々に深く感謝しながら勉強しましょう。

CircleCIに関しては、調べながらやっていけば比較的簡単に導入できます。

ポートフォリオを作成できていない未経験の方へ

Progateなどで基礎を勉強することは楽しいですし、素晴らしいことです。
しかし、勉強したことを元に物を作るのはもっと楽しいです!

なんでもいいので、物を作り、そこに自分にとって未知の技術を取り入れることは、
勉強にもなり非常に楽しいです。

最後に

これからは、この二ヶ月はサーバーサイドに偏りすぎていたので、フロントの勉強に力を入れつつ
引き続き頑張ります!

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

eachで回す時の<% %>と<%= %>の違い

eachで回していて、
なぜか、以下の画像のように出力されました。
スクリーンショット 2019-10-20 21.12.05.png

[#<User id: 1, name: "aaaa", email: "aaaaa", created_at: "2019-10-19 12:10:03", updated_at: "2019-10-19 12:10:03">,
 #<User id: 2, name: "bbb", email: "bbbb", created_at: "2019-10-19 12:10:35", updated_at: "2019-10-19 12:10:35">]

この部分いらないな。てか、なんで出てきたんだろう。今までこんな事なかったのに、、、
と思っていたところ、問題は、viewでした。

index.html.erb
<h1>Users#index</h1>
<p>Find me in app/views/users/index.html.erb</p
<div>
  <%= @user.each do |user| %>
    <p>
      <%= user.name %>
      <%= user.email %>
    </p>
  <% end %>
</div>

<%= @user.each do |user| %>の=が問題でした。
<% @user.each do |user| %>とすると、いらない部分は消えました。
=をつけると、データ側も出力されるという新しい発見でした笑

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

Payjpを使って、商品購入機能を実装する【Rails】

概要

「payjp」というgemを使うことで、簡単にクレジットカード(以下CC)購入フォームと機能を作成することができます。
本記事では、某フリマアプリのクローンアプリを開発した際に、購入機能を実装する上で登録画面のマークアップからpayjp利用したサーバーサイドの実装までを紹介していきます。

実装する機能

  • Checkoutを利用した購入機能(この段階でのみCheckout利用)
  • payjp.jsを利用した実装
    • CC登録機能
    • 登録したCCをユーザー情報と紐づけて表示させる
    • CC削除機能
    • 購入機能(再実装)
  • ユーザー新規登録登録画面でもCC登録ができるようにする(+α)

バージョン情報や前提条件

  • ruby 2.5.1
  • payjp 5.2.3
  • 前提条件
    • hamlで記述
    • sassで記述
    • deviseを導入しており、ログイン機能が実装されている

実装

DB設計やルーティングの違いにより、実装する上で記述内容に差異が生じるかと思います。
適宜考えて実装していただけると幸いです。

payjp gemをインストール

Gemfileに以下を記述して、bundle installします。

gem 'payjp'

payjpのサイトにアカウント登録

payjpのサイトでアカウントを登録します。
https://pay.jp/

APIキーを確認

登録が完了したら、payjpの管理画面でAPIキーを確認します。
今回確認するのは、テスト用の公開鍵(pk~)と秘密鍵(sk~)です。
こちらは後ほど使用するので、画面を残しておきましょう。
Image from Gyazo

購入機能の実装(checkout利用)

続いて購入機能の実装です。
購入機能自体はpayjpで用意されているライブラリ 「Checkout」 を利用すれば、簡単に実装できます。

1.ビューファイル編集

購入確認画面のビューファイル内の 「購入する」 の記述を、以下の記述と置き換えます。

app/views/transacts/buy.html.haml
= form_with "パスを指定" do
  :plain
  %script{type: "text/javascript", src: "https://checkout.pay.jp", class:"payjp-button", "data-text": "購入する", "data-key": "公開鍵(pk_~)"}

これだけで購入に関するフォームの記述は終了です。
置き換えた後に表示された「購入する」をクリックすると、CC登録フォームがモーダルで表示されます。
Image from Gyazo
使用するカード情報
カード: 4242424242424242(Visaのテストカード)
有効期限: 現在より未来の期日
CVC番号: 3~4桁の任意の数字
名前: 任意の名前

使用するカードはpayjpよりテストカードが用意されているので、そちらを使用しましょう。
https://pay.jp/docs/testcard

2.コントローラ編集

app/controllers/transacts_controller.rb
require 'payjp'

  def pay
    Payjp.api_key = "秘密鍵(sk_~)"
    Payjp::Charge.create(
      amount: 1100, # 決済する値段
      card: params['payjp-token'], # フォームを送信すると生成されるトークン
      currency: 'jpy'
    )
  end

後は、ルーティングを設定すれば、購入できるようになります。
実際に購入できているのが確認できます。
Image from Gyazo

再実装【payjp.jsを利用】

checkoutを利用することで、簡単に購入機能が実装できました。
しかし、現在の実装では実際の運用は難しいので、実際の運用を想定して実装し直します。

実装条件

  • ログインしているユーザーに紐づけてカード情報を登録する、削除もできる
  • ユーザーは登録したCCを使用して、商品を購入できる
  • 独自でCC登録フォームを作成する

1.テーブルの作成

マイグレーションファイルを作成し、テーブルの作成とカラムの紐付けを行います。
userは外部キーなので、references型で外部キー制約を指定しています。

db/migrate/2019**********_create_cards.rb
class CreateCards < ActiveRecord::Migration[5.2]
  def change
    create_table :cards do |t|
      t.references  :user,           null: false,    foreign_key: true
      t.string      :customer_id,    null: false
      t.string      :card_id,        null: false
    end
  end
end

記述したら、rake db:migrate を行います。
なお、DBに顧客情報やカード情報そのものを保存することは禁止されているのでご注意ください。http://payjp-announce.hatenablog.com/entry/2017/11/10/182738

モデルの紐付け

モデルファイルを作成し、編集します。
以下、カードに関する紐付けの記述のみ載せています。

app/models/card.rb
class Card < ApplicationRecord
  belongs_to :user
end
app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :cards # 追記する
end

2.クレジットカード登録フォームのマークアップ

続いて登録フォームを作成します。
今回は、某フリマアプリに寄せてています。
一つのファイルに以下の内容も記述すると、記述量が膨大になるため、部分テンプレートを採用しています。

/_card_registration.html.haml
.credit-update
  .credit-update__label
    クレジットカード情報入力
  .card-form
    .card-form__box
      = form_tag(cards_path, method: :post, id: 'charge-form', name: "inputForm") do |f|
        .card-form__box__number
          %label{class:'box-group--label', for: 'card_number'} カード番号
          %span.input-require
            必須
          = text_field_tag "number", "", class: 'card-number--input', type: "text", id: 'card_number', maxlength: "16", placeholder: "半角数字のみ"
          .registration-error{type: "hidden", value: "必須項目です"}
          %ul.card-list
            -# assets/imagesにimageを設置しており、それをimage_tagで呼び出しています。
            %li.card-list--item{ style: "margin-left: 0;"}
              = image_tag "visa.svg", width:"49", height:"20"
            %li.card-list--item
              = image_tag "master-card.svg", width:"34", height:"20"
            %li.card-list--item  
              = image_tag "saison-card.svg", width:"30", height:"20"
            %li.card-list--item  
              = image_tag "jcb.svg", width:"32", height:"20"
            %li.card-list--item
              = image_tag "american_express.svg", width:"21", height:"20"
            %li.card-list--item
              = image_tag "dinersclub.svg", width:"32", height:"20"
            %li.card-list--item 
              = image_tag "discover.svg", width:"32", height:"20"

        .card-form__box__expire
          %label.box-group--label 有効期限
          %span.input-require
            必須
          .card-expire
            .card-expire__select-month
              %select#exp_month{name: "exp_month", type: "text"}
                %option{value: "1"}01
                %option{value: "2"}02
                %option{value: "3"}03
                %option{value: "4"}04
                %option{value: "5"}05
                %option{value: "6"}06
                %option{value: "7"}07
                %option{value: "8"}08
                %option{value: "9"}09
                %option{value: "10"}10
                %option{value: "11"}11
                %option{value: "12"}12
              %i.card-form-expire-icon
                = image_tag "arrow-bottom.png", size:"16x10",class:"arrow-bottom-icon5"
              %span{class: "month"}.card-expire__select-year
              %select#exp_year{name: "exp_year", type: "text"}
                %option{value: "2019"}19
                %option{value: "2020"}20
                %option{value: "2021"}21
                %option{value: "2022"}22
                %option{value: "2023"}23
                %option{value: "2024"}24
                %option{value: "2025"}25
                %option{value: "2026"}26
                %option{value: "2027"}27
                %option{value: "2028"}28
                %option{value: "2029"}29
              %i.card-form-expire-icon
                = image_tag "arrow-bottom.png", size: "16x10",class:"arrow-bottom-icon6"
              %span{class:"year"}.card-form__box__security-code
          %label.box-group--label{for: "cvc"} セキュリティーコード
          %span.input-require
            必須
          = text_field_tag "cvc", "", class: 'payment__security-code', type: "text", id: "cvc", maxlength: "4" ,placeholder: "カード背面4桁もしくは3桁の番号"
          .question-form
            %span.question-form__mark ?
            %span.question-form__text 
              カード裏面の番号とは?
        #card_token
        = submit_tag "追加する", class: "card-form__box__add", id: "token_submit", type: 'button'

こちらにcssを当ててあげると以下のようになります。
Image from Gyazo

3.payjp.jsを編集

payjp.jsファイルを作成し、編集します。
checkoutを利用した場合、checkoutが簡単にトークンを作成してくれていましたが、それを利用しないのでpayjp.jsでトークンを生成する処理を記述する必要があります。

app/assets/javascripts/payjp.js
$(function(){

  var submit = document.getElementById("token_submit");

  submit.addEventListener('click', function(e){  // 追加するボタンが押されたらイベント発火
    e.preventDefault();  // ボタンを一旦無効化
    Payjp.setPublicKey("秘密鍵(pk_~)");
    var card = {  // 入力されたカード情報を取得
      number: document.getElementById("card_number").value,
      exp_month: document.getElementById("exp_month").value,
      exp_year: document.getElementById("exp_year").value,
      cvc: document.getElementById("cvc").value
    };
    if (card.number == "", card.exp_month == "1", card.exp_year == "2019", card.cvc == "") {
      alert("カード情報が入力されていません。"); // 送られた値がデフォルト値だった場合
    } else { // デフォルト値以外の値が送られてきた場合
      Payjp.createToken(card, function(status, response) {  // トークンを生成
        if (status === 200) {
          $("#card_number").removeAttr("name");
          $("#exp_month").removeAttr("name");
          $("#exp_year").removeAttr("name"); 
          $("#cvc").removeAttr("name");
          $("#card_token").append(
            $('<input type="hidden" name="payjp-token">').val(response.id)
          ); 
          document.inputForm.submit();  // 生成したトークンを送信する準備を整える
          alert("登録が完了しました");
        } else {
          alert("正しいカード情報を入力してください。");
        }
      });
    }
    false
  });
});

4.payjp.jsを読み込めるようにする

application.haml.hamlに以下の内容を追記します。
%script{src: "https://js.pay.jp/", type: "text/javascript"}
%script{type: "text/javascript"} Payjp.setPublicKey('公開鍵(pk_~)');

app/views/layouts/application.html.haml
%html
  %head
    %meta{content: "text/html; charset=UTF-8", http: { equiv: "Content-Type" }}
    %title FreemarketSample59a
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
    %script{src: "https://js.pay.jp/", type: "text/javascript"}
    %script{type: "text/javascript"} Payjp.setPublicKey('公開鍵(pk_~)');
  %body
    = yield

5.コントローラを編集

続いてコントローラを編集します。
先ほどはtransacts_controller(商品取引に関する)を編集しましたが、今回は新たにcards_controller(カードに関する)を作成し、それを編集します。

app/controllers/cards_controller.rb
class CardsController < ApplicationController
  require 'payjp'
  before_action :set_card

  # 後ほど登録したクレジットの表示画面を作成します。
  def index
  end

  # クレジットカード情報入力画面
  def new
    if @card
      redirect_to card_path unless @card
    else
      render 'mypages/create_card'
    end
  end

  # 登録画面で入力した情報をDBに保存
  def create
    Payjp.api_key = "秘密鍵(sk_~)"
    if params['payjp-token'].blank?
      render 'mypages/create_card'
    else
      customer = Payjp::Customer.create( # ここで先ほど生成したトークンを顧客情報と紐付け、PAY.JP管理サイトに送信
        email: current_user.email,
        card: params['payjp-token'],
        metadata: {user_id: current_user.id} # 記述しなくても大丈夫です
      )
      @card = Card.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card)
      if @card.save
        redirect_to cards_path
      else
        render 'mypages/create_card'
      end
    end
  end

  # 後ほど削除機能を実装します。
  def destroy
  end

  private

  def set_card
    @card = Card.where(user_id: current_user.id).first if Card.where(user_id: current_user.id).present?
  end
end

秘密鍵を必ず設置するようにしましょう。

6.ルーティングを設定

ルーティングには今後実装するindex、destroyのアクションも記述します。

config/routes.rb
resources :cards , only: [:new, :index, :create, :destroy]

これでユーザーがクレジットカードを登録できるようになりました。

最後に

本記事の紹介は、一旦ここまでの実装で終わります。
また後日続きの実装を載せたいと考えているので、本記事を通して少しでも読者様の参考になれば幸いです。
また、学習期間3ヶ月の若輩者ですので、記事に多々不備があるかと思います。
ご意見やご質問がありましたらお気軽にご連絡ください。

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

チーム開発体験会でやったこと&感想【rails勉強会】

rails勉強会で行ったことまとめ。
今回のrails勉強会ではチーム開発体験を行いました。
超劣化版ツイッター作成くらいに思っていただければ結構です。

ユーザーがいて、コメントができる。
簡単なアプリケーションです。

時間が40分くらい。

一人でも作れちゃう。
いや
そっちの方が早いのではないか?と思うアプリを
40分という短い時間で作ります。

短い時間のためか、
人が増えることによって「大変だなぁ」と思う部分だけ見える会
だと思います。

私は助けてもらった側なのでありがたかったです。ハイ。

これは悪い意味ではなく、悪いところだけ見えるからこそ、対策できるってこと?
と思いましたけど実際のところどうなんですかね。(未確認)

①railsを新規作成

$ rails _5.2.2.1_ new mini_app -d mysql

まずはrails環境を構築したいフォルダに移動し、
「mini_app」というディレクトリでrails構築するコマンドを入力します。

この時、「-d mysql」をつけることで、mysqlにデータベース登録を同時に行えます。
あとからmysql登録もできるようですが、このやり方はご自身で調べてください。

先輩に聞いた話だと、後からmysqlを導入すると、ところどころ齟齬が出てその度
に修正をして時間がかかったそうなので、この「-d mysql」を忘れてしまった場合には作り直した方が手っ取り早いかもしれません。

②deviseの導入とモデルの作成

[初心者] deviseを使ってログイン機能を実装してみよう
上記の記事を参考にveviseを導入しました。

※ひとつでも手順を飛ばしたり、順序を間違えると正常に動作しないため慎重に!
※何度も失敗した人間のありがたいお言葉

お手本のmini_appにはログインユーザの「名前」がなかったため、
作成したマイグレーションファイルに追記せずそのままrake db:migrate すれば問題ありません。
チャットスペースではこのモデルに「name」というモデルを追記するくだりがあるので気をつけてください。

上記を行ってできたテーブルは下記となります。

9f79fd5b79ce334300a9d7587c11e9f7.png

③tweetsモデル作成

$ rails g migration tweets

上記のコマンドでマイグレーションファイルが作成されます。

20191020064311_create_tweets.rb
#$ rails g migration tweets で作られたマイグレートファイル
class CreateTweets < ActiveRecord::Migration[5.2]
  def change
    create_table :tweets do |t|
      t.text :text #この1行を追加しました
      t.timestamps #もともとあります。作成日と更新日の部分です
    end
  end
end

④tweetsにuser_idを持たせる

③でマイグレートした状態では、tweet(コメント)には持ち主がいない状態になってしまうので、
TweetsにUserを紐付けるために、tweetsテーブルに「user_id」を紐付けます。

$ rails g migration AddColumnToTweets user_id:integer
#AddColumnToTweets → Tweetsカラムに
#user_id:integer → user_idをinteger型で追加する

integer型は正数っていう意味で、IDとかに使われるらしいですよ。

上記のコマンドを入力すると、マイグレートファイルが作成されます。

20191020070028_add_user_id_to_tweets.rb
#$ rails g migration AddColumnToTweets user_id:integer で作られたマイグレートファイル
class AddUserIdToTweets < ActiveRecord::Migration[5.2]
  def change
    add_column :tweets, :user_id, :integer
  end
end

特に修正せずにrake db:migrateすればOK!
もし、コマンドを間違えてしまってマイグレートファイルの内容が違っていても、
マイグレートファイルを修正してrake db:migrateすれば大丈夫です。

c0290b3b721cf80273740f739ba0194a.png

⑤モデルファイルにアソシエーションを追記する

上記で2回マイグレートしまして、「tweet.rb」「user.rb」が一緒に生成されています。
このファイルにアソシエーションを追加します。

belongs :tech → techに属している(単数形)
has_many :students → 生徒たち(複数形)

とかいうやつです。ってな感じで細かい説明は省きまして、今回設定したアソシエーションは下記

user.rb
#②で作成されます
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :tweets #これを追記

end
tweet.rb
#③で作成されます
class Tweet < ApplicationRecord
  belongs :user #これを追記
end

これでベースは整いました。多分。

⑥ブラウザで見られるようにする

ここまでの状態ではまだブラウザで見れません。
超基本的な部分ですが、ルーティング・コントローラー・ビューの設定を行います。

$ rails g controller tweets index

上記のコマンドを入力して実行すると、routesに1行追加され、
コントローラーとアクションを一緒に作成できます。

config/routes.rb
Rails.application.routes.draw do
  get 'tweets/index' #⑥のコマンドで追加されたもの
  devise_for :users #②のコマンドで追加されたもの
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
app/controllers/tweets_controller.rb
#⑥のコマンドで生成されたもの
class TweetsController < ApplicationController
  def index
  end
end
app/views/tweets/index.html.erb
<!-- ⑥のコマンドで生成されたもの -->
<h1>Tweets#index</h1>
<p>Find me in app/views/tweets/index.html.erb</p>

⑦ブラウザで確認する

$ rails s

e62a269e2108ce88f0073c40bfc7efc4.png

無事にブラウザで表示することができました。

ここでタイムアップ!

チーム開発体験の感想

チーム開発体験で学んだこと。

  • リーダーとスクラムマスターを決める
    • 短い時間だったらとっとと決めた方がいいと思ってる。
    • 実際のグループ開発の時は話し合ってちゃんと決めたい。
  • どうやって開発するか流れを決める
    • 今回はビュー組とDB組に別れた
    • 短時間の場合は上記の方法が良いと思った
    • 実際のグループ開発の際にはグループ内の環境構築が全く同じであることが重要だと思った
  • 実際に開発する
    • ガッチャンコするところまで行けなかった
    • ここら辺の方針はグループ開発の際にしっかり決めたいなと思った。

先輩に話を聞いたんですが、誰かがベースの環境構築をして、
それをgit cloneして足並み揃える方式でやっているようです。

まだ時間があったらどうするか?

  1. 作ってもらったビューを「application.html.erb」にあてがい表示を確認
  2. 上記の状態をgitを使用してcloneしてもらう
  3. 「tweet」組と「devise」組に別れる。
  4. 影響範囲が少ないところからガッチャンコして調整する
  5. 部分テンプレート化した方が良さそうなところは対応する
  6. リファクタリングする

上記のような優先順位で対応したらいいんじゃないかな〜?と感じました。
勉強会でチームになった方、ライフコーチ方ありがとうございました!

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

Railsのmemberとcollectionについての備忘録

routes.rbの設定で使うmember,collectionの違い

Railsのルーターは受け取ったURLをコントローラーに割り当てる役割を持っている。
ルーターを設定する時に、member,collectionという機能について調べたため、備忘録として残す。

memberの役割

memberは、特定のモデル(idで判別)に対するアクションを設定する時に使う。

ex)

routes.rb
resources :users do
  member do
      get 'like'
    end
  end
ルーターの設定
like_users GET  /users/:id/like(.:format)  users#like

collectionの役割

collectionは、全てのモデルに対するアクションを設定する時に使う。

ex)

routes.rb
resources :users do
  member do
      get 'search'
    end
  end
ルーターの設定
search_users GET  /users/search(.:format)  users#search

まとめ

RESTfulな7つのルーティング以外を設定したい場合、
特定のモデルに対してのアクション
member
全てのモデルのアクション
collection
で設定する。

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

最近学習したrailsコマンド(Progateにて)

Progateにて最近学習した内容をアウトプッティングしてみた

1.アプリ作成

rails new アプリ名_app
例) rails new tweeeet_app

2.機能追加(コントローラー作成)

rails g controller コントローラー名 アクション名
例) rails g controller image index

ホームページ作成
例) rails g controller home top

3. データベースの追加(テーブル作成)

rails g model テーブル名の単数形 カラム名:データ型
例) rails g model User name:string
  ※複数カラムを作成したい場合⬇︎
rails g model テーブル名の単数形 カラム名:データ型 カラム名:データ型•••
例) rails g model User name:string number: integer
rails db:migrate
例) rails db:migrate

4. データベースに情報追加(カラム追加)

rails g migration データ名(add_カラム名_to _追加先のテーブル名)
例) rails g migration add_nickname_to_users
add_column :テーブル名, :カラム名, :データ型 (migrationファイルの”データ名”の中に記入)
例) add_column :users, :name, :string
rails d:b migrate
例) rails db:migrate

〜追記〜

  • データ型は知る限り3種類あり

    • string : 文字列(短い)
    • text : 複数行にわたる場合
    • integer : 数値
  • データの値の変換

    • .to_s ••• 文字列へ
    • .to_i ••• 数値へ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails4 -> rails5 にアップデートしようとしたら、ActionController::InvalidCrossOriginRequest: Security warning: an embedded <script> tag on another site requested protected JavaScript. のエラーが出るようになった

何が起きたのか

rails4 -> 5 へのアップグレードガイドに、 rails5 からは protect_from_forgeryは今後デフォルトでprepend: falseに設定される と書かれているので、今までの挙動を変えたくないということで、

- protect_from_forgery
+ protect_from_forgery prepend: true, with: :exception`

に変更。 with: :exception オプションは prepend: true を設定したときに RuboCop だかに指摘されたので設定。

そうすると、 ajax で部分テンプレートを返す実装をしている ajax/hogehoge というエンドポイントで下記のエラーが発生しているよ、というエラー通知が飛んでくるようになった。

ActionController::InvalidCrossOriginRequest: Security warning: an embedded <script> tag on another site requested protected JavaScript. If you know what you're doing, go ahead and disable forgery protection on this action to permit cross-origin JavaScript

TL;DR

routes.rb 内のエラーが起きているエンドポイントを設定しているルーティングで constraints オプションを設定し、 XMLHttpRequest だけを受け付けるようにしてあげればいい。 ajax/hogehoge みたいなエンドポイントだったら下のような感じ。

namespace 'ajax' do
  get 'hogehoge', constraints: lambda { |req| req.xhr? }
end

問題の再現方法

エラーが起きていた、 ajax/hogehoge にブラウザから直接 ajax/hogehoge.js といった感じでアクセスしてあげると発生。

起こった問題の原因

  • ajax/hogehoge はとあるページからの ajax 通信でアクセスされることを想定したエンドポイントだった。
  • そのエンドポイントに対し、ブラウザから直接アクセスしたことにより発生。
  • rails5 移行の実装を入れる前は、 protect_from_forgery with: :exception のオプションが設定されていなかったため、エラーは起きているものの例外が投げられずスルーされていた。(ということは ここのコメントを見て予想)

対応方針

一番簡単なのは、 rails4 の時と同じ挙動になるように、

protect_from_forgery prepend: true, with: :null_session

に設定してあげる方法。(未検証ですが多分ちゃんと動く)
ただ、 rails5 の推奨は with: :exception らしいので、 このオプションを設定した上でどうにかしたい。

また、今回の問題は ajax 通信を期待しているエンドポイントに対して、非 ajax 通信が来たことにより発生しているので、コントローラレイヤとかではなくルーティングのレイヤでどうにかしたい。

対応内容

routes.rb 内のエラーが起きているエンドポイントを設定しているルーティングで constraints オプションを設定してあげる。 ajax/hogehoge みたいなエンドポイントだったら下のような感じ。

namespace 'ajax' do
  get 'hogehoge', constraints: lambda { |req| req.xhr? }
end

constraints オプションに関しては、 railsガイドに詳細が乗っているのでそちらを参考にしてください。

constraints オプションでは、 受け付けるリクエストに制限を課すことができるので、 lambda を用いて、リクエストが XMLHttpRequest (ajaxのリクエスト)であるかどうかを検証している感じです。

この設定により、 ajax/hogehoge に ajax でリクエストをする場合は、 Ajax#Hogehoge コントローラーが呼び出され正常にレスポンスが返されるが、 ブラウザから `ajax/hogehoge.js などでアクセスした場合は 404 エラーを返すようになる。

終わりに

rails6も出ていることですし、あんまり今回のような問題で詰まることもないかと思いますが、せっかくだったので記事にしてみました。個人的には、 rails のコードを読んだりするいい機会になったので結構楽しかったです。

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

【Rails6】generateコマンドでyarnエラー

内容

  • Rails6でgenerateコマンドを打ったらyarnエラーが発生したのでその対処法
  • 対処法と言ってもエラー内容に従って解決しただけです(もし原因ご存じの方がいたらご教示ください)

環境

  • macOS Mojave 10.14.6
  • VirtualBox 6.0.10
  • Vagrant 2.2.5
  • centos/7 (virtualbox, 1905.1)
  • rbenv 1.1.2-4-g577f046
  • ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux]
  • Rails 6.0.0

エラー内容

# エラーを発生させたコマンド
bin/rails generate controller {samples}



# エラー内容
/usr/share/yarn/lib/cli.js:46099
  let {
      ^

SyntaxError: Unexpected token {
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:373:25)
    at Object.Module._extensions..js (module.js:416:10)
    at Module.load (module.js:343:32)
    at Function.Module._load (module.js:300:12)
    at Module.require (module.js:353:17)
    at require (internal/module.js:12:17)
    at Object.<anonymous> (/usr/share/yarn/bin/yarn.js:24:13)
    at Module._compile (module.js:409:26)
    at Object.Module._extensions..js (module.js:416:10)


========================================
  Your Yarn packages are out of date!
  Please run `yarn install --check-files` to update.
========================================


To disable this check, please change `check_yarn_integrity`
to `false` in your webpacker config file (config/webpacker.yml).

やったこと

Please run 'yarn install --check-files' to update.とあったので次のコマンドを実行

$ yarn install --check-files


# 結果
/usr/share/yarn/lib/cli.js:46099
  let {
      ^

SyntaxError: Unexpected token {
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:373:25)
    at Object.Module._extensions..js (module.js:416:10)
    at Module.load (module.js:343:32)
    at Function.Module._load (module.js:300:12)
    at Module.require (module.js:353:17)
    at require (internal/module.js:12:17)
    at Object.<anonymous> (/usr/share/yarn/bin/yarn.js:24:13)
    at Module._compile (module.js:409:26)
    at Object.Module._extensions..js (module.js:416:10)

そもそもyarn -vなどのyarnコマンド全てで同じ結果でした。

解決

please change 'check_yarn_integrity'
to 'false' in your webpacker config file (config/webpacker.yml)

とあったので、config/webpacker.ymlを編集して該当項目をtrue->falseへ変更

config/webpacker.yml
# Note: You must restart bin/webpack-dev-server for changes to take effect
...略

development:
  <<: *default
  compile: true

  # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules
-  check_yarn_integrity: true
+  check_yarn_integrity: false

...略

やってみる

$ bin/rails generate controller samples
Running via Spring preloader in process 25275
      create  app/controllers/samples_controller.rb
      invoke  erb
      create    app/views/samples
      invoke  test_unit
      create    test/controllers/samples_controller_test.rb
      invoke  helper
      create    app/helpers/samples_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/samples.scss

generateコマンドは無事通りましたがyarnコマンドは使えないままです。
原因ご存じの方はご教示ください。

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

Ruby on Railsの基本的な仕組みってどうなってるの?(ルーティング、コントローラー、ビュー)

?? Ruby on Railsの基本的な仕組みってどうなってるの ??

◯ Ruby on Railsのやり始めるとき、大抵の人がビビる! ◯

Ruby on Railsを始めようとするとき、まずはインストールしてみると・・・ビビりました? ビビりますよね!?
スクリーンショット 2019-10-20 14.54.47.png

私はもともとビビりなので、めちゃくちゃビビりました・・・

たくさんのフォルダ(ディレクトリ)が並んでいて何がなんのか全くわかりませんよね。

そこで、とにかく最初に紐解くべき事(1)が今回のテーマです。


Ruby on Railsをやっている方でしたら、Rubyはもう学習されているはずです。

1、コードを書く

2、ブラウザに結果を表示させる

HTMLやRubyでは、コードを書いたら即、表示を作ることができましたが、
Ruby on Railsでは、この基本的な作業を実行するためには、次の「3つ」を理解する必要があります。

1、 ルーティング   (routes)
2、 コントローラー   (controller)
3、 ビュー      (view)   →  HTML,Rubyで学んできた部分

1、 まず、ブラウザを開く際には、そのページのアドレスが必要ですね。
  「ルーティング」ではそのアドレスを指定します。

2、 次に、1で指定したページのバックグラウンドで作業や設定などの複雑な計算をする部分(アクションと言います)を
  「コントローラー」で指定します。

3、 最後に、「ビュー」のファイルにHTMLやRubyで実際に表示されるコードを書きます。

ということで、ざっくり手間が「1」と「2」の二つ分増えるわけです。

Ruby on Rails は「ルーティング」や「コントローラー」、「ビュー」などのフォルダ(ディレクトリ)が
それぞれ繋がっていて、それぞれが反映されるようになっているので、離れているフォルダ(ディレクトリ)同士でも
勝手に連携してくれます。

スクリーンショット 2019-10-20 14.50.21.png

どこに何があるのか最初はわかりずらいですが、まずはこの三つの場所と上記のような関係を頭に入れましょう!

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

Ruby on Railsの基本、基礎的な仕組みが分からない!(ルーティング、コントローラー、ビュー編)

?? Ruby on Railsの基本的な仕組みってどうなってるの ??

◯ Ruby on Railsのやり始めるとき、大抵の人がビビる! ◯

Ruby on Railsを始めようとするとき、まずはインストールしてみると・・・ビビりました? ビビりますよね!?
スクリーンショット 2019-10-20 14.54.47.png

私はもともとビビりなので、めちゃくちゃビビりました・・・

たくさんのフォルダ(ディレクトリ)が並んでいて何がなんのか全くわかりませんよね。

そこで、とにかく最初に紐解くべき事(1)が今回のテーマです。


Ruby on Railsをやっている方でしたら、Rubyはもう学習されているはずです。

1、コードを書く

2、ブラウザに結果を表示させる

HTMLやRubyでは、コードを書いたら即、表示を作ることができましたが、
Ruby on Railsでは、この基本的な作業を実行するためには、次の「3つ」を理解する必要があります。

1、 ルーティング   (routes)
2、 コントローラー   (controller)
3、 ビュー      (view)   →  HTML,Rubyで学んできた部分

1、 まず、ブラウザを開く際には、そのページのアドレスが必要ですね。
  「ルーティング」はそのアドレスを指定する所です。

2、 次に、1で指定したページのバックグラウンドで作業や設定などの複雑な計算をする部分(アクションと言います)を
  「コントローラー」で指定します。

3、 最後に、「ビュー」のファイルにHTMLやRubyで実際に表示されるコードを書きます。

ということで、ざっくり手間が「1」と「2」の二つ分増えるわけです。
1ページだけのホームページなら、ルーティングやコントローラーは不要かもしれませんが、
複数のページが連携するようになってくると、1と2の作業が必要になってくるので、まずはここで理解に苦しみます。

Ruby on Rails は「ルーティング」や「コントローラー」、「ビュー」などのフォルダ(ディレクトリ)が
それぞれ繋がっていて、それぞれが反映されるようになっているので、離れているフォルダ(ディレクトリ)同士でも勝手に連携してくれます。

スクリーンショット 2019-10-20 14.50.21.png

一つのホームページやシステムを作るのに欠かせない最低限の部分ですので、
どこに何があるのか最初はわかりずらいですが、まずはこの三つの場所と上記のような関係を頭に入れましょう!

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

〜ダレワカ〜Ruby on Railsの基本、基礎的な仕組みが分からない!(ルーティング、コントローラー、ビュー編)

?? Ruby on Railsの基本的な仕組みってどうなってるの ??

◯ Ruby on Railsのやり始めるとき、大抵の人がビビる! ◯

こんにちは。誰でも分かるRuby on Rails攻略 〜ダレワカ〜 のコムリンです。

Ruby on Railsを始めようとするとき、まずはインストールしてみると・・・ビビりました? ビビりますよね!?
スクリーンショット 2019-10-20 14.54.47.png

私はもともとビビりなので、めちゃくちゃビビりました・・・

たくさんのフォルダ(ディレクトリ)が並んでいて何がなんのか全くわかりませんよね。

そこで、とにかく最初に紐解くべき事(1)が今回のテーマです。


Ruby on Railsをやっている方でしたら、Rubyはもう学習されているはずです。

1、コードを書く

2、ブラウザに結果を表示させる

HTMLやRubyでは、コードを書いたら即、表示を作ることができましたが、
Ruby on Railsでは、この基本的な作業を実行するためには、次の「3つ」を理解する必要があります。

1、 ルーティング   (routes)
2、 コントローラー   (controller)
3、 ビュー      (view)   →  HTML,Rubyで学んできた部分

1、 まず、ブラウザを開く際には、そのページのアドレスが必要ですね。
  「ルーティング」はそのアドレスを指定する所です。

2、 次に、1で指定したページのバックグラウンドで作業や設定などの複雑な計算をする部分(アクションと言います)を
  「コントローラー」で指定します。

3、 最後に、「ビュー」のファイルにHTMLやRubyで実際に表示されるコードを書きます。

ということで、ざっくり手間が「1」と「2」の二つ分増えるわけです。
1ページだけのホームページなら、ルーティングやコントローラーは不要かもしれませんが、そんなものは無料のツールで誰でも簡単に作れてしまいます。

通常、企業がお金をかけて(私たちが仕事として請け負うレベルのもの)作るものは、複数のページが色々と連携するように作られることがほとんどで、1と2の作業が必要になってくるので、初心者はまずはここで理解に苦しみます。

Ruby on Rails は「ルーティング」や「コントローラー」、「ビュー」などのフォルダ(ディレクトリ)が
それぞれ繋がっていて、それぞれが反映されるようになっているので、離れているフォルダ(ディレクトリ)同士でも勝手に連携してくれます。

スクリーンショット 2019-10-20 14.50.21.png

一つのホームページやシステムを作るのに欠かせない最低限の部分ですので、
どこに何があるのか最初はわかりずらいですが、まずはこの三つの場所と上記のような関係を頭に入れましょう!

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

Ruby on Railsにスタイルを反映させる手順

概要

Webアプリのscssファイルを更新しても本番環境に変更が反映されませんでした。
httpdリスタートしたりpassengerをリスタートしたりしま
したが変わらず。。。

解決方法

何が問題なのかと調べた結果、こちらを参考にしました。

環境

ruby 2.3.3
rails 5.1.7
SAKURA VPS
CentOS 6

手順

$ rm -rf public/assets
$ rm -rf tmp/cache
$ bundle exec rake assets:precompile
$ sudo service httpd restart 

1,2行目:実行するプロジェクトのassets,cacheフォルダを削除
3行目:プリコンパイルを行う
4行目:httpdをリスタート

以上の流れで、変更したscssファイルが反映されました。
私としては、アプリで利用するファイルの一部が残っていたので削除してもう一度作り直す、という理解になっています。

何か違う!というのがあれば教えてください!!

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

Rails チュートリアル 第6章 まとめ

フォームで実現したいこと
永続的なデータを保存したい、一時的な変数ではなく
そのためにはDBを使う必要がある
DBを扱うには別の言語を学ぶ必要があるが、RailsのActiveRecordを使えば、変数のように保存できる

ユーザー用のデータモデルを作成(以下2つのファイルが自動生成)

rails g model User name:string email:string 

DBの変更を反映

rails db:migrate

をすると、db/にdevelopment.sqlite3というバイナリファイルが作成される
ここにDBが保存される

migrationファイルの中身を確認(自動生成1)

db/migrate/2019-----_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.1] #クラスの継承
  def change #changeメソッドの定義
    create_table :users do |t| #create_tableというブロック付きメソッドの定義
      t.string :name #t.stringのnameというシンボル
      t.string :email

      t.timestamps
    end
  end
end

モデル(自動生成2)

app/model/user.rb

usersのtableが見つからないというエラーが出た時は、もう一度rails db:migrate

app/model/user.rbで、ApplicationRecordを継承していることで、Userクラスに何ができるようになったのかがポイント
・Rail console上で、User.newを使いnameとemailを作成する。普通なら、consoleを閉じると、データは消えるはずだが、u(変数名).saveとするとDBに保存されるため、次にconsoleを立ち上げても、Userのデータが残っている
・継承していることで、DBに保存したり(save)、DBから情報を取得したり(all)、情報を検索したり(find_by)、情報を更新する(update_attributes)ことができる
・普通更新する時はu[:name] = "---"だが、u.name = "^^^"のように代入することもできる
・User.createは作成と保存を同時に行える。戻り値を生成しているので、変数に入れることもできる
foo = User.create(---)
・Validationができるようになる


Validation

テスト(test/models/user_test.rb)と修正(app/models/user.rb)を繰り返すTDD

app/test/models/user_test.rb
# setupはtest do-endの直前で呼ばれる
 def setup
    @user = User.new(
      name: "Example User",
      email: "user@example.com")
  end
# サンプルデータの形式が適切であるかのチェック
# valid?はvalidationが全て通るべきという意味
  test "should be valid" do
    assert @user.valid?
  end
# nameが空文字のユーザーはvalidationではじけているべき(assert_not)であるというチェック
  test "name should be present" do
    @user.name = "     "
    assert_not @user.valid?
  end
  test "email should be present" do
    @user.email = "     "
    assert_not @user.valid?
  end
# nameが51字以上の場合にはvaliではじけているべき
  test "name should not be too long" do
    @user.name = "a" * 51
    assert_not @user.valid?
  end

  test "email should not be too long" do
    @user.email = "a" * 244 + "@example.com"
    assert_not @user.valid?
  end
  #成功しているemailのフォーマットは全て通るべき
   test "email validation should accept valid addresses" do
  #成功しているemailのフォーマットを配列で作成
    valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org
                         first.last@foo.jp alice+bob@baz.cn]
#配列からひとつずつ取り出す
    valid_addresses.each do |valid_address|
      @user.email = valid_address #変数に代入
#validationを通っているかチェック、さらに失敗したならどのemailで失敗したのか表示
      assert @user.valid?, "#{valid_address.inspect} should be valid"
    end
#失敗しているemailのformatは全てはじけているべきである
    test "email validation should reject invalid addresses" do
    invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.
                           foo@bar_baz.com foo@bar+baz.com]
    invalid_addresses.each do |invalid_address|
      @user.email = invalid_address
      assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
    end
  #一意性の確認
  test "email addresses should be unique" do
  #@user.dupで@userを複製する
    duplicate_user = @user.dup
    @user.save #@userを保存
  #@userが保存されているので、同じデータのduplicate_userは保存されずにはじかれるべき
    assert_not duplicate_user.valid?
  end

一意性の検証
railsだけではなくDBでも検証する仕組みを作る(ほぼ同時のユーザー登録に対処)

すでにあるモデルにカラムを追加したい

rails generate migration add_index_to_users_email 
#DBにemailカラムが一意であることを確認させる

Password
passwordはハッシュ関数をかけて、ハッシュ値に変換してからDBに保存
DBにハッシュ値を保存するための場所を準備

rails generate migration add_password_digest_to_users password_digest:string
rails db:migrate

ハッシュ関数の用意

1.gem 'bcrypt', '3.1.12' #gemインストール
2.has_secure_password を追加

has_secure_passwordはpasswordとpassword_confirmationを最初から用意してくれている
password,confのカラムはないが、アクセスすることはできる(u.password)
代入できるが、セーブはしない、ハッシュ値をかけたダイジェストだけが保存されている

認証の方法
u.authenticate("---")を使うと、引数の値と本当のパスワードが正しいかチェックし、一致したらユーザーオブジェクトを返し、一致しなかったらfalseを返してくれる

herokuにデプロイした後、dbを使ったので、heroku run

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

Railsを5.0から6.0にアップデートしたらコンソールで日本語が使えなくなった問題の解決

コンソールで日本語を入力したらエラーで強制終了されてしまう

Railsプロジェクトのバージョンを5.0→6.0に上げたら、Rails cで立ち上がるコンソールで日本語を入力すると、
"\xE2" from ASCII-8BIT to UTF-8 (Encoding::UndefinedConversionError)
というエラーが発生し、強制終了するようになった。

解決策

Gemfileのdevelopmentグループにあるgem 'rb-readline'を削除し、$ bundleを実行する。
これでエラーが発生しなくなりました。
なぜrb-readlineが問題だったのかわかる方、コメントお待ちしています、、、

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

#Rails + #rspec の change 記法で model のインスタンスを検証する時、うまくいかないときは reload を挟めば良いじゃない

こんなノリ

subject { change_name! }
let!(:user) { create :user }

it { expect { subject }.to change { user.reload.name }.from('Alice').to('Bob')  }

以前は次のようににいみにくい reload を subject の中に挟んでしまっていたが、メソッドチェーンの中に relaod を仕込むほうが良いね

subject { change_name! }
let!(:user) { create :user }

it { expect { subject; user.reload }.to change { user }.from('Alice').to('Bob')  }

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2608

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

macOS 10.15 Catalinaで、"fatal error: 'stdio.h' file not found"が出てしまう!

初めてRailsとやらの環境構築をしていた際、gem install pgなどすると以下のようなエラーが出ていて困り果てていました。

/Users/Rascal/.rbenv/versions/2.6.4/lib/ruby/gems/2.6.0/extensions/x86_64-darwin-18/2.6.0-static/pg-1.1.4/mkmf.log
find_executable: checking for pg_config... -------------------- yes

--------------------

"clang -o conftest -I/Users/Rascal/.rbenv/versions/2.6.4/include/ruby-2.6.0/x86_64-darwin18 -I/Users/Rascal/.rbenv/versions/2.6.4/include/ruby-2.6.0/ruby/backward -I/Users/Rascal/.rbenv/versions/2.6.4/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/Users/Rascal/.rbenv/versions/2.6.4/include  -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT   -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens  -pipe conftest.c  -L. -L/Users/Rascal/.rbenv/versions/2.6.4/lib -L/usr/local/lib -L. -L/Users/Rascal/.rbenv/versions/2.6.4/lib  -fstack-protector-strong -L/usr/local/opt/openssl/lib -L/usr/local/lib -L/usr/local/Cellar/llvm/8.0.1/lib/clang/8.0.1/lib     -lruby.2.6-static -framework Security -framework Foundation -lpthread -lgmp -ldl -lobjc   "
In file included from conftest.c:1:
In file included from /Users/Rascal/.rbenv/versions/2.6.4/include/ruby-2.6.0/ruby.h:33:
In file included from /Users/Rascal/.rbenv/versions/2.6.4/include/ruby-2.6.0/ruby/ruby.h:29:
In file included from /Users/Rascal/.rbenv/versions/2.6.4/include/ruby-2.6.0/ruby/defines.h:123:
/Library/Developer/CommandLineTools/usr/include/c++/v1/stdio.h:108:15: fatal error: 'stdio.h' file not found
#include_next <stdio.h>
              ^~~~~~~~~
1 error generated.
checked program was:
/* begin */
1: #include "ruby.h"
2: 
3: int main(int argc, char **argv)
4: {
5:   return 0;
6: }
/* end */

/Users/Rascal/.rbenv/versions/2.6.4/lib/ruby/gems/2.6.0/extensions/x86_64-darwin-18/2.6.0-static/pg-1.1.4/mkmf.log (END)

stdio.hがないですと…?

Mojave(10.14)のときは、open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkgで何とかなっていたのですが今回からそのようなパッケージもなく、以下のパスを参照するようにしても上手く行きませんでした。
export CPATH=/Library/Developer/CommandLineTools/usr/include/c++/v1

結論としては、以下を~/.bash_profileなり~/.bashrcに追記することで問題は解消されました。
export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"

この問題にぶつかるまでは、Dockerを使っての環境構築をしていたので自身のマシンで苦労するとは思いませんでした。

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

ajaxを活用した非同期通信について

ajaxを使った非同期通信

10月19日現在、ajaxによる非同期通信を学習しているところですが、なかなか理解が追いつかないので自分なりにJavaScriptの復習をかねて中身を確認していきたいと思います。かなり自分向けに書いていますので、語弊がある部分もあるかもしれません。

今回は、ブログに対して投稿されるコメントが非同期通信にて行われるようにしていきます。

イベントの発火

まずは動作のきっかけ部分を記述します。きっかけはコメントを入力し、投稿ボタンをクリックした時です。

comment.js
$(function(){
  $('#newcomment').on('submit', function(e){
    e.preventDefault();
    var formdata = new FormData(this);
  })
})
  • $(function(){
    jQuery(JavaScript)のコードが読み込まれる際に、まだHTMLが読み込まれていないとエラーが発生します。1行目、$(function(){から書き始めることで、HTMLのページ情報が読み込み完了してからコードが実行されます。
    ちなみにこのfunctionメソッド(書き方あっているのかな・・・)では引数は不要ですが、引数がない場合でも()括弧を書く必要があります。

  • $('#newcomment')
    jQueryでどのボタンがトリガーになるかは、HTMLタブ内のclassもしくはidから探します。
    2行目の$('#newcomment').はHTML内のid(newcomment)を探します。#はidを探す書き方です。
    $('.〜')といったように、.を使うとclass要素から探されます。

  • .on('submit', function(e){
    イベントsubmitが起こった際に以下の関数を実施する、という部分。
    ちなみにfunction(e)のeはイベントの頭文字。
    次の行のe.preventDefaultは実行されるべきイベントをキャンセルさせます。

  • var 〜〜 = 〇〇
    変数〜〜〇〇を代入します。
    Rubyだと頭のvarなんて要りませんでしたが、他のプログラム言語では必要な場合も多いようです。

  • new FormData(this)
    フォームの情報を取得します。引数がthisの場合、イベントが発生した部分、今回はコメント投稿の部分からの取得になります。

コメントの保存

非同期通信の中で、入力したコメントがcreateメソッドで保存されるようにします。

comment.js
$(function(){
  $('#newcomment').on('submit', function(e){
    e.preventDefault();
    var formdata = new FormData(this);
    $.ajax({
      url: $(this).attr('action'),
      type: "POST",
      data: formData,
      dataType: 'json',
      proseccData: false,
      contentType: false
    })
  })
})
  • $.ajax({
    ajaxで非同期通信をする際のオプションを記述します。
    1.type HTTP通信の種類を記述。GETもしくはPOST。
    2.url リクエスト送信先のURLを記述。
    3.data 送信する値を記述。
    4.dataType データの種類を記述。今は基本的にjson
    5.processData
    6.contentType データのファイル形式を指定。基本false

  • attr
    要素が持つ属性から、指定した属性の値を返します。
    今回では投稿ボタンを含むformタグの中からaction要素を探し、その値となるURLを取得するようにします。

以上の記述で、ajaxで扱われるデータは
「json形式でテキストボックス内の文字をPOSTでコメント投稿のアドレスへ送信」
となります。すなわちコメントが投稿される形となります。

返ってくる結果を受け取る

.doneメソッドで非同期通信の結果を受け取ります。

comment.js
    .done(function(data){
      $('.comments').append(〜〜)
      $('.textbox').val('')
    })
    .fail(function(){
      alert('error')
    })

.doneメソッドで、受け取った結果を元に処理を行います。
commentsクラスの後ろに()内の記述を追加します。詳細は省略。
textboxクラスの値を空にします。今回はフォームボックスの中をクリアします。
.failメソッドは、エラーが発生した時に表示させる処理です。
alertメソッドはポップアップを表示させます。

こんな感じなんでしょうか。
まだなんとなく程度の理解ですので、もっと精進していきます。

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

Ajaxを活用した非同期通信について

ajaxを使った非同期通信

10月19日現在、ajaxによる非同期通信を学習しているところですが、なかなか理解が追いつかないので自分なりにJavaScriptの復習をかねて中身を確認していきたいと思います。かなり自分向けに書いていますので、語弊がある部分もあるかもしれません。

今回は、ブログに対して投稿されるコメントが非同期通信にて行われるようにしていきます。

イベントの発火

まずは動作のきっかけ部分を記述します。きっかけはコメントを入力し、投稿ボタンをクリックした時です。

comment.js
$(function(){
  $('#newcomment').on('submit', function(e){
    e.preventDefault();
    var formdata = new FormData(this);
  })
})
  • $(function(){
    jQuery(JavaScript)のコードが読み込まれる際に、まだHTMLが読み込まれていないとエラーが発生します。1行目、$(function(){から書き始めることで、HTMLのページ情報が読み込み完了してからコードが実行されます。
    ちなみにこのfunctionメソッド(書き方あっているのかな・・・)では引数は不要ですが、引数がない場合でも()括弧を書く必要があります。

  • $('#newcomment')
    jQueryでどのボタンがトリガーになるかは、HTMLタブ内のclassもしくはidから探します。
    2行目の$('#newcomment').はHTML内のid(newcomment)を探します。#はidを探す書き方です。
    $('.〜')といったように、.を使うとclass要素から探されます。

  • .on('submit', function(e){
    イベントsubmitが起こった際に以下の関数を実施する、という部分。
    ちなみにfunction(e)のeはイベントの頭文字。
    次の行のe.preventDefaultは実行されるべきイベントをキャンセルさせます。

  • var 〜〜 = 〇〇
    変数〜〜〇〇を代入します。
    Rubyだと頭のvarなんて要りませんでしたが、他のプログラム言語では必要な場合も多いようです。

  • new FormData(this)
    フォームの情報を取得します。引数がthisの場合、イベントが発生した部分、今回はコメント投稿の部分からの取得になります。

コメントの保存

非同期通信の中で、入力したコメントがcreateメソッドで保存されるようにします。

comment.js
$(function(){
  $('#newcomment').on('submit', function(e){
    e.preventDefault();
    var formdata = new FormData(this);
    $.ajax({
      url: $(this).attr('action'),
      type: "POST",
      data: formData,
      dataType: 'json',
      proseccData: false,
      contentType: false
    })
  })
})
  • $.ajax({
    ajaxで非同期通信をする際のオプションを記述します。
    1.type HTTP通信の種類を記述。GETもしくはPOST。
    2.url リクエスト送信先のURLを記述。
    3.data 送信する値を記述。
    4.dataType データの種類を記述。今は基本的にjson
    5.processData
    6.contentType データのファイル形式を指定。基本false

  • attr
    要素が持つ属性から、指定した属性の値を返します。
    今回では投稿ボタンを含むformタグの中からaction要素を探し、その値となるURLを取得するようにします。

以上の記述で、ajaxで扱われるデータは
「json形式でテキストボックス内の文字をPOSTでコメント投稿のアドレスへ送信」
となります。すなわちコメントが投稿される形となります。

返ってくる結果を受け取る

.doneメソッドで非同期通信の結果を受け取ります。

comment.js
    .done(function(data){
      $('.comments').append(〜〜)
      $('.textbox').val('')
    })
    .fail(function(){
      alert('error')
    })

.doneメソッドで、受け取った結果を元に処理を行います。
commentsクラスの後ろに()内の記述を追加します。詳細は省略。
textboxクラスの値を空にします。今回はフォームボックスの中をクリアします。
.failメソッドは、エラーが発生した時に表示させる処理です。
alertメソッドはポップアップを表示させます。

こんな感じなんでしょうか。
まだなんとなく程度の理解ですので、もっと精進していきます。

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