20200414のRailsに関する記事は20件です。

HTML、CSSで忘れそうだけど押さえておきたいこと

CSS

HTMLと別のファイルで書くのが基本。

大きさの単位

絶対的な大きさとしてpxが主流。しかしスマホの普及により、rem(html要素のフォントを1とした時の相対的な単位)を使用される

色は#FFFFFFやrgb(255,255,255)などの方法で表される。
カラーコードでぐぐるとわかる

idとclass

idは一度しか定義できず、CSSで読みだすときは#をつける。
classは複数回定義できるが、CSSで読みだすときは.をつける。またclassは複数所属可能なので、class = aaa bbbとしてやるとaaaとbbbに所属できる。

優先度

親要素でCSSの内容を記述し、子要素でもCSSの内容を記述したとき子要素の内容が優先される
同じ要素のidのCSSの内容とclassのCSSの内容ならば、idのCSSの内容が優先される

詳しくはCSSセレクタの優先度に関してググると良い

ボックスモデル

HTMLの要素はpadding border marginで囲まれている。
HTMLの要素自体の幅、高さをwidth、heightで表す

要素.jpg

またその要素に
box-sizing: border-box;
と指定すると、widthやheightをmarignまで含んだ時の大きさに設定することになる

インラインとブロック

ブロック要素

ブロックは画面の横幅全体を使い、次の要素は改行された状態になる。
例 h1~h6 p form table ul ol dl li div

text-alignやmarginをいじるやインラインブロックへの変更をすることでブロック要素は配置設定に使用できる(インライン要素は配置設定をしないのは基本。)

インライン要素

インラインは画面の横幅全体を使わず、次の要素は隣にくるようになる。
例 br a img input select textarea strong span

上述したがインライン要素は配置設定をしない。

そもそも下記なので配置設定できない。
・大きさ制限できない(width,height)
・marginできない(ホントは左右はできる)
・text-alignできない
・floatできない

またインライン要素の配置を変えたいならば、その要素に親要素を作り、そいつのtext-alignやmarginをいじるやインラインブロックへの変更をすることで配置設定をする。そのためインライン要素から別の要素への変換(display)をする機会はない。

インラインブロック要素

インラインブロックは基本的にブロック要素的な面が強いが、次の要素は隣にくるようになる。ブロック要素だけでは配置設定のバラエティーがなくなるので、ブロック要素からインラインブロック要素に変更することがある。ブロック→インラインブロックのパターンしかない。

重要

以上のことからわかることは
配置変更をしたければ、ブロック要素をいじることで行う。例えば、text-alignやmargin変更、インラインブロックへの変更で横に大きいの並べるとか

text-alignとmargin

私はtext-alignとmargin 0 autoが同じだと思っていた。でも違うのだ。

text-alignはその要素の幅の範囲内(width内)での文字の位置を指定している。つまり文字の位置を指定しているのである。

marginはその要素のmargin(余白)の部分を変更しているので、要素の幅(width)や高さ(height)の範囲には影響を与えない。

要するにtext-alignは要素の中での指定、marginは要素の外側での指定。

marginのあれこれ

同じ方面でmarginがかぶると、marginの相殺(margin同士の足し算ではなく、marginの大きい方を採用する)。marginの相殺と呼ぶ
margin 0 autoで中央ぞろえ
margin-left autoでベタッリ右

要素の真ん中に文字をおきたければ

要素の大きさ(幅や高さ)のwidthやheightで行う。要素の大きさを変えても要素内での文字の位置は変わらない。

要素内での文字の位置を変えたければ、text-align: center;で文字を幅でも高さでも真ん中に置くのかを決める。またline-heightで文字自体の高さを決めれるので、要素のheightとline-heightを同じにすることで、完璧に要素の真ん中にもじが来るようになる。

範囲指定

範囲を指定するためだけの要素

ブロックで範囲を指定するのがdiv(text-alignやmarginでいじって、指定範囲の位置指定)

インラインで範囲をしてするのがspan(特定の文字の色変更とかに使用)

background

backgroundを使用すると背景を変えれる。backgroundでの範囲はその要素のborderまでの範囲なので、余白をつけたければ,marginを使う。またrepeatを指定すると、永遠にその背景ってことになる。

img

imgはインライン要素なので、pで囲って無理やりブロックにして、配置指定をする。

HTML

<>で囲まれている部分を要素idやnameなどは属性という。

h1やpで囲んで記事を書くが、なくても文字は表示される

imgのalt属性は画像が表示されない時に出る文字、最近はSEOに必要な要素にもなっている?

ulは箇条書き(順序なし)、olは箇条書き(順序あり)、dlは説明って感じ(dlの中にdt(用語)、ddが用語の詳細説明)

表に関して。trで行をざっくり作る、trの中にthやtdを入れて行にマスを作っていくイメージ。thは見出し、tdは左寄せ。

bodyの構造。headerとfooterを頭とお尻に。その間の空間をarticleとして、その中に複数のsection(切りの良いところで、次のセクションに次の内容を書く)を置く。最近はSEO的にも重要だとか

form_tagとform_forの違い。モデルに対して操作を行うならば、form_forを使い、そうでないならばform_tagを使用する。詳しくはrailsで学ぶサーバーサイド

またform_tagでmethodを指定するが、postは見えない状態で、paramsの中に入れて運ぶ感じ。getではURLに?でクエリ(条件)を置いて、送信する。詳しくはrailsで学ぶサーバーサイド

<a>は外部のURLにアクセスするための要素
<%= link_to %>はアプリ内部のrootingにアクセスするために作られた関数

viewでの注意

・お作法としてapplication.htmlには統一レイアウトを置き
yeildという場所を書き、そこに全てのviewの内容が反映される。

app/views/layouts/application.html.erb
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag    'application', media: 'all',
                               'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application',
                               'data-turbolinks-track': 'reload' %>
    <%= render 'layouts/shim' %>
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
      <%= render 'layouts/footer' %>
    </div>
  </body>
</html>

同様に全てのviewは、別のviewファイルから読みだして自身のviewに反映させてもいい

<%= render '反映させたいviewの場所' %>
<%= render @microposts %>
#これをするとviewの中のmicroposts/_micropost.html.erbを追加することになる

これをパーシャルを追加するという。他にも

<%= render 'shared/error_messages', object: f.object %>
#これを行うとshared/error_messagesの方で、f.object(つまりf自身、入力内容自身)を変数objectと設定してパーシャリ川で使用できるという事

_error_messages.html.erb
<% if object.errors.any? %> #このようにして使用する
.
.
.

これも同じような意味

this.erb
<%= render partial: "list_footer", locals: {username: @username, listname: @listname} %>

この意味はlist_footerパーシャル(view)の中でthis.erbで定義した@usernameをusername、 @listnameをlistnameとして使用できるという事。しかしloacalsを使わずに、@usernameをそのまま使用することも可能。しかしlocalsを使った方がパーシャルを再利用しやすいため使用される。参考文献は部分テンプレートにlocals:を使って変数を渡す時は partial:もつけないとエラー

・erb(rubyの埋め込まれたHTML)
erbでは<% 中身 %>で中身の処理をして、<%= 中身 %>では中身の処理をして結果をHTMLに出す。

app/views/static_pages/home.html.erb
<% provide(:title, "Home") %> #ここで:titleにHomeを代入
<!DOCTYPE html>
<html>
  <head> #ここで読み出して、HTMLに出す
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
  </head>

provideもyieldも関数で、変数に値を入れる、読み出すの処理をしてくれているということ。

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

【Rails】商品出品時にカテゴリテーブルのIDと紐づける方法

はじめに

某プログラミングスクールで、フリマアプリのクローンサイトを作成中につまづきました。
内容としては、出品のタイミングでカテゴリを紐づけて投稿したいけど、そのidがうまく機能しないといったもの。困ったもんだ。

やりたいこと

商品を登録するitemテーブルに、categoryテーブルの紐付けを行いたい。
categoryテーブルはancestryというgemを使って作成しています。
ざっくり言うと、categoryテーブルの中で、親カテゴリ、子カテゴリ孫カテゴリが管理されてます。

テーブルの構成としてはこんな感じ。
ちょっとわかりにくいかも

nameカラム ancestryカラム
親カテゴリ メンズ、レディースなど null
子カテゴリ アウター、ボトムスなど 1、2など
孫カテゴリ Tシャツ、スラックスなど 1/y、2/yなど

ancestryカラムがnullのものが親カテゴリ
ancestryカラムが1だったり2だったりするものが子カテゴリ
ancestryカラム が1/1だったり、1/2だったり、2/1だったり、2/2だったりするのが孫カテゴリ
こんな風に管理しています。

現状

非同期通信でカテゴリーの選択肢は表示できている。
けど・・・送られてきたデータみたらなんかitemのハッシュからcategory_idがハブられてるじゃないのっ!!
おめぇの席ねぇから。状態です。確かに、これじゃ登録できない。
スクリーンショット 2020-04-14 19.08.22.png

きっとこの子がしっかり皆に受け入れられればうまくいくはず。

 解決前の実装

解決する前の状態です。
商品登録画面で、親カテゴリを選択すると、子カテゴリのプルダウンを表示。
さらに、子カテゴリを選択すると、孫カテゴリのプルダウンが表示されるように非同期通信で対応しています。
ここまで頑張ったんだし良い感じに登録してくれないものかね( ꇐ₃ꇐ )

解決前のソースコード

itemコントローラー

結論、ここに問題はなかったです。

item_controller.rb
#〜〜〜省略〜〜〜
  def create
    @item = Item.new(item_params)
    if @item.save!
      # 商品の投稿に成功したらindexに飛ばす処理
      redirect_to root_path
    else
      # 商品の投稿に失敗したらnewアクションを再度実行new.html.hamlを表示
      render :new
    end
  end
#〜〜〜中略〜〜〜
  # 親カテゴリーが選択された後に動くアクション
  def get_category_children
    #選択された親カテゴリーに紐付く子カテゴリーの配列を取得
    @category_children = Category.find_by(name: "#{params[:parent_name]}", ancestry: nil).children
  end

  # 子カテゴリーが選択された後に動くアクション。 ajaxからハッシュで子要素のIDを受け取る{child_id: childId}
  def get_category_grandchildren
    #選択された子カテゴリーに紐付く孫カテゴリーの配列を取得
    @category_grandchildren = Category.find("#{params[:child_id]}").children
    binding.pry
  end

  private

  #プライベートメソッドにしたいので、private配下に記述
  def item_params
    binding.pry
    params.require(:item).permit(:product_name, :price, :condition,:description, :delivery_fee, :shipping_origin, :days_to_ship,:buyer_id, images_attributes: [:image]).merge(user_id: current_user.id, seller_id: current_user.id)
  end
end

ソースコード上に、binding.pryが書かれているあたり、すごい検証しまくった形跡が残ってますね。
恥ずかしい(笑

jsファイル

結論、この子がいじめっこの犯人でした。

category.js
$(document).ready(function() {

  // カテゴリーセレクトボックスのオプションを作成
  function appendOption(category){
    var html = `<option value="${category.name}" data-category="${category.id}">${category.name}</option>`;
    return html;
  }

  // 子カテゴリーの表示作成
  function appendChidrenBox(insertHTML){
    var childSelectHtml = '';
    childSelectHtml = `<div id="children_wrapper">
                        <select class= 'SellPage__Information__Box__Inner__Form', id= 'child_category' name="category_id">
                          <option value="---" data-category="---">---</option>
                          ${insertHTML}
                      </div>`;
    $('#PullDownCategory').append(childSelectHtml);
  }

    // 孫カテゴリーの表示作成
    function appendGrandchidrenBox(insertHTML){
      var grandchildSelectHtml = '';
      grandchildSelectHtml = `<div id="grandchildren_wrapper">
                          <select class= 'SellPage__Information__Box__Inner__Form', id= 'grandchild_category' name="category_id">
                            <option value="---" data-category="---">---</option>
                            ${insertHTML}
                        </div>`;
      $('#PullDownCategory').append(grandchildSelectHtml);
    }

    $('#parent_category').on('change', function(){
    // 変数"parentCategory"に、プルダウンで選択した値を代入
    var parentCategory = document.getElementById('parent_category').value;
    if(parentCategory != "---"){
      $.ajax({
        url: 'get_category_children',
        type: 'GET',
        data: { parent_name: parentCategory },
        dataType: 'json'
      })
      .done(function(children){
        $('#children_wrapper').remove(); //親が変更された時、子以下を削除するする
        $('#grandchildren_wrapper').remove();
        var insertHTML = '';
        children.forEach(function(child){
          insertHTML += appendOption(child);
        });
        appendChidrenBox(insertHTML);
      })
      .fail(function(){
        alert('カテゴリー取得に失敗しました');
      })
    }else{
      $('#children_wrapper').remove(); //親カテゴリーが初期値になった時、子以下を削除するする
      $('#grandchildren_wrapper').remove();
    }
  });

  // 子カテゴリー選択後のイベント
  $('#PullDownCategory').on('change', '#child_category', function(){
    // カテゴリーの子要素に紐づくIDを取得して、そのIDに紐づく孫要素を取得する。
    // option:selected を指定する事で、プルダウンで選択したものの情報を取得できる事になる。
    var childId = $('#child_category option:selected').data('category');
    if (childId != "---"){
      // 自身で作成したget_category_grandchildrenのルーティングへ飛ばす。その際、プルダウンで選択されている子要素のIDも渡す。
      $.ajax({
        url: 'get_category_grandchildren',
        type: 'GET',
        data: { child_id: childId },
        dataType: 'json'
      })
      .done(function(grandchildren){
        if (grandchildren.length != 0) {
          $('#grandchildren_wrapper').remove(); //子が変更された時、孫以下を削除するする
          var insertHTML = '';
          grandchildren.forEach(function(grandchild){
            insertHTML += appendOption(grandchild);
          });
          appendGrandchidrenBox(insertHTML);
        }
      })
      .fail(function(){
        alert('カテゴリー取得に失敗しました');
      })
    }else{
      $('#grandchildren_wrapper').remove();
    }
  });
});

Viewファイル

この子もなんも悪い子ではなかったです。
最初から疑ってなかったよ。うん。

new.html.haml
-# ~~~省略~~~
.SellPage__Information__Box
 .SellPage__Information__Box__About
  商品の詳細
 #PullDownCategory.SellPage__Information__Box__Inner
  .SellPage__Information__Box__Inner__SellText
   カテゴリー
  .SellPage__Information__Box__Inner__SellBox
   必須
  = f.select :category, @category_parent_array, {}, {class: 'SellPage__Information__Box__Inner__Form', id: 'parent_category'}

data-category="${category.id}を付与しているので、各選択肢のidは付与できている状態。
このdata-category="${category.id}の値をどうにかitem={}の仲間入りをさせたい。
そうすればcategory_id君はいじめから開放されるはず・・・!

解決&方法

やっぱり、jsが悪さをしていました
ソースコード書いたのお前じゃん。とか言われたら言い返せませんが、jsが悪さをしてたんです。

修正後.js
  // 子カテゴリーの表示作成
  function appendChidrenBox(insertHTML){
    var childSelectHtml = '';
    childSelectHtml = `<div id="children_wrapper">
                        <select class= 'SellPage__Information__Box__Inner__Form', id= 'child_category' name="item[category_id]">
                          <option value="---" data-category="---">---</option>
                          ${insertHTML}
                      </div>`;
    $('#PullDownCategory').append(childSelectHtml);
  }

  // 孫カテゴリーの表示作成
  function appendGrandchidrenBox(insertHTML){
    var grandchildSelectHtml = '';
    grandchildSelectHtml = `<div id="grandchildren_wrapper">
                        <select class= 'SellPage__Information__Box__Inner__Form', id= 'grandchild_category' name="item[category_id]">
                          <option value="---" data-category="---">---</option>
                          ${insertHTML}
                      </div>`;
    $('#PullDownCategory').append(grandchildSelectHtml);
  }

5行目、16行目の記述を変えています。
変更Before/Afterはこんな感じ↓

比較.js
// After
<select class= 'SellPage__Information__Box__Inner__Form', id= 'child_category' name="item[category_id]">
// Before
<select class= 'SellPage__Information__Box__Inner__Form', id= 'grandchild_category' name="category_id">

nameの記述を変更しています。
正直理解仕切れているか?というとそうではないです。
item[category_id]とすることでインスタンス変数@itemの要素の一つとして認識してくれるようです。
正直推測です。ですので、良い子の皆はこの記事を鵜呑みにしないようにしましょう(汗
もし、解説していただける方いたらコメント頂けますと幸いです・・・!

修正後のparamsの中身↓
スクリーンショット 2020-04-14 21.27.07.png

ちゃんとitem{ }の中に入れてもらえました。
やっぱり仲良くするのが一番です。

 さいごに

(しっかり理屈で理解しきれていないのに)記事にして大丈夫なのかな。という内容でした・・・。
有識者の方が解説してくださる事を願っています。

最後までお付き合いくださりありがとうございました!

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

【Rails】単一テーブル継承(STI)について

執筆中

単一テーブル継承(STI)とは

STI(Single Table Inheritance)

同じカラム設計のテーブルを、一つのテーブルにまとめて、継承することで余計なテーブルを増やさず、DRYなテーブル設計にするというもの。
(テーブルが多いですねw)

考え方はクラスの継承と同じ!!

悪い例

良い例

図にも書いてある通り、authors、category、tagsは擬似テーブルであり、実在はしないテーブルになります。

実在はしないので、データは全てtaxonomiesに保管されます。

Model

article.rb
class Article < ApplicationRecord

  belongs_to :category
  belongs_to :author

  has_many :article_tags, dependent: :destroy
  has_many :tags, through: :article_tags
end
taxonomies.rb
class Taxonomy < ApplicationRecord

end
author.rb
class Author < Taxonomy
  has_many :articles
end
category.rb
class Category < Taxonomy
  has_many :articles
end
tag.rb
class Tag < Taxonomy
  has_many :article_tags
  has_many :articles, through: :article_tags
end

擬似テーブル3つは継承元がTaxonomyになっている点に注意!!

Active Record

[1] pry(main)> Article.joins(:tags).where(tags: { id: 1 }).first
  Article Load (3.5ms)  SELECT  `articles`.* FROM `articles` INNER JOIN `article_tags` ON `article_tags`.`article_id` = `articles`.`id` INNER JOIN `taxonomies` ON `taxo
nomies`.`id` = `article_tags`.`tag_id` AND `taxonomies`.`type` IN ('Tag') WHERE `tags`.`id` = 1 ORDER BY `articles`.`id` ASC LIMIT 1
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'tags.id' in 'where clause': SELECT  `articles`.* FROM `articles` INNER JOIN `article_tags` ON `article_ta
gs`.`article_id` = `articles`.`id` INNER JOIN `taxonomies` ON `taxonomies`.`id` = `article_tags`.`tag_id` AND `taxonomies`.`type` IN ('Tag') WHERE `tags`.`id` = 1 ORDER
 BY `articles`.`id` ASC LIMIT 1
[3] pry(main)> Article.joins(:tags).where(tags: { id: 1 })
  Article Load (2.0ms)  SELECT `articles`.* FROM `articles` INNER JOIN `article_tags` ON `article_tags`.`article_id` = `articles`.`id` INNER JOIN `taxonomies` ON `taxon
omies`.`id` = `article_tags`.`tag_id` AND `taxonomies`.`type` IN ('Tag') WHERE `tags`.`id` = 1
  Article Load (0.6ms)  SELECT  `articles`.* FROM `articles` INNER JOIN `article_tags` ON `article_tags`.`article_id` = `articles`.`id` INNER JOIN `taxonomies` ON `taxo
nomies`.`id` = `article_tags`.`tag_id` AND `taxonomies`.`type` IN ('Tag') WHERE `tags`.`id` = 1 LIMIT 11
=> #<Article::ActiveRecord_Relation:0x3fe90b9879dc>

ここでのポイントはエラーが出力されているかということ。

なぜかというと、whereは条件通りに絞り込んではくれても使用されるまでは実行されないため、エラーが出力されない。

エラー文にWHERE 'tags'の文字がある。
これはtagsテーブルからという条件のもと検索をかけているのだが、単一テーブルを継承したテーブルは擬似テーブルであり、実在しない。
実在しないテーブルから検索するのは無理なのでエラーが出ている。

[6] pry(main)> Article.joins(:tags).where(taxonomies: { id: 1 })
  Article Load (9.0ms)  SELECT `articles`.* FROM `articles` INNER JOIN `article_tags` ON `article_tags`.`article_id` = `articles`.`id` INNER JOIN `taxonomies` ON `taxon
omies`.`id` = `article_tags`.`tag_id` AND `taxonomies`.`type` IN ('Tag') WHERE `taxonomies`.`id` = 1
=> []

これでok

ちなみにどういうSQLが走るかだけ見たい時はto.sqlを使う。

[7] pry(main)> Article.joins(:tags).where(tags: { id: 1 }).to_sql
=> "SELECT `articles`.* FROM `articles` INNER JOIN `article_tags` ON `article_tags`.`article_id` = `articles`.`id` INNER JOIN `taxonomies` ON `taxonomies`.`id` = `artic
le_tags`.`tag_id` AND `taxonomies`.`type` IN ('Tag') WHERE `tags`.`id` = 1"

テーブル周り弱すぎなのでSQLに励みます。

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

AWS 課金回避方法

AWS無料期間について

AWSのアカウントを作成してから12ヶ月間、以下のアプリケーションは無料で利用できます。

※12ヶ月が過ぎてしまうと課金が始まってしまうので注意が必要。

Amazon EC2

750時間/月 (t2.microインスタンスの使用もこれに含む)まで

Amazon S3:


5GBの標準ストレージ、20,000件のGETリクエスト、2,000件のPUTリクエスト

Elastic IPアドレス


実行中のインスタンスに関連づけられたElastic IPアドレスを1つだけ
インスタンスに紐付いていないElastic IPアドレスは全て課金対象

IAM


IAMユーザーの一時的なセキュリティ認証情報を使用して他のAWSサービスにアクセスするときのみ料金が発生

2つ目以降のインスタンスを作成する際の課金回避法

インスタンスを作成し直す際や、別のアプリケーションをデプロイするためには別インスタンスを立ち上げることになります。以下の手順を踏めば課金は解除できるでしょう。

手順

1.Elastic IPアドレスを解放する
2.(すでに作成済みで、これ以上必要の無い場合)紐付いているS3バケットを削除する
3.インスタンスを削除する

1.Elastic IPアドレスを解放する

1.AWSアカウントのルートユーザーにログインする
※IMAユーザーの場合、以下のようなエラーが出ると思います。
a.png

2.Amazon EC2 コンソールを開く
3.Elastic IP アドレスを選択し、[Actions]、[Release addresses] の順に選択します。
a.png

※もし、解放ができない場合は、アドレスの関連付けの解除を押してからアドレスの解放を押してください。
以上で、1.Elastic IPアドレスを解放は完了です。

2.(すでに作成済みで、これ以上必要の無い場合)紐付いているS3バケットを削除する

1.AWS マネジメントコンソールにサインインし、Amazon S3 コンソールを開きます。
2.[バケット名] リストで、削除するバケットの名前の横にあるバケットアイコンを選択し、[バケットを削除する] を選択します。

※バケットにオブジェクトが含まれている場合は、[This bucket is not empty (このバケットは空ではありません)] というエラーアラートの [バケットを空にする設定] リンクを選択し、[バケットを空にする] ページの指示に従って、バケットを空にしてから削除します。次に、[バケットを削除する] ページに戻り、バケットを削除します。
以下のような状態になれば2.に戻って削除に入ります。
a.png
3.[バケットを削除する] ダイアログボックスで、削除するバケットの名前を確認のために入力し、[確認] を選択します。

3.インスタンスを削除する

インスタンスを停止するとEIastic IPアドレスは関連づけられなくなるので、あらかじめEIastic IPアドレスを解放し課金されないように気をつけましょう。
1.ナビゲーションペインで [Instances] を選択し、インスタンスを選択します。


2.[Actions] を選択して [Instance State] を選択し、[Stop] を選択します。
[Stop] が無効になっている場合は、インスタンスが既に停止しているか、またはルートボリュームがインスタンスストアボリュームです。(インスタンスを停止すると、インスタンスストアボリューム上のデータは消去されます。インスタンスストアボリュームのデータを保持するには、このデータを永続的ストレージに必ずバックアップしてください。)

停止、終了など選択できると思うので削除したい場合は終了を押してください
停止の場合はEIastic IP(住所のようなもの)を削除していないと課金が発生してしまいます。

3.確認ダイアログボックスで [Yes, Stop] を選択します。
※インスタンスが停止するまで、数分かかる場合があります。

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

[rails]ストロングパラメータ受け取り時の400 bad requestの例と理由考察

どんな事例

パラメータは投げ込めているのに受け取ることができないといったエラーでした。下記に詳細を示します。

エラー文(ターミナル上)の様子

Completed 400 Bad Request in 70ms (ActiveRecord: 32.8ms)



ActionController::ParameterMissing (param is missing or the value is empty: delete_images_ids):

app/controllers/items_controller.rb:163:in `delete_image_params'
app/controllers/items_controller.rb:62:in `update'

原因の記述とパラメータが投げ込まれている様子

app/assets/javascript/item_edit.js
$('#edit_item').on('submit', function(e){

  // 通常のsubmitイベントを止める
  e.preventDefault();
  // images以外のform情報をformDataに追加
  var formData = new FormData($(this).get(0));
  var url = $(this).attr('action')
  // 削除画像がない場合は便宜的に0を入れる
  if (clickdelete_registered_images_ids == gon.imageids) {
    formData.append("delete_images_ids[ids][]", 0)
    console.log(formData);
  // 削除画像で、のidをformDataに追加していく
  } else {
    clickdelete_registered_images_ids.forEach(function(delete_image_id){
      formData.append("delete_images_ids[ids][]", delete_image_id)
      console.log(formData);
    });
  }


  // 新しく追加したimagesがない場合は便宜的に空の文字列を入れる
  if (new_image_files.length == 0) {
    formData.append("new_images[images][]", " ")
  // 新しく追加したimagesがある場合はformDataに追加する
  } else {
    new_image_files.forEach(function(file){
      formData.append("new_images[images][]", file)
    });
  }

  $.ajax({
    url: url,
    type:        "PATCH",
    data:        formData,
    contentType: false,
    processData: false,
  })
});

パラメータを受け取れていない様子

app/controllers/items_controller
def delete_image_params
  #binding.pryをした結果は下記に示します
  params.require(:delete_images_ids).permit(ids:[])
end
From: /Users/kontatomoya/projects/freemarket_sample_62d/app/controllers/items_controller.rb:162 ItemsController#delete_image_params:

    161: def delete_image_params
 => 162:   binding.pry
    163:   params.require(:delete_images_ids).permit({ids:[]})
    164: end

[1] pry(#<ItemsController>)> params
=> <ActionController::Parameters {"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"8Ld4lh2Zdf9r6wXZ9judJrqSMedhtgIdq2BqSJUtLurJqoHxVEWYWUiCjceulnnyXyl+4h99mEsAh213iYXtfQ==", "item"=>{"name"=>"お供", "text"=>"kkkkk", "category_id"=>"799", "condition_id"=>"4", "deliverycost_id"=>"1", "pref_id"=>"2", "delivery_days_id"=>"2", "price"=>"20000"}, "delete_images_ids"=>{"ids"=>["20", "22"]}, "new_images"=>{"images"=>[" "]}, "controller"=>"items", "action"=>"update", "id"=>"2"} permitted: false>

上記のように欲しい値は"delete_images_ids"=>{"ids"=>["20", "22"]}という形で投げ込まれていましたが受け取ることができませんでした

解決方法

最終的な決着としてどうなったかと言いますと、問題の根本は結局何かわからず、次の方法で迂回して解決を行いました。
①受け取り方をrequire(:item)に含めること
②ajax通信をやめるて通常のhtml通信のsubmitで送信すること(javascriptからではなくhtmlからデータを拾う形とすること)

理由考察

最終的には原因究明までは至りませんでしたが、原因の可能性は4つ感じており、今後の自分と、同じエラーで困っている方のお力になれればと思い記述させていただきます。結局は解決できていないため、可能性として目を通していただければと思います。

1.投げ入れた値が許可済みのものではなかった

ajax通信でjavascriptのformDataに配列名とその中身をappend(追記)して投げ込みませんでしたがRailsガイドの該当箇所(4.5.1 許可済みスカラー値)の中にformDataはないので弾かれてしまった。
(参考)Railsガイドの該当箇所(4.5 Strong Parametersの箇所をご覧ください)
https://railsguides.jp/action_controller_overview.html

2.一つのform_withに対して、送るモデルの数が2つ以上になってしまった

params.require(:item).permit(そのほかのもの,ids:[])にまとめたら動いたため

3.ajax通信で送る時に余計なデータが入った時に400エラーが起きるらしい

これに関しては手がかりはないのですが、検索すると出てくるらしいです

4..require()の中に入るシンボルがモデルに定義されていないと動かない

これは1番のRailsガイドを見ると.require()で投げ込むことができるモデルは〜という記述があるのでこれも一つ可能性があります。今の例も一応該当はしています

最後に

とりとめない記事になってしまったのですが、何かのきっかけにしていただければ幸いです。

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

heroku apps は 5個までらしいです。

Ruby on Rails チュートリアル 5.1(第4版)を学習中。
学習途中におきた出来事をつらつら備忘録として書いているチラシの裏のようなもの。
Qiita見切り発車。たぶん動くからリリースしようぜって偉い人が言ってた。
見にくさも技術も良くなっていくと思います。たぶん。知らんけど。

$heroku create

createコマンドを実行したら以下のとおり言われてしまった。

 ▸    You've reached the limit of 5 apps for unverified accounts. Delete some apps or
 ▸    add a credit card to verify your account.
(訳)

ver未確認のアカウントのアプリ数の上限5に達しました。
一部のアプリを削除するかaccountクレジットカードを追加して、アカウントを確認します。

無料のうちはアプリを5つ以上は作れないらしい。

$heroku apps

上記コマンドで、現在作成したappを表示できた。

hmbrsnmhrgk
mysterious-atoll-47707
pure-hollows-45904
radiant-harbor-62133
rails-tutorial-nmhrgk

……確かに5個作成していた。

$heroku destroy --app hmbrsnmhrgk(アプリ名)

古いappを1つ削除してみる。

 ▸    WARNING: This will delete ⬢ hmbrsnmhrgk including all add-ons.
 ▸    To proceed, type hmbrsnmhrgk or re-run this command with
 ▸    --confirm hmbrsnmhrgk
(訳)

▸警告:これにより、すべてのアドオンを含む⬢hmbrsnmhrgkが削除されます。
proceed続行するには、hmbrsnmhrgkと入力するか、次のコマンドでこのコマンドを再実行します
▸-hmbrsnmhrgkを確認します

hmbrsnmhrgkを入力してみた。

$heroku apps
mysterious-atoll-47707
pure-hollows-45904
radiant-harbor-62133
rails-tutorial-nmhrgk

5個あったappが4個に減っている。
削除に成功したようだ。

$heroku create

無事新しいappを作成できたよ。

ちゃんちゃん。

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

【Rails】order

取得した値を並び替えるメソッドになります。

orderとは

create_table "posts", force: :cascade do |t|
 t.string "content"
 t.datetime "created_at", null: false
 end

通常、上記のようなPostモデルだとデータベースに格納される値は昇順になります。

:id => 1
:created_at =>3.days.ago
:id =>2
:created_at =>1.days.ago
:id =>3
:created_at =>Time.zone.now

ただし、実際のアプリケーションであれば投稿を行う際に時間などは降順で表示されます。
そこでorderを使用します。

以下はorderを使用したサンプルになります。

class Post < ActiveRecord::Base
  default_scope -> { order(created_at: :desc) }
end

default_scopeは読んだままデフォルトのスコープを定義するメソッドです。ここで定義されたスコープは全てのクエリに適用されます。
※ クエリ:データベースさんに対する命令文のこと。
スコープ:影響範囲

created_atの後の:descはdescendingの略で降順という意味です。

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

【Rspec】エラー文 undefined local variable or method `response' for RSpecの解決法

以下のようにテストを書いたところ、次のエラー。

gyms_conotoller_spec.rb
require 'rails_helper'

describe "indexメソッドが動く" do

expect(response).to render_template :index

end

エラー文

 undefined local variable or method `response' for RSpec::ExampleGroups::Gyms::Index:Class

原因

そもそもfeature_specにはresponseオブジェクトがないから。

解決法

controller_specでrenderオブジェクトを確認するテストをすればOK.
responceメソッドはcontroller_specのためだけにあります。

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

active_hashを用いて都道府県データを表示してみた

なぜactive_hashを使うのか?

都道府県のデータなど、変更される可能性が低いものをハッシュとして管理できるため、わざわざテーブルを作成する必要が無い

というメリットがあります。

今回の内容はフリマアプリでactive_hashを利用した際に、つまずいた点があったので、
備忘録としてまとめたものです。

環境

Rails 5.2.3
Ruby 2.5.1

導入方法

gem active_hashをインストール

gem 'active_hash'

bundle installを実行。

1.png

active_hashのインストールが完了しましたので

続いてモデルの作成に移ります。

モデルを作成

まずはaddress.rbを作成します。

rails g model adderss

作成したアドレス内に以下の内容のように編集する

class Address < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :prefecture
end

この記述により、後ほど作成するprefecture.rbとアソシエーションを組むことができ、prefecture.rbの内容を利用することができます。

rails db:migrateを行った後、prefecture.rbを作成します。

内容は以下のように編集しましょう

class Prefecture < ActiveHash::Base
  self.data = [
      {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'},
      {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'},
      {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'},
      {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'},
      {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'},
      {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'},
      {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'},
      {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'},
      {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'},
      {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'},
      {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'},
      {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'},
      {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'},
      {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'},
      {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'},
      {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'}
  ]
end

これで、モデル内の準備は完了です。

次はコントローラー内を編集します。

コントローラーを編集

今回はフリマアプリなので、products_controller.rb内のnewアクション内に記述を行います。

def
  @address = Prefecture.all
end

これにより@addressでprefectureを利用できるようになりました。

rails cでも確認してみると

2.png

下は見切れてしまっていますが、取り出せているようです。

続いてviewファイル内のformの記述を編集し、ユーザーが都道府県を選択できるようにします。

viewファイル内を編集、都道府県データが表示されるか確認

= form_for @product do |f|

の記述から、= f.text_field, = f.text_areaといった形で入力フォームを作成しているので

f.collection_select :prefecture_id, Prefecture.all, :id, :name

フォームを設置したい箇所に上記を記述し、投稿してみる。

3.png
エラーが出てしまった....

binding.pryを用いて、prefectureが取り出せているか確認してみることに
5.png

4.png

しっかり取り出せている...

formの記述に誤りがあるのかもしれないとのことで、以下に変更

 = f.select :prefecture_id, Prefecture.all, :id, :name

6 .png

またしても、エラー...

form以外に怪しい点は無いか探してみると

なんとproductテーブル内にprefectureの入力を反映させるprefecture_idが存在していなかった。

これでは、中身があっても入れ物が無いようなもので、エラーが起きてしまうのも無理はない

no method errorは文字通りmethodが定義されていないというエラーでもあれば、もっと単純にコンピュータ側から「これがなんだかわからない」というメッセージでもあったことを知る。

カラムを追加して確認すると、、、
7.png

無事、実装完了。

終わりに

初めてactive_hash実装にあたって苦労する箇所は多かったが、使いこなすメリットは大きいと感じました。

ただエラー内容は単純なものだったこともあり、もう少し早く解決できた内容だったかなと...

まだまだ初学者の粋を脱することは難しいようだ

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

#Rails + rake で複数行のDescriptionを設定する、表示する ( display multiple lines description / describe with heredoc newlines )

rake

lib/tasks/some_job.rake

desc <<~DESC
  This job
  is so
  Great job
DESC

task some_job: :environment do
  # do something
end

describe

descript オプションで全行表示される

bin/rails --describe some_job

rails some_job
    This job
    is so
    Great job

tasks

一覧だと一行だけ表示される

bin/rails --tasks

...
rails some_job                           # This job
...

Original by Github issue

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

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

コーディング未経験のPO/PdMのためのRails on Dockerハンズオン、Rails on Dockerハンズオン vol.15 - Deploy to Heroku

はじめに

こんにちは!

今回は実際にここまで作ったアプリケーションをデプロイして公開してみます!
Herokuというサービスを使って、今まで開発してきたアプリケーションをデプロイしてみます。

ちなみに今回のハンズオンではGitコマンドを使用します(Herokuにソースコードをアップロードするときに使う)。各自の環境に合わせてGitコマンドをインストールしてから進んでください!(ググって!)

前回のソースコード

今回使うソースコードです(今まで作ってきたアプリです)。今回はこれをデプロイしていきますので、お手元にアプリがない方はこちらからダウンロードしてください。

Heroku

アプリを公開するには、サーバーが必要ですね。今ではサーバーも物理的にサーバーを用意するのではなくAWS、Azure、GCPなどのクラウドサービスのIaaSを利用して用意するのが一般的になってきているかと思います。
HerokuはIaaSのもう一段上のPaaSに位置付けられるサービスです。
IaaS的なサーバーの利用はもちろん、アドオンとしてデータベースやCDNなどのサービスを簡易に利用できたり、ソースコードからビルド・デプロイを簡単に行なってくれたり(ビルドパック)、IaaS+αなことを提供してくれます。
時間などの制約などはありますが、個人の開発者でも無料でアプリケーションを公開できたりするのもオススメポイントです。
Herokuに関しては公式ドキュメントも整っていますし、色々な方が記事を書いたりしているので、この記事での説明はこのくらいにしておきましょう。

では実際にHerokuにアプリケーションをデプロイしていきます!

システム構成

HerokuではDynoと呼ばれるコンテナにアプリケーションをデプロイすることができます。
コンテナといっても今まで開発してきたようにDockerfileを書いてdocker-compose.ymlを書いてというようなことは必要ありません。(似たようなファイルを書くことで意図した通りのデプロイをさせる方法などはありますが、今回はスコープ外とします。)
今まで作ってきたRailsアプリケーションは、Dyno上で動作させましょう。

また、ここまでPostgreSQLをDockerコンテナで起動させてきました。
こちらはHerokuのアドオンとして、Heroku Postgresが用意されているのでこちらを利用しましょう。
Heroku Postgresも容量の制約はありますが無料でも利用が可能です。

Herokuアカウントを作成する

さて、では実際にデプロイを始めていきましょう。
まずはHerokuアカウントを作成します。サインアップページからアカウントを作成してみてください。

Heroku CLIをインストールする

コマンドラインからHeroku操作をするために公式インストールサイトからHeroku CLIをインストールしましょう。

Heroku CLIでログインする

$ heroku login
heroku: Press any key to open up the browser to login or q to exit:

でサインインページがブラウザで開くと思うので、先ほど作ったアカウントでサインインしてください。
コマンドライン側に以下の表示が出ていればサインイン成功です!

Logged in as [アカウントのメールアドレス]

Gitを使えるようにしておく

Herokuにデプロイする際はHerokuで用意してくれるGitレポジトリにアプリをpushします。
なので、Gitを使える準備をしておく必要があります。もし現段階でGitリポジトリを作成していない場合は、ローカルレポジトリを作っておきましょう。

$ git init

Heroku Appを作成する

まずHerokuでAppを作ります。ワークスペースみたいなものですね。
Appの名前はサブドメインにも使われるので、お好みの名前を指定してあげるといいです。ただし、他のアカウントも含めて重複は許されないのでかぶっちゃった時は諦めましょう。

$ heroku create test-app

test-appがアプリ名です。アプリ名をつけないとHerokuが適当な名前をつけちゃうのでちゃんとつけてあげてくださいね。

Heroku Add-onsのPostgresを追加する

Railsアプリケーションが使うPostgresをHeroku Add-onsから追加します。
今回は無料で済ませたいのでhobby-devプランを使います。

$ heroku addons:create heroku-postgresql:hobby-dev

Herokuにアプリをデプロイする

先ほどもお話した通り、HerokuへのデプロイはHerokuのGitリポジトリにソースコードをpushするだけです。
リモートリポジトリの情報は先ほどheroku createをした時にherokuという名前で登録されています。

前回のソースコードからソースコードをダウンロードしている場合は、ローカルブランチがvol.14になっている思います。(これはgit branchコマンドで確認できます。)
Herokuのmasterブランチにpushするために、ローカルブランチもmasterに切り替えてあげましょう。

$ git checkout -b master

そして、pushします。

$ git add .
$ git commit -m "First Commit"
$ git push heroku master

Enumerating objects: 300, done.
Counting objects: 100% (300/300), done.
Delta compression using up to 8 threads
Compressing objects: 100% (191/191), done.
Writing objects: 100% (300/300), 305.11 KiB | 30.51 MiB/s, done.
Total 300 (delta 108), reused 241 (delta 82)
remote: Compressing source files... done.
remote: Building source:
remote:
remote:  !     Warning: Multiple default buildpacks reported the ability to handle this app. The first buildpack in the list below will be used.
remote:             Detected buildpacks: Ruby,Node.js
remote:             See https://devcenter.heroku.com/articles/buildpacks#buildpack-detect-order
remote: -----> Ruby app detected
remote: -----> Installing bundler 1.17.3
remote: -----> Removing BUNDLED WITH version in the Gemfile.lock
remote: -----> Compiling Ruby/Rails
remote: -----> Using Ruby version: ruby-2.6.5
remote: -----> Installing dependencies using bundler 1.17.3
remote:        Running: bundle install --without development:test --path vendor/bundle --binstubs vendor/bundle/bin -j4 --deployment
remote:        The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
remote:        Fetching gem metadata from https://rubygems.org/............
remote:        Fetching rake 13.0.1
remote:        Installing rake 13.0.1
remote:        Fetching concurrent-ruby 1.1.6
...
remote: -----> Compressing...
remote:        Done: 92.2M
remote: -----> Launching...
remote:        Released v6
remote:        https://test-app.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/test-app.git
 * [new branch]      master -> master

なんかいろいろログが出てきますが、注目は『Ruby app detected』や『Compiling Ruby/Rails』ですね。
HerokuのGitレポジトリにpushした段階でビルドパックが作動しています。
ソースコードから勝手に「Ruby使ってますよね?Railsですよね?ビルドしてデプロイしときますね!」という感じで勝手にビルド&デプロイしてくれます。

最後の方に「https://test-app.herokuapp.com/ deployed to Heroku」とありますね。
このURLでデプロイしたよ、ということです。

デプロイされたところでDBをマイグレートしてあげます。

$ heroku run rails db:migrate

このようにheroku runコマンドをつけることでpushしたアプリを起動してコマンドを発行します。
正確にはOne-Off Dynosが使われているみたいなので、今WebからアクセスできるDynosとは別にコマンド発行用に一時的に起動されているみたいです。(なのでログファイルとかを見るようではない)

ここまで完了したら先ほどデプロイしたよと言われたURL(https://test-app.herokuapp.com/)にアクセスしてみましょう。

image.png

お、デプロイされとる。動く、動くぞ。
ちょっといろいろと触ってみましょう。今までコーディングしてきた機能がちゃんと動くなっているはずです。

デプロイできました。クソほど簡単でした。

まとめ

今回はここまでで終了です!
いままで作ってきたアプリをHerokuにデプロイすることができました!

そういえば、HerokuはGitHubとの連携がいい感じらしいです。GitHubにpushしたらHerokuに自動でデプロイするように設定したりできるってことですね。
GitHubにはCIツールのGitHub Actionsがあったりもするので、テストコードをパスしたらHerokuにデプロイみたいなことも意外と簡単にできるのかも(やったことない)。
もちろん他のGitサービスやCI/CDサービスでもできます。僕はGitlab CI使ってやってましたー。

ここまででDocker上でRailsアプリを開発する環境を作って、TDDでコーディングをして、Herokuにデプロイする、という一連の流れを体験できましたね!
あとは自分の作りたいものを作ってみるのが一番勉強になります!走り出す準備はできたはず!あとはひたすらに作って調べて作って調べてたまに記事とか書いてアウトプットして!開発を楽しみましょう!本業も忘れずに!

次回はチャレンジング!僕も勉強中なんですが、せっかくDockerをやってきたのでKubernetesだとどうやってデプロイ・公開できるの?というのを体験してみたいと思います!「こいつ、動くぞ!」

ではまた次回!

後片付け

今回はHerokuアプリをデプロイしたままになっちゃっているのでちゃんと後片付けしましょう。

まずHeroku Appを削除しちゃいます。

$ heroku apps:destroy --app test-app --confirm test-app

これでAdd-onsも含めて削除されました。
Herokuアカウントもいらない場合はWebサイトのダッシュボードから削除できます。詳しくは公式サイトを。

本日のソースコード

本日は特にソースコードを更新していないので、ないです。

Other Hands-on Links

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

RailsアプリにCircleCI2.1でCI/CDを導入する設定ファイル【初心者向け】

こんにちは、ペーパーエンジニアのよしこです。

自作RailsアプリにCircleCIを使ってCI/CDを導入してから数週間が経過して、導入当初と比較して動作が安定しました。

私の場合、CircleCI公式ドキュメントを基本として自分の環境を構築しましたが、
当時はCircleCI version: 2.1の新機能※ に対応した他環境の設定ファイルを参考にしたいと思ってました。

※ CicleCI vertsion: 2.1の新機能
orbs / commands / executors

機能の説明は次の連載記事が分かりやすかったです。
「エンジニアのためのCI/CD再入門」連載一覧

そこで今回、CI/CDを導入したい初心者エンジニア向けに、CircleCI2.1に対応した設定ファイルを公開したいと思います。

環境

Ruby  2.6.3
Rails  5.1.6
PostgreSQL  12.2
CircleCI  2.1
デプロイ先はHeroku

CI/CDの全体像

$ git push origin HEADでCircleCI作動

  1. bundle(依存関係)のインストール・リストア
  2. 静的コード解析 RuboCop
  3. テスト実行 RSpec
  4. Herokuにデプロイ(masterブランチのみ)

設定ファイル(コメントなし)

./.circleci/config.ymlに記述。
次項にコメント(個人メモ)ありver.

config.yml
version: 2.1
orbs:
  ruby-orbs: sue445/ruby-orbs@1.6.0
  heroku: circleci/heroku@1.0.1
workflows:
  build_test_and_deploy:
    jobs:
      - build
      - rubocop_job:
          requires: 
            - build
      - rspec_job:
          requires: 
            - build
      - deploy:
          requires:
            - rubocop_job
            - rspec_job
          filters:
            branches:
              only:
                - master
executors:
  default:
    working_directory: ~/repository
    docker:
      - image: circleci/ruby:2.6.3-stretch-node
        environment:
          BUNDLE_PATH: vendor/bundle
          RAILS_ENV: test
  extended:
    working_directory: ~/repository
    docker:
      - image: circleci/ruby:2.6.3-stretch-node
        environment:
          BUNDLE_PATH: vendor/bundle
          PGHOST: 127.0.0.1
          PGUSER: postgres
          RAILS_ENV: test
      - image: circleci/postgres:12-alpine
        environment:
          POSTGRES_USER: postgres
          POSTGRES_DB: app_name_test
jobs:
  build:
    executor: default
    steps:
      - checkout
      - bundle-install
  rubocop_job:
    executor: default
    steps:
      - preparate
      - run:
          name: 静的コード解析を実行(RuboCop)
          command: bundle exec rubocop
  rspec_job:
    executor: extended
    steps:
      - preparate
      - run:
          name: DBの起動まで待機
          command: dockerize -wait tcp://localhost:5432 -timeout 1m
      - run:
          name: DBをセットアップ
          command: bin/rails db:schema:load --trace
      - run:
          name: テストを実行(RSpec)
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
              circleci tests split --split-by=timings)"
            bundle exec rspec --profile 10 \
                              --format RspecJunitFormatter \
                              --out test_results/rspec.xml \
                              --format progress \
                              $TEST_FILES
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results
  deploy:
    executor: heroku/default
    steps:
      - checkout
      - heroku/install
      - heroku/deploy-via-git
commands:
  bundle-install:
    steps:
      - ruby-orbs/bundle-install:
          bundle_clean: true
          bundle_extra_args: ''
          bundle_gemfile: Gemfile
          bundle_jobs: 4
          bundle_path: vendor/bundle
          bundle_retry: 3
          cache_key_prefix: v1-bundle-dependencies
          restore_bundled_with: true
  preparate:
    steps:
      - checkout
      - bundle-install

設定ファイル(個人メモあり)

config.yml
# CircleCI 2.1 を使用
version: 2.1
# 公開されているCircleCI設定を読み込む。version: 2.1以上
orbs:
  ruby-orbs: sue445/ruby-orbs@1.6.0
  heroku: circleci/heroku@1.0.1

# CI/CD工程の全体像
workflows:
  build_test_and_deploy:
    jobs:
      - build
      - rubocop_job:
          requires: 
            - build
      - rspec_job:
          requires: 
            - build
      - deploy:
          requires:
            - rubocop_job
            - rspec_job
          filters:
            branches:
              only:
                - master

# 実行環境
executors:
  default:
    # コマンドを実行するディレクトリ(Gitリポジトリ名)を指定
    working_directory: ~/repository
    docker:
      - image: circleci/ruby:2.6.3-stretch-node
        environment:
          # デフォルトはBUNDLE_PATH=/usr/local/bundleで設定。上書きが必要
          BUNDLE_PATH: vendor/bundle
          RAILS_ENV: test
  extended:
    working_directory: ~/repository
    docker:
      - image: circleci/ruby:2.6.3-stretch-node
        environment:
          BUNDLE_PATH: vendor/bundle
          PGHOST: 127.0.0.1
          # config/database.ymlの内容と一致させる
          PGUSER: postgres
          RAILS_ENV: test
      # $ psql -V で確認したバージョンと合わせる。-alpineが軽量版のため望ましい
      - image: circleci/postgres:12-alpine
        environment:
          # config/database.ymlの内容と一致させる
          POSTGRES_USER: postgres
          POSTGRES_DB: app_name_test

# 各工程の定義
jobs:
  build:
    executor: default
    steps:
      # ソースコードを作業ディレクトリにチェックアウトする特別なステップ
      - checkout
      # 依存関係とバンドルの処理
      - bundle-install
  rubocop_job:
    executor: default
    steps:
      - preparate
      # 静的コード解析を実行
      - run:
          name: 静的コード解析を実行(RuboCop)
          command: bundle exec rubocop
  rspec_job:
    executor: extended
    steps:
      - preparate
      # DBの起動まで待機
      - run:
          name: DBの起動まで待機
          command: dockerize -wait tcp://localhost:5432 -timeout 1m
      # DBをセットアップ
      - run:
          name: DBをセットアップ
          command: bin/rails db:schema:load --trace
      # テストを実行
      - run:
          name: テストを実行(RSpec)
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
              circleci tests split --split-by=timings)"
            bundle exec rspec --profile 10 \
                              --format RspecJunitFormatter \
                              --out test_results/rspec.xml \
                              --format progress \
                              $TEST_FILES
      # テスト結果を保存
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results
  deploy:
    executor: heroku/default
    steps:
      - checkout
      - heroku/install
      - heroku/deploy-via-git

# 処理など。version: 2.1以上
commands:
  # 依存関係の処理(Orbsを利用)
  bundle-install:
    steps:
      - ruby-orbs/bundle-install:
          bundle_clean: true
          bundle_extra_args: ''
          bundle_gemfile: Gemfile
          bundle_jobs: 4
          bundle_path: vendor/bundle
          bundle_retry: 3
          cache_key_prefix: v1-bundle-dependencies
          restore_bundled_with: true
  # 各jobの準備工程
  preparate:
    steps:
      - checkout
      - bundle-install

参考ドキュメント(日本語)

構築から少なくとも導入初期までは公式ドキュメントがオススメです。

言語ガイド:Ruby(Rails)
データベースの設定例
CircleCI を設定する(設定リファレンス)

CircleCIの用語やCI/CDの全体像を把握するのに役に立ったネット記事です。参考程度に。

「エンジニアのためのCI/CD再入門」連載一覧

Orbsの導入は、CircleCIドキュメントやGitHubなどを参考にするほうがオススメです。

sue445/ruby-orbs
CircleCI
GitHub

circleci/heroku
CircleCI
GitHub

参考までに私の記事もどうぞ。

既存RailsアプリにCircleCIを導入した手順
RailsアプリにDockerとCircleCIを導入した際、DB周りでエラーになったときの対処法

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

RESTとRESTfulについてまとめてみた

はじめに

Railsの技術書終盤に差し迫っているのですが、ついにラスボスと出会いました。
そのラスボスとは、、、

『RESTfulなインターフェイス』とはRESTの特徴を備えたルートの事を指す。

という一文。
頭の中ハテナだらけになり、コテンパンにされました、、、??

なので、今回は〜
『RESTfulなインターフェイス』と『REST』の理解できるようになりラスボスを倒すのが目的です!??

RESTを理解するために

この後説明していきますが、RESTはWebサービスの設計概念のため、具体的にこう!というよりかは、抽象度高く理解しておいた方が、他の言語などでも適用しやすくなるので良いみたいです??

なので、自分の中でしっかり落とし込むより、ほわ〜っと理解できればオッケ〜?‍♀️?‍♂️

RESTとは?

まずはRESTから倒していきます?

RESTは「リソース」を扱うための考え方

なに(リソース)をどうする(HTTP)を表現するための考え方

※リソースはWebページや記事などの情報のことを指します。

⇩イメージ
rest.png

そして、リソースはそれぞれのURIを持っていて、そのURIとHTTPメソッドの組み合わせで、どんなCRUD処理をするのかをあらかじめ紐づけています!

RESTfulとは?

ラスボス退治まであと少し!?

『RESTfulなインターフェイス』とはRESTの特徴を備えたルートの事を指す。

簡単に言うとRESTのルールに基づいて設計されたインターフェイス(接続部分)のこと。

RESTfulなインターフェイスを使うと、統一感があってわかりやすいURLをつくることができます!?

簡単にまとめると『このURIとHTTPだったら、このCRUD処理』(=REST)というセットを決めておけば使いやすくできるからこのルールにしたがってルートの設計しましょう~!という考え方で設定されたルートのこと??

RESTfulなインターフェイスと定義するためには?

Railsではresourcesメソッドを使用するとRESTfulなインターフェイスと定義することができます!しかも自動で!??

routes.rb
Rails.application.routes.draw do
  resources :users
end

このようにresourcesメソッドを使うと、、、

    Prefix   Verb   URI Pattern                Controller#Action
     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

上記のようなRESTfulなインターフェイス(ルート)が完成します!
※Verb = HTTPのことを指す。
※updateするときのHTTPがなぜ2つあるのか気になった方はこちらへどうぞ

まとめ

REST

RESTは「リソース」を扱うための考え方の1つ。HTTPメソッドとURIのセットでCRUD処理を表す。

RESTful

RESTのルールに基づいて設計されたインターフェイス(接続部分/ルート)のこと。

resourcesメソッド

RailsでRESTfulなインターフェイスと定義するためのメソッド

以上です!最後までみていただきありがとうございました!
今回は正直めっちゃまとめるの苦労しました〜?

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

#Rails ( ActiveRecord ) で alias / alias_method がエラーを起こすので alias_attribute を使う

Class

class User < ApplicationRecord
  alias_attributes :some_name, :name
end

run example

User.last.name
# Alice

User.last.alias_name
# Alice

Error case ( alias / alias method )

  alias foo_alias_name name
  # NameError: undefined method `name' for class `User (call 'User.connection' to establish a connection)'

  alias_method :bar_alias_name, :name
  # NameError: undefined method `name' for class `User (call 'User.connection' to establish a connection)'

Why?

  • alias / alias_method が使えないのは評価順、ActiveRercord的なメソッド生成タイミングの問題と思われる
  • ActiveRecord によってメソッド生成されたあとにOpenClass すると alias をつけることも出来る様子

Open class
Before ActiveRecord Model methods occur

class User
  alias alias_name name
end
# NameError: undefined method `name' for class `User'

Open class
After ActiveRecord Model methods occur

User.first # Maybe ActiveRecord method occurs in this timing

class User
  alias alias_name name
end

User.first.alias_name
# => "Alice"

Original by Github issue

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

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

【Rails】date_selectを使ってみた

学習メモです

タスクに期限を設定したくてカレンダーだと面倒なので日付選択が簡単なdate_selectを使ってみた。

html.slim

    = f.date_select(:カラム名,
                    use_month_numbers: true,
                    start_year:        Date型で最初の日付,
                    end_year:          Date型で終了の日付(interger型なので+5などでつければOK,
                    default:           何も入力しなかった場合の値,
                    date_separator:    '区切りかた')

実際に書いたコード

    = f.date_select(:start_at,
                    use_month_numbers: true,
                    start_year:        Date.today.year,
                    end_year:          Date.today.year + 5,
                    default:           Date.today,
                    date_separator:    '/')

今paramsになにが来ているのかbinding.pryで見てみる

 <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"e12hjdLpjd/M5iuOGCkPaK5gZE6oR5US4HLybq1zKMNP178svzeGhjg6y2a3Fu6XnrlFuIKnkKtJgvIFqs6rkQ==", "task"=>{"name"=>"", "description"=>"", "start_at(1i)"=>"2020", "start_at(2i)"=>"2", "start_at(3i)"=>"30", "priority"=>"low", "completed"=>"doing"}, "commit"=>"Create Task", "controller"=>"tasks", "action"=>"create"} permitted: false>

ちゃんと"start_at(1i)"=>"2020", "start_at(2i)"=>"2", "start_at(3i)"=>"30"がきていたのでこれをsaveする。

controllerのcreateアクションで

time = Date.new(params[:task]["start_at(1i)"].to_i,params[:task]["start_at(2i)"].to_i,params[:task]["start_at(3i)"].to_i)
if taime.save
  redirect_to :index
else
  render :new
end

これはparamsに入っているhashから要素を取り出しDateクラスの引数に入力してインスタンスを生成している。

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

railsのversion確認でエラーが出た時

$gem install rails -v 5.2.3

でinstallして

$rails -v

でversion確認すると

Rails is not currently installed on this system. To get the latest version, simply type:

   $ sudo gem install rails
You can then rerun your "rails" command.

んんんん、さっきインストールしたよな?
ま、この通りやってみるか

$sudo gem install rails

実行

また同じエラーが出ました

色々試してみました

http://tomoprog.hatenablog.com/entry/2017/02/03/015936

https://qiita.com/arashida/items/ae982df5e534fd4bc97a

上を参考にさせてもらって試してみました

けど全く同じエラー

そうだ、passを確認

$which rails
~/.rbenv/shims/rails

$which ruby
~/.rbenv/shims/rails

うん、全く同じ
rubyのversionを確認
ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-darwin19]

出るやんどういう事?

解決策

色々調べた結果、アプリを再起動してみた
そして
$rails -v

すると

Rails 5.2.4.1

結構しょうもなかったが、色々勉強させてもらった

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

LINE BOTで位置情報を使って天気予報を取得する

railsで位置情報で天気予報を取得できるBOTを作成したので、APIの搭載の仕方をご紹介します。今回はXML形式で取得した場合です。

LINEBOTの作り方はこちらを参照いたしました。
https://qiita.com/takashico/items/edb6050a8e54dd137148

apiはこちらから取得いたしました。
https://openweathermap.org/

apiの搭載はこちらの記事を参考にいたしました。
https://yoheikoga.github.io/2016/08/14/weather-in-the-area-now-on-by-gps-module/
http://tokin-kame.hatenablog.com/entry/2015/05/31/105245

ライブラリーを記載

controller.rb

  require 'line/bot'
  require 'open-uri'
  require 'kconv'
  require 'rexml/document'

XMLファイルから必要なものを取得

URLからXMLを取得しパースしていきます。
OpenWeatherMapは属性の中に必要な情報が記載されていたため、情報の取得に苦労しました。

controller.rb

# 省略
      when Line::Bot::Event::Message
        case event.type
        when Line::Bot::Event::MessageType::Location
      # LINEの位置情報から緯度経度を取得
          latitude = event.message['latitude']
          longitude = event.message['longitude']
          appId = "取得したAPI KEY"
          url= "http://api.openweathermap.org/data/2.5/forecast?lon=#{longitude}&lat=#{latitude}&APPID=#{appId}&units=metric&mode=xml"
         # XMLをパースしていく
          xml  = open( url ).read.toutf8
          doc = REXML::Document.new(xml)
          xpath = 'weatherdata/forecast/time[1]/'
          nowWearther = doc.elements[xpath + 'symbol'].attributes['name']
          nowTemp = doc.elements[xpath + 'temperature'].attributes['value']
          case nowWearther
     # 条件が一致した場合、メッセージを返す処理。絵文字も入れています。
          when /.*(clear sky|few clouds).*/
            push = "現在地の天気は晴れです\u{2600}\n\n現在の気温は#{nowTemp}℃です\u{1F321}"
          when /.*(scattered clouds|broken clouds|overcast clouds).*/
            push = "現在地の天気は曇りです\u{2601}\n\n現在の気温は#{nowTemp}℃です\u{1F321}"
          when /.*(rain|thunderstorm|drizzle).*/
            push = "現在地の天気は雨です\u{2614}\n\n現在の気温は#{nowTemp}℃です\u{1F321}"
          when /.*(snow).*/
            push = "現在地の天気は雪です\u{2744}\n\n現在の気温は#{nowTemp}℃です\u{1F321}"
          when /.*(fog|mist|Haze).*/
            push = "現在地では霧が発生しています\u{1F32B}\n\n現在の気温は#{nowTemp}℃です\u{1F321}"
          else
            push = "現在地では何かが発生していますが、\nご自身でお確かめください。\u{1F605}\n\n現在の気温は#{nowTemp}℃です\u{1F321}"
          end
      }
# 省略

最後に

OpenWeahterMapは降水確率がないので、簡易的な天気予報しか作れませんでした。もう少し精度の高い天気予報APIが欲しいですが、おそらく有料になってくると思います。
LINE BOTは一度形ができてしまえば、意外と簡単に作ることができました。
ぜひ、皆さんもオリジナルボット作り試してみてください。

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

画像有り投稿を検索する(画像有フィルター)

実装したいこと

画像が投稿されている投稿に絞って一覧表示できるようにします。(imageカラムの中身が存在するレコード一覧を取ってくる)

indexページ

indexページの適当な場所に以下を追記します。

tweets/index.html.erb
  <%= form_tag({controller:"tweets",action:"index"}, method: :get) do %>
  画像有りフィルター<%= radio_button_tag("image", "image") %>
  <%= submit_tag '?'  %>
  <% end %>

これは以下のようになります。
image.png

ラジオボタンをクリックして、送信すると、tweetsコントローラーのindexアクションに「image」というパラメーターが送られます。それを、コントローラーにて後述のような処理をすることで、画像有り投稿の一覧を取得し表示できます。

コントローラー

tweetsコントローラーのindexアクションに以下のように書きます。

tweets_controller.rb
def index
  if params[:image]
     @tweets = Tweet.where.not(image: nil)
  else
     @tweets = Tweet.all
  end
end

ややこしいですが、imageカラムの中身が存在しないもの以外のレコードを取ってこいという命令になってます。(カラムの中身が存在するものを取ってこいというストレートな書き方の情報が、意外と見当たらないんですよね~)

また、検索機能などと組み合わせる場合は、if文が入れ子になります。もしくは、あまりおすすめしませんが、検索機能付きのif文の下に追記する形でも問題なく動くっちゃ動きます。
以下参考

tweets_contoroller.rb
def index
    if params[:search]
      @tweets = Tweet.where("content LIKE ? ",'%' + params[:search] + '%')
    else
      @tweets= Tweet.all
    end

    if params[:image]
        @tweets = Tweet.where.not(image: nil)
    end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kaminariをカスタマイズ

ページネーションはkaminariというgemを用いることが多いかと思います。

参考
https://qiita.com/residenti/items/1ae1e5ceb59c0729c0b9

機能面は実装済みという前提で話します。

kaminariを実装すると初めは以下のように見にくいデザインになっているかと思います。

image.png

例えば、これを以下のように変えましょう!
image.png

上記リンクのように、bootstrapを適用するという方法もありますが、リンクホバーなど自分でカスタマイズしたいですよね。

kaminariのviewファイルを作成

ターミナルにて以下を打ち込んでください。

rails g kaminari:views default

これによりviewフォルダの中にkaminariのview一覧が作成されました!これはそのままにして次に進んでください!

日本語化

kaminariのgemのデフォが英語になっているので、これを日本語環境に変える必要があります。まず、config/applicationファイルをに以下を追記します。

config/application.rb
config.i18n.default_locale = :ja
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]

一行目は、日本語環境を適用、二行目は複数のlacaleファイル(後述)が適用されるコードになります。

全体としては以下のようになっているかと思います。

config/application.rb
require_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module Geektwitter
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.1
    config.i18n.default_locale = :ja
    config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]
    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration should go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded.
  end
end

これで日本語化が適用されます。

次にconfig/localesフォルダーにkaminari_ja.ymlを作成してください。(ja_ymlファイル中に例えばcreated_atの日本語化が既に書いてある場合、他の日本語化は本ページのように別のファイルを作成する必要があります。)

その中に以下をコピペします。

config/locales/kaminari_ja.yml
ja:
  views:
    pagination:
      first: "&laquo; 最初"
      last: "最後 &raquo;"
      previous: "&lsaquo; 前へ"
      next: "次へ &rsaquo;"
      truncate: "..."

英語を日本語に変えました!

カスタマイズ

次に該当のcssファイルに以下を追記しましょう。

tweets.css
// paginate
.pagination{
  margin: auto;
  width: 50%;
  display: flex;
  justify-content: flex-start;
}
.pagination span{
  background-color: rgba(158, 158, 158, 0.4);
  text-align: center;
  width: 50px;
  border: solid 1px #344963;
  color: rgb(197, 20, 159);
  transition: .3s;
  -webkit-transform: scale(1);
  transform: scale(1);
}
.pagination span:hover{
  transition: .3s;
  -webkit-transform: scale(1.1);
  transform: scale(1.1);
}
.pagination span a:hover{
  transition: .3s;
  -webkit-transform: scale(1.1);
  transform: scale(1.1);
}

※注意※
日本語環境に変えたあと、一度rails sを再起動して下さい。

これで良い感じになったかと思います!色合いや位置の調整(width)は、各自変えてみてください!

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

返信機能実装時、工夫した点 -newアクション-

投稿に対する返信機能を作成する際に、newアクションの実装を少し工夫することで便利にすることができました。

●実装コード

Q&Aサイトを実装しているため、投稿=Question 返信=Answerとなっています。

app/contorollers/answers_controlelr.rb
  def new
    @question = Question.find(params[:id])
    @answer = @question.answers.build
  end
config/routes.rb
  get  "/answers/:id", to: "answers#new", as: :new_answer
  resources :answers, except: %i(new)

●解説

1.アソシエーションでquestionを取得することができる。

app/contorollers/answers_controlelr.rb
  def new
    @question = Question.find(params[:id])
    @answer = @question.answers.build
  end

こうすることで、@answer = @question.answersとすることで、@answer.questionのような形で、questionを取得することができます。例えば下記のように活用したり。

app/contorollers/answers_controlelr.rb
def create
redirect_to question_path(@answer.question)

2.@questionを取得することで、"answers/new" ページに質問の内容を記載することができる。

get  "/answers/:id", to: "answers#new", as: :new_answer
  resources :answers, except: %i(new)

こうすることで、/new/:question_id とすることができます。
この記述がないと、/new.question_id となってしまい、パラメーターを渡すことができません。

●まとめ

あくまで自分はポートフォリオを作成している段階のレベル感です。
もっと良い実装方法や誤りがありましたら、指摘して頂けますと大変助かります。

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