- 投稿日:2020-10-06T22:53:30+09:00
ActiveStorageを使って、画像投稿機能実装までの流れ
はじめに
よくSNSで画像をあげるような機能があるが、それをRailsで実装するときに使うGemを紹介する。長くなるため、今回は実装の準備段階に絞って投稿する。
全2回の予定。Active Storageとは
現在はGemとしてインストールしなくても、Railsに搭載されている。画像などのファイルのアップロードを簡単に行えるメソッドが使えるようになる、画像を保存するテーブルの作成も簡単に行える。
画像アップロード機能の実装までの流れ
- ImageMagickのインストール
- 2つのGemのインストール
- ローカルサーバーの再起動
- Active Strageのインストール
- テーブルの生成
1.ImageMagickのインストール
そもそもImageMagickとは、画像加工ツールであり、Gemではなく、ソフトウェアの部類になる。Homebrewからインストールを行う場合は、
brew install imagemagickImageMagickだけでは、Rubyで扱えないので、次の2つのGemをインストールする必要がある。
2.2つのGemのインストール
(1)MiniMagick
ImageMagickの機能がRubyで使えるようになる。
(2)ImageProcessing
MiniMagickだけではできない、画像のサイズの調整をする。Gemfilegem 'mini_magick' gem 'image_processing', '~>1.2' #バージョンの指定Gemfileのいちばん下でOK。
記述をしたら、忘れずに、ターミナルで、bundle install3.ローカルサーバーの再起動
rails sGemを新たにインストールしたときは、忘れずに。
4.Active Storageのインストール
晴れて、ActiveStorageが使えるようになったので、
ターミナルを使って、インストールrails active_storage:installインストールすると、マイグレーションファイルが自動で生成される。
5.テーブルの生成
特にカラムの変更がなければ、そのまま、
rails db:migrateこのマイグレーションによって、2つのテーブルが生成されることを確認。
ポイント
- ActiveStorageを使って画像アップロード機能を実装する。
- Rubyで使えるようにするために、2つのGemをインストールする。
最後に
次回、画像の保存方法、保存した画像の表示方法についてまとめる。
- 投稿日:2020-10-06T22:44:39+09:00
本日の『railsチュートリアル』で詰まったところ(2020/10/06)
詰まったところ
1.3.4 Hello, world!
・事象:
$ git push heroku master実行後、URLを開くとMethod Not Allowedと表示される。
・解決方法: 開くURLが間違っていた。
・留意点
Heroku導入は難しくて理解を飛ばしたため、振り返る必要あり。感想
・1章読了!
・pushとcommitを頭の中でイメージできるようになってきた。
・もしかしてQiitaの使い方間違ってる?日付毎に新しい記事を量産するのではなく、1記事に追記していく形がいいのかもしれない。Qiitaルール要確認。
・Qiitaのマークダウン記法は読んだので多少記事が見やすくなったはず。
- 投稿日:2020-10-06T22:26:34+09:00
ヘッダーやフッターを一度作って別のページでも流用する方法
フッターやヘッダーを作ったら他のページにも使いたい!
ポートフォリを制作中
フッターやヘッダーをページが変わるたびに一から作り直すのかなり面倒だなと感じ調べたらすぐに出てきたので共有させてください!結論
このapp > layouts >application.html.erb
の中にあるapplication.html.erb#開くとすでに記述してあります <!DOCTYPE html> <html> <head> <title>PhotoRoke</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <body> #ここにヘッダーの記述をする <%= yield %> #ここにフッターの記述をする </body> </html><%= yield %>
をフッターとヘッダーで挟む感じで記述しましょう!yieldメソッドって何?
レイアウトテンプレートに、各テンプレートファイルを展開するためのメソッドです。とのこと
レイアウトテンプレートとはRailsでいう先ほど編集した applocation.html.erb のことです。
Railsでいうと、ということは他の言語では違うファイルがレイアウトテンプレートなのかもしれませんyieldメソッドが何をしているか
すごく簡単にいうと
yield = トップページのHTML
yield = 新規投稿ページのTHML
yield = その他のページのHTMI
のようにyieldに各ページのHTMLが中に入る終わり
以上になます!
手を抜くプログラミングを行っていきましょう
ご高覧いただきありがとうございました!
- 投稿日:2020-10-06T22:01:10+09:00
【No.008】ActionView::Componentを導入して、ナビバーを全画面に適応させる
github/view_component
[Rails 6.1] ActionView::Component について"
ActionView::Componentの導入概要
ActionView::Componentを導入して、ナビバーを全画面に適応させる
ToDo詳細
- ActionView::Componentのインストール
Gemfilegem "view_component", require: "view_component/engine"Terminalbundle install
- navbar部分をcomponent化
Terminal-> % bin/rails generate component Layout::Navbar org Running via Spring preloader in process 30401 create app/components/layout/navbar_component.rb invoke test_unit create test/components/layout/navbar_component_test.rb invoke slim create app/components/layout/navbar_component.html.slim
- app/views/orders/ordering_org_sides/index.html.slimにnavbarのcomponentを適応
app/views/orders/ordering_org_sides/index.html.slim.bg-gray-100 = render(::Layout::NavbarComponent.new(org: @org)) .py-10 .max-w-7xl.mx-auto.sm:px-6.lg:px-8 .py-5 .-my-2.overflow-x-auto(class="sm:-mx-6 lg:-mx-8") .py-2.align-middle.inline-block.min-w-full(class="sm:px-6 lg:px-8") .shadow.overflow-hidden.border-b.border-gray-200(class="sm:rounded-lg") table.min-w-full.divide-y.divide-gray-200 thead tr th.px-6.py-3.bg-gray-200.text-left.text-xs.leading-4.font-medium.text-gray-500.uppercase.tracking-wider | 注文No th.px-6.py-3.bg-gray-200.text-left.text-xs.leading-4.font-medium.text-gray-500.uppercase.tracking-wider | 商品画像 th.px-6.py-3.bg-gray-200.text-left.text-xs.leading-4.font-medium.text-gray-500.uppercase.tracking-wider | 販売元ページ th.px-6.py-3.bg-gray-200.text-left.text-xs.leading-4.font-medium.text-gray-500.uppercase.tracking-wider | 買付先ページ th.px-6.py-3.bg-gray-200.text-left.text-xs.leading-4.font-medium.text-gray-500.uppercase.tracking-wider | 色・サイズ等 th.px-6.py-3.bg-gray-200.text-left.text-xs.leading-4.font-medium.text-gray-500.uppercase.tracking-wider | 数量 th.px-6.py-3.bg-gray-200.text-left.text-xs.leading-4.font-medium.text-gray-500.uppercase.tracking-wider | お届け先 th.px-6.py-3.bg-gray-200.text-left.text-xs.leading-4.font-medium.text-gray-500.uppercase.tracking-wider | 買付費用 th.px-6.py-3.bg-gray-200.text-left.text-xs.leading-4.font-medium.text-gray-500.uppercase.tracking-wider | ステータス th.px-6.py-3.bg-gray-200.text-left.text-xs.leading-4.font-medium.text-gray-500.uppercase.tracking-wider | 編集 tbody.bg-white.divide-y.divide-gray-200 - @orders.each do |order| tr td.px-6.py-4.whitespace-no-wrap.text-sm.leading-5.text-gray-500 = order.trade_no td.px-6.py-4.whitespace-no-wrap.text-sm.leading-5.text-gray-500 | 商品画像 td.px-6.py-4.whitespace-no-wrap.text-sm.leading-5.text-gray-500 a.text-indigo-600.hover:text-indigo-900 href="#" 販売元ページ td.px-6.py-4.whitespace-no-wrap.text-sm.leading-5.text-gray-500 a.text-indigo-600.hover:text-indigo-900 href="#" 買付先ページ td.px-6.py-4.whitespace-no-wrap.text-sm.leading-5.text-gray-500 = order.color_size td.px-6.py-4.whitespace-no-wrap.text-sm.leading-5.text-gray-500 = order.quantity td.px-6.py-4.whitespace-no-wrap.text-sm.leading-5.text-gray-500 | 〒#{order.postal} br = order.address td.px-6.py-4.whitespace-no-wrap.text-sm.leading-5.text-gray-500 | $100 td.px-6.py-4.whitespace-no-wrap.text-sm.leading-5.text-gray-500 = order.status_i18n td.px-6.py-4.whitespace-no-wrap.text-right.text-sm.leading-5.font-medium a.text-indigo-600.hover:text-indigo-900 href="#" i.fas.fa-edit
- app/views/orgs/index.html.slimにnavbarのcomponentを適応
app/views/orgs/index.html.slim= render(::Layout::NavbarComponent.new(org: @org)) .bg-white.shadow.m-auto.sm:rounded-md.mt-5(class="w-2/4") ul - @orgs.each do |org| / TODO:Sassで場合分けできるようにする - border_t = org == @orgs.first ? '' : 'border-t border-gray-200' li.(class=border_t) = link_to [org], class: 'block hover:bg-gray-50 focus:outline-none focus:bg-gray-50 transition duration-150 ease-in-out' do .flex.items-center.px-4.py-4.sm:px-6 .min-w-0.flex-1.flex.items-center .min-w-0.flex-1.px-4.md:grid.md:grid-cols-2.md:gap-4 div .text-sm.leading-5.font-medium.text-indigo-600.truncate = org.name div i.fas.fa-sign-in-alt.fa-lg.bg-gray-50.text-gray-500
- app/views/orgs/show.html.slimにnavbarのcomponentを適応
app/views/orgs/show.html.slim= render(::Layout::NavbarComponent.new(org: @org)) .bg-white.shadow.m-auto.mt-5.overflow-hidden.sm:rounded-lg(class="w-1/2") .px-4.py-5.border-b.border-gray-200.sm:px-6 h3.text-lg.leading-6.font-medium.text-gray-900 | 会社詳細 p.mt-1.max-w-2xl.text-sm.leading-5.text-gray-500 | 会社詳細を説明します。 div dl .bg-gray-100.px-4.py-5.sm:grid.sm:grid-cols-3.sm:gap-4.sm:px-6 dt.text-sm.leading-5.font-medium.text-gray-500 | 会社名 dd.mt-1.text-sm.leading-5.text-gray-900.sm:mt-0.sm:col-span-2 = @org.name .bg-white.px-4.py-5.sm:grid.sm:grid-cols-3.sm:gap-4.sm:px-6 dt.text-sm.leading-5.font-medium.text-gray-500 | 会社タイプ dd.mt-1.text-sm.leading-5.text-gray-900.sm:mt-0.sm:col-span-2 / TODO:enum化する = @org.org_type動作確認
準備
bin/rails db:migrate:reset bin/rails db:reset受入基準
- 下図のように全画面にnavbarが適応されている
- 投稿日:2020-10-06T20:45:32+09:00
Ruby 3.0 で x ** 2 が速くなった件
x ** 2 は遅い
変数
xに Integer とか Float とかの数値が入っているとする。
xの 2 乗の計算は,x ** 2と書けるが,もしできるだけ高速に計算させたいなら
x * xと書いたほうがいい。
後者のほうが断然速い。
というか前者はめちゃんこ遅い。ベンチマークテスト
まず
gem install benchmark_driverして benchmark_driver をインストールしておき,
square.yamlprelude: | x = 3.0 benchmark: - "x ** 2" - "x * x"という YAML ファイルを用意して,
benchmark-driver square.yamlとやると比較できる。
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-darwin17]で計測してみたところ,結果は以下のとおり。x * x: 92940175.9 i/s x ** 2: 15605744.0 i/s - 5.96x slowerわお!
まあ,遅いったって,毎秒 1500 万回実行できるわけだから,この遅さが問題になるのは大量の数値計算をやる場合に限られるわけだけれど1。
Ruby 3.0 で改善
Float に対する
** 2を特別扱いして速く計算するような変更が入った。
Ruby 3.0.0-preview1 で確認できる。rbenv で Ruby を入れている方は benchmark_driver で簡単にテストできる。
さきほどと同じ YAML ファイルに対して,こんどはbenchmark-driver square.yaml --rbenv "2.7.2;3.0.0-preview1"とsる。
結果は以下のとおり。x ** 2 3.0.0-preview1: 37039739.4 i/s 2.7.2: 16010580.7 i/s - 2.31x slower x * x 2.7.2: 91881050.6 i/s 3.0.0-preview1: 88138626.5 i/s - 1.04x slower
x * xで,むしろ3.0.0-preview1のほうが遅いが,これは誤差の範囲。
x ** 2のほうは,2.3 倍速になった。感想
まあそれでも
x * xのほうがx ** 2より倍以上速いわけだけど,それでも重要な進化だと思う。
世の中にx ** 2のようなコードはたくさんあって,それらが何もしなくても速くなってくれるわけだから。場合によっては速度を犠牲にしてあえて
** 2を使いたいときもある。
それは 2 乗したいものが変数に入っていなくて,式のとき。
まあ,こういうイメージかな:items.map.with_index{ |item, i| (a * item.value + b[i]) ** 2 }これを
items.map.with_index{ |item, i| (a * item.value + b[i]) * (a * item.value + b[i]) }などとすると,冗長なばかりか,おそらくかえって速度を落とすことになる。
そこで,いったん式の値を変数に入れてitems.map.with_index{ |item, i| x = a * item.value + b[i]; x * x }と書くことになる。
速度が最優先ならこう書くところだが,「速度もそこそこ求めるけど簡潔さも欲しい」といった場合は** 2を使うこともあるだろう。
プログラムを高速化したいとき,ベンチマークテストをやってきちんと数字を追うことが大事なのと,プログラム全体の実行時間を左右しない箇所の最適化に血道を上げても無意味,ということは肝に銘じておきたい。 ↩
- 投稿日:2020-10-06T20:28:35+09:00
ActionView::Componentの導入
github/view_component
[Rails 6.1] ActionView::Component について
PR準備
Terminalrails new view_component_app -d postgresql環境
Gemfileruby '2.7.1' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 6.0.3', '>= 6.0.3.3' # Use postgresql as the database for Active Record gem 'pg', '>= 0.18', '< 2.0'Slim入れる
Gemfilegem 'slim-rails' gem 'html2slim'Terminalbundle installTailwind入れる
ref: tailwindcss Documentation
1.Install Tailwind via npm
# Using npm npm install tailwindcss # Using Yarn yarn add tailwindcss2.Add Tailwind to your CSS
app/javascript/src/scss/application.scssを追加
@import "tailwindcss/base"; @import "tailwindcss/components"; @import "tailwindcss/utilities";app/javascript/packs/application.jsに下記追加
import '../src/scss/application.scss'3.Create your Tailwind config file (optional)
npx tailwindcss init4.Process your CSS with Tailwind
postcss.config.jsに下記追加
module.exports = { plugins: [ // ... require('tailwindcss'), require('autoprefixer'), // ... ] }Scaffold
Terminalbin/rails g scaffold blog content:textroutes.rbRails.application.routes.draw do root 'blogs#index' #追加 resources :blogs # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html endDB作成
Terminalbin/rails db:create bin/rails db:migrateActionView::Componentの導入
インストール
Gemfilegem "view_component", require: "view_component/engine"Terminalbundle installサンプル1
コンポーネントを用意
Terminal-> % bin/rails generate component Example title content Running via Spring preloader in process 24985 create app/components/example_component.rb invoke test_unit create test/components/example_component_test.rb invoke slim create app/components/example_component.html.slimapp/components/example_component.rbclass ExampleComponent < ViewComponent::Base def initialize(title:, content:) @title = title @content = content end endapp/components/example_component.html.slimdiv = @title div = @content呼び出し元
app/views/blogs/index.html.slimh1 Listing blogs table thead tr th Content th th th tbody - @blogs.each do |blog| tr td = blog.content td = link_to 'Show', blog td = link_to 'Edit', edit_blog_path(blog) td = link_to 'Destroy', blog, data: { confirm: 'Are you sure?' }, method: :delete br = link_to 'New Blog', new_blog_path = render(ExampleComponent.new(title: 'my title', content: 'my content'))表示
サンプル2
コンポーネントを用意
Terminalbin/rails generate component Test titleapp/components/test_component.rbclass TestComponent < ViewComponent::Base def initialize(title:) @title = title end endapp/components/test_component.html.slimdiv span(title=@title) = content呼び出し元
app/views/blogs/index.html.slimh1 Listing blogs table thead tr th Content th th th tbody - @blogs.each do |blog| tr td = blog.content td = link_to 'Show', blog td = link_to 'Edit', edit_blog_path(blog) td = link_to 'Destroy', blog, data: { confirm: 'Are you sure?' }, method: :delete br = link_to 'New Blog', new_blog_path div.mt-3 | サンプル1 = render(ExampleComponent.new(title: 'my title', content: 'my content')) div.mt-3 | サンプル2 = render(TestComponent.new(title: "my title")) do | Hellow, World!表示
- 投稿日:2020-10-06T19:49:33+09:00
Ruby 2.7.2 がリリースされ、今後は非推奨な警告はデフォルトでは出なくなる
ブログ記事からの転載です。
先日 Ruby 2.7.2 がリリースされました。
このリリースで先日お伝えした『非推奨な警告がデフォルトでは出なくなる』ようになりました。
なので、今後は非推奨な機能が削除されるといきなりアプリケーションが動作しなくなる可能性があります。
このような問題を回避するために今後は以下のようにして『明示的に非推奨な警告が出力されるようする』ことで安全に開発する事ができます。
-wや-W:deprecatedを付けて Ruby を実行す- コード上に
Warning[:deprecated] = trueを追記する参照
- 投稿日:2020-10-06T19:11:43+09:00
フリマアプリ作成におけるJSを用いたカテゴリ選択フォームの実装
はじめに
某プログラミングスクールの最終課題でフリマアプリのクローンを作成中です。
商品出品機能に必要な「カテゴリ選択機能」の実装において作業した内容について記載していきます。
- JavaScriptを用いたプルダウン選択フォームの作成
※この前段階として、ancestryを使って使用するカテゴリデータのテーブルを作っています。
その記事はコチラ▶︎ancestryを用いた階層型カテゴリデータの作成方法この記事でわかること
- Ajax通信で3段階のカテゴリ選択フォームを表示させる
この機能で達成したいゴール
下記のように親要素(レディース)選択したら、子要素の選択フォームが、子要素を選択したら孫要素の選択フォームが非同期通信で表示されるようにします。
また、子や孫の選択肢を変えたらフォーム内容が初期化されたり、フォームそのものが消えたりします。
開発環境
- ruby 2.6.5
- rails 6.0.3.2
- sequel pro
大まかな流れ
- gemのインストール
- JSを使うためのコントローラー、JSファイルを作成し、呼び出しの記述をする。
- ルーティングを追加する。
- コントローラーにデータを検索する#search機能を設定する。
- jsファイルを編集。
- Ajax通信時に用いるjson.jbuilderファイルを編集。
- 上記4−6を子要素、孫要素で繰り返す。
- 子要素・孫要素を変更した際のアクションを追加する。
- 完成
前提として、
- ancestryを用いたデータ作成は完了していることとします。
*ancestryを使ったカテゴリデータ作成方法はコチラをクリック
- itemsコントローラーも作成済とする。具体的な実装手順
0. gemのインストール
まずは必要なgemをインストール。
gem jquery-railsbundle install1.JavaScriptを使うための記述をする
app/javascript/packs/application.jsrequire('jquery') require('item/category')config/webpack/environment.jsconst { environment } = require('@rails/webpacker') const webpack = require('webpack') environment.plugins.prepend('Provide', new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', jquery: 'jquery', }) ) module.exports = environmentここで早速つまづいたのが、わたしの開発環境がrails6だったこと。
ネットで調べた記事のほとんどがrails5以前での情報になっており、
rails6ではapplication.jsファイルの生成場所や呼び出しのための記述が違うらしく、
最初うまく読み込まれなくて混乱しました。開発の際はバージョンを確認の上、各自の環境にあわせて記述してください。
rails6でのjquery導入はこの記事を参考にさせていただきました。=>rails6でのjqueryの導入方法
2.ルーティングを追加
のちほどitemsコントローラーにsearchアクションを追加するため、
searchアクションへのルーティングを追記します。routes.rbRails.application.routes.draw do resources :items do collection do get :search end end end3.ビューにカテゴリ選択フォームを作成
こちらは、あくまで私の記述なの参考までに。
このような形でまずは親カテゴリ用のフォームを作成しました。qpp/view/items/new.html.haml=form_with model @item do |f| = f.collection_select :category_id, @parents, :id, :name, {prompt: "選択してください"}, {class: "categoryForm", id: "category_form"}4.コントローラーにsearchアクションを追加
newアクションでレコードを取得。
items_controllerdef new @item = Item.new @item_image = ItemImage.new endまた、newアクションとcreateアクションの前に親要素の情報を取得しておきたいので、before_actionを設定しました。
ite,s_controller.rbbefore_action :set_parents, only: [:new, :create] def set_parents @parents = Category.where(ancestry: nil) endancestry=nil、つまり親要素の値を取得し、変数@parentsへ代入しています。
newだけではなく、createの時にもset_parentsが必要な理由は、
コチラの記事を参考にさせていただきました。
(正直、コード書いている時はわかったような、わからないような...でした。)
そして、子・孫のカテゴリ検索のため、searchアクションを設定します。items_controller.rbdef search respond_to do |format| format.html #ajax通信開始 format.json do #子カテゴリの情報を@childrensに代入 @childrens = Category.find(params[:parent_id]).children end end end余談ですが、複数形の"children"に更に"s"を付けるのにどうしても違和感があったのですが、
childとchildrenを使いわけるのもわかりにくいなぁと思い、仕方なくchildrensにしました。5.json.jbulderファイルを作成
app/view/items/search.json.jbuilderjson.array! @childrens do |children| json.id children.id json.name children.name endとりあえず、いまは子要素の分のみ記載しています。
6.jsファイルを編集し、上記アクションで送られてくるデータを受け取る
1.まずは、親要素を選択した後に現れる「子要素の選択フォーム」を作成。
category.js// 子要素を選択するフォーム function add_childSelect_tag() { let child_select_form = ` <select name="item[category_id]" id="item_category_id" class="child_category_id"> <option value="">カテゴリを選択</option> </select> ` return child_select_form; }このHTMLの部分を書くのが苦手なのですが、ビューの検証ツールで親要素のフォームをみて書くのが一番簡単な気がしています。
name="item[category_id]はデータの送り先です。
あとで、子要素のフォームを消したり初期化したりしたい(親要素を変更したときなど)ので、子要素特有のclass名も追加しました。
2.表示された選択フォームにデータを取得するためのoptionを記載。category.jsfunction add_Option(children) { let option_html = ` <option value=${children.id}>${children.name}</option> ` return option_html; }
3.親カテゴリを選択した後に起こるイベントを設定する。category.js//親カテゴリを選択したあとのイベント $("#category_form").on("change", function() { let parentValue = $("#category_form").val(); if (parentValue.length !== 0) { $.ajax({ url: '/items/search', type: 'GET', data: { parent_id: parentValue}, dataType: 'json' }) .done(function (data){ let child_select_form = add_childSelect_tag $(".ItemInfo__category--form").append(child_select_form); data.forEach(function(d){ let option_html = add_Option(d) $(".child_category_id").append(option_html); }); }) .fail(function (){ alert("カテゴリ取得に失敗しました"); });おおまかな流れとしては、
1. 親要素のデータを取得し、parentValueへ代入。
2. if〜でデータが初期値でなければajax通信。
3. .doneでappendを使って、親要素のフォームの後に前手順で作成した子要素のフォームを表示させる。
4. optionでとりだしたデータをひとつずつ取り出して表示させる。これで子要素の表示は完成!
親要素を選択すれば、子要素フォームが表示されます。4.孫要素の表示を作成する。
基本的には、子要素の手順を繰り返すだけです。
app/views/items/search.json.jbuilderjson.array! @grandchildrens do |grandchildren| json.id grandchildren.id json.name grandchildren.name endcategory.js// 孫要素の選択フォーム function add_grandchildSelect_tag(){ let grandchild_select_form = ` <select name="item[category_id]" id="item_category_id" class="grandchild_category_id"> <option value="">カテゴリを選択</option> </select> ` return grandchild_select_form }categroy.js// 子カテゴリを選択後のイベント $(document).on("change", ".child_category_id", function(){ let childValue = $(".child_category_id").val(); if (childValue.length !=0){ $.ajax({ url: '/items/search', type: 'GET', data: { children_id: childValue}, dataType: 'json' }) .done(function (gc_data){ let grandchild_select_form = add_grandchildSelect_tag $(".ItemInfo__category--form").append(grandchild_select_form); gc_data.forEach(function (gc_d){ let option_html = add_Option(gc_d); $(".grandchild_category_id").append(option_html); }) }) .fail(function (){ alert("カテゴリ取得に失敗しました"); }); })これで、子要素を選択すれば孫要素の選択フォームがでてくるようになります。
5.フォームを初期化する記述を追加する。
1-4の手順で一見完成したように見えるのですが、
このままだと以下のような不具合がでます。
- 親や子カテゴリを変更しても子カテゴリの内容がそのまま。
- 子や孫カテゴリを変更すると、孫の下に更に新たなカテゴリが出現してしまう。要するに、子や孫カテゴリをいじっていると、エンドレスでフォームが追加されていってしまう状態。
なので、望ましい挙動として、
- 親カテゴリを変更したら、孫カテゴリフォームは消えて、子カテゴリのフォームの内容は親カテゴリに紐づいた内容に変わる。
- 子カテゴリを変更したら、孫カテゴリの内容は子カテゴリに紐づいたデータに変わる。
- 親や子カテゴリを初期値(選んでいない状態)にしたら、その次の階層のフォームは消える。
この動作を実現させないといけません。
そのために、下記の記述を加えました。category.js// 親カテゴリを選択したあとのイベント $("#category_form").on("change", function() { let parentValue = $("#category_form").val(); if (parentValue.length !== 0) { $.ajax({ url: '/items/search', type: 'GET', data: { parent_id: parentValue}, dataType: 'json' }) .done(function (data){ $(".child_category_id").remove(); $(".grandchild_category_id").remove(); let child_select_form = add_childSelect_tag $(".ItemInfo__category--form").append(child_select_form); data.forEach(function(d){ let option_html = add_Option(d) $(".child_category_id").append(option_html); }); }) .fail(function (){ alert("カテゴリ取得に失敗しました"); }) }else{ $(".child_category_id").remove(); $(".grandchild_category_id").remove(); } });追加したのは、①.doneのあとのこの2行と
$(".child_category_id").remove(); $(".grandchild_category_id").remove();②.failのあとにelseで条件分けしたこの部分です。
else{ $(".child_category_id").remove(); $(".grandchild_category_id").remove(); }①で親カテゴリの内容が変わるたびに子/孫カテゴリを一度消し、その上で子カテゴリを出現させるようにしました。
②のelse文の後ろは、親が初期値を選択した場合。同じコードを子カテゴリ選択後のイベント部分にも追記しました。
そして、全部ページを読み込んでからこれらのJSを作動させるため、
category.jswindow.addEventListener('load', function () { }これでコード全体を括りました。
これで本当に完成!!
このあと、うっかりチームメンバーに「db:seedしてね」と言うのを忘れ、
「カテゴリ選択できない!!なぜ?!」という事態もおきましたが、無事に解決にいたりました。参考にした記事
[Rails]某フリマアプリのカテゴリー機能の実装方法
フリマアプリのカテゴリ機能〜gem : ancestryを使用〜
[Rails] Ajax通信を用いたカテゴリボックス作成
- 投稿日:2020-10-06T18:59:09+09:00
アクション内でrenderを使わずにバリデーションエラーや入力していた内容を前ページに反映する方法
はじめに
フォーム内容を送信する際、バリデーションに引っ掛かったら
renderメソッドで新規投稿画面をレンダリングして
そこでエラーなり入力していた内容なりを表示すると学習した方が多いはずしかし、
renderメソッドが使えない時はどうしますか?
実は制作中のポートフォリオにGoogleMapAPIを使用しているのですが
renderでページをレンダリングするとなぜかMAPが表示しなくなる不具合が発生...仕様を変えれば解決できる問題だったのですが、変えたくなかったので
renderを使わずに上記の動きを実現する方法を紹介します。通常renderを使った場合
例として
tasks_controller.rbに
新規投稿ページのアクションnewと、
新規投稿内容を保存するアクションcreateがあるとします。tasks_controller.rbdef create @task = Task.new(task_params) if @task.save redirect_to @task, notice: 'タスクを保存しました。' else render :new end end
@task.saveが失敗しelse以下のrender :newが実行され、
新規投稿ページがレンダリングされます。
あとはもうご存知のとおり、viewのほうで@task.errors.full_messagesを活用してく感じですね。renderを使わない場合
redirect_toで再アクセスをするという形をとり、
エラー文や入力内容はアクション内でflashに格納してしまいます。tasks_controller.rbdef create @task = Task.new(task_params) if @task.save redirect_to @task, notice: 'タスクを保存しました。' else flash[:error_msgs] = @task.errors.full_messages flash[:tmp_body] = @task.body redirect_to new_task_url end end
flash[:error_msgs]にはエラー文を、
flash[:tmp_body]には文章内容を、それぞれ格納しておきます。あとはアクセス先のviewで、flashの値を活用してすればOKです。
以上!
- 投稿日:2020-10-06T18:47:27+09:00
ac-library-rb で解く AtCoder 優先度付きキュー(Priority Queue)
はじめに
AtCoder Problems の Recommendation を利用して、過去の問題を解いています。
AtCoder さん、AtCoder Problems さん、ありがとうございます。今回のお題
AtCoder Beginner Contest D - Powerful Discount Tickets
Difficulty: 826AtCoder CODE FESTIVAL 2016 qual C B - K個のケーキ
Difficulty: 905今回のテーマ、優先度付きキュー
ac-library-rb は、AtCoder Library (ACL)のRuby版です。
今回はその中の
優先度付きキュー(Priority Queue)を使用しています。
JavaやPythonですと標準ライブラリに優先度付きキューがありますが、Rubyにはないので待望のライブラリです。D - Powerful Discount Tickets
ruby.rb# frozen_string_literal: true # Priority Queue # Reference: https://github.com/python/cpython/blob/master/Lib/heapq.py class PriorityQueue # By default, the priority queue returns the maximum element first. # If a block is given, the priority between the elements is determined with it. # For example, the following block is given, the priority queue returns the minimum element first. # `PriorityQueue.new { |x, y| x < y }` # # A heap is an array for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for all k, counting elements from 0. def initialize(array = [], &comp) @heap = array @comp = comp || proc { |x, y| x > y } heapify end attr_reader :heap # Push new element to the heap. def push(item) shift_down(0, @heap.push(item).size - 1) end alias << push alias append push # Pop the element with the highest priority. def pop latest = @heap.pop return latest if empty? ret_item = heap[0] heap[0] = latest shift_up(0) ret_item end # Get the element with the highest priority. def get @heap[0] end alias top get # Returns true if the heap is empty. def empty? @heap.empty? end private def heapify (@heap.size / 2 - 1).downto(0) { |i| shift_up(i) } end def shift_up(pos) end_pos = @heap.size start_pos = pos new_item = @heap[pos] left_child_pos = 2 * pos + 1 while left_child_pos < end_pos right_child_pos = left_child_pos + 1 if right_child_pos < end_pos && @comp.call(@heap[right_child_pos], @heap[left_child_pos]) left_child_pos = right_child_pos end # Move the higher priority child up. @heap[pos] = @heap[left_child_pos] pos = left_child_pos left_child_pos = 2 * pos + 1 end @heap[pos] = new_item shift_down(start_pos, pos) end def shift_down(star_pos, pos) new_item = @heap[pos] while pos > star_pos parent_pos = (pos - 1) >> 1 parent = @heap[parent_pos] break if @comp.call(parent, new_item) @heap[pos] = parent pos = parent_pos end @heap[pos] = new_item end end HeapQueue = PriorityQueue n, m = gets.split.map(&:to_i) a = gets.split.map(&:to_i) h = PriorityQueue.new(a) m.times do x = h.pop h.push(x / 2) end puts h.heap.sumB - K個のケーキ
ruby.rb# frozen_string_literal: true # Priority Queue # Reference: https://github.com/python/cpython/blob/master/Lib/heapq.py class PriorityQueue # By default, the priority queue returns the maximum element first. # If a block is given, the priority between the elements is determined with it. # For example, the following block is given, the priority queue returns the minimum element first. # `PriorityQueue.new { |x, y| x < y }` # # A heap is an array for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for all k, counting elements from 0. def initialize(array = [], &comp) @heap = array @comp = comp || proc { |x, y| x > y } heapify end attr_reader :heap # Push new element to the heap. def push(item) shift_down(0, @heap.push(item).size - 1) end alias << push alias append push # Pop the element with the highest priority. def pop latest = @heap.pop return latest if empty? ret_item = heap[0] heap[0] = latest shift_up(0) ret_item end # Get the element with the highest priority. def get @heap[0] end alias top get # Returns true if the heap is empty. def empty? @heap.empty? end private def heapify (@heap.size / 2 - 1).downto(0) { |i| shift_up(i) } end def shift_up(pos) end_pos = @heap.size start_pos = pos new_item = @heap[pos] left_child_pos = 2 * pos + 1 while left_child_pos < end_pos right_child_pos = left_child_pos + 1 if right_child_pos < end_pos && @comp.call(@heap[right_child_pos], @heap[left_child_pos]) left_child_pos = right_child_pos end # Move the higher priority child up. @heap[pos] = @heap[left_child_pos] pos = left_child_pos left_child_pos = 2 * pos + 1 end @heap[pos] = new_item shift_down(start_pos, pos) end def shift_down(star_pos, pos) new_item = @heap[pos] while pos > star_pos parent_pos = (pos - 1) >> 1 parent = @heap[parent_pos] break if @comp.call(parent, new_item) @heap[pos] = parent pos = parent_pos end @heap[pos] = new_item end end HeapQueue = PriorityQueue gets a = gets.split.map(&:to_i) h = PriorityQueue.new(a) while h.heap.size > 1 u = h.pop v = h.pop h.push(u - 1) if u - 1 > 0 h.push(v - 1) if v - 1 > 0 end if h.heap.size.zero? puts 0 else puts h.pop - 1 end
D - Powerful Discount Ticketsは、実行時間が 880 -> 395 msと速くなっていい感じですね。まとめ
- 優先度付きキュー(Priority Queue) を解いた
- ACL に詳しくなった
- Ruby に詳しくなった
参照したサイト
ac-library-rb - GitHub
Ruby と Python と Java で解く AtCoder ABC141 D 優先度付きキュー
Ruby と Python で解く AtCoder CODE FESTIVAL 2016 qual C B 優先度付きキュー
- 投稿日:2020-10-06T17:26:29+09:00
モモンガプロジェクトの開発者-最近
- 投稿日:2020-10-06T16:56:50+09:00
AWS SDK for Ruby profile変更 & assume role
AWS SDK for Rubyにてcredentialsのprofile指定とassume role方法についてわかりづらかったので記載
↓profileはaws-cliで下記のように作成した際のもの
$ aws configure --profile hogeprofile指定
Clientの引数に渡すだけ
例:EC2ec2 = Aws::EC2::Client.new( profile: "hoge", # ... )assume role
require 'aws-sdk-core' require 'aws-sdk-ec2' role_credentials = Aws::AssumeRoleCredentials.new( client: Aws::STS::Client.new(opts), role_arn: "arn:aws:iam::xxxxxxxxxxxx:role/hoge_role", role_session_name: hoge ) ec2 = Aws::EC2::Client.new( credentials: role_credentials, # ... )
- 投稿日:2020-10-06T15:31:48+09:00
RubyOnRailsで環境変数を使用する方法
■英語語の記事↓
How to Use Environment Variables in Ruby On Rails環境変数とは
各アプリケーションには、外部サービス用の電子メールアカウント資格情報やAPIキーなどの構成設定が必要です。環境変数を使用して、ローカル構成設定をアプリケーションに渡すことができます。
Ruby On Railsで環境変数を使用する方法はいくつかあり、FigaroGemのようなgemもあります。
この記事には、local_env.ymlファイルを使用して、実装の方法を共有させていただきます。環境変数ファイルをプライベートとして保持する
GitHubを使用してコードを保存および共有していて、プロジェクトがオープンソースである場合、開発者は誰でもコードにアクセスできます。 個人情報やAPIキーを一般の人と共有したくない、プライベートgitリポジトリを使用してチームで共同作業している場合、ローカル設定がチームのすべてのメンバーに適しているとは限りません。
local_env.ymlファイルを使用:
標準のYAMLファイル形式を使用して、各環境変数のキーと値のペアを含む単純なファイルを作成します。
config/local_env.ymlファイルを作成:
MAIL_USERNAME: 'Your_Username' MAIL_PASSWORD: 'Your_Username'.gitignoreに設定
アプリケーションのgitリポジトリを作成した場合、アプリケーションのルートディレクトリには.gitignoreという名前のファイルが含まれている必要があります。
.gitignoreファイルに以下の行を追加/config/local_env.ymlRailsアプリケーションファイルに設定
環境変数を設定後で、ファイル「local_env.yml」は「config/application.rb」に設定が必要です。
config/application.rbファイルに下記のコードを設定config.before_configuration do env_file = File.join(Rails.root, 'config', 'local_env.yml') YAML.load(File.open(env_file)).each do |key, value| ENV[key.to_s] = value end if File.exists?(env_file) end上記のコードは、local_env.ymlファイルから環境変数を設定します。
コードに環境変数を使用
RailsアプリケーションでENV ["MAIL_USERNAME"]を使用することができます。
例:ActionMailer::Base.smtp_settings = { address: "smtp.gmail.com", enable_starttls_auto: true, port: 587, authentication: :plain, user_name: ENV["MAIL_USERNAME"], password: ENV["MAIL_PASSWORD"], openssl_verify_mode: 'none' }コーディングを楽しみましょう!
![]()
ご不明な点がございましたらご連絡して頂ければと思います。
以上です。よろしくお願いいたします。
- 投稿日:2020-10-06T13:50:17+09:00
【Rails】migrate時にAnnotateが実行されない
はじめに
Annotateが実行できず、コメントが書き出されない時に試したことをまとめました。
環境
- Rails6.0.3
- Ruby2.7.1
- annotate3.1.1
Annotateとは
schemaに書かれている情報をルーティングの情報をファイルの先頭にコメントしてくれるgemです。
カラム情報やルーティングを確認する手間が省くことができます。
導入や詳細な使い方は下記を参考にしました。
【Rails】annotateの使い方発生したバグ(migrate時にコメントが書き出されない)
migrate時にannotateが実行され、modelにコメントされる設定にしているのに、実行されないという問題が発生しました。
作業ブランチで1つ目のmigrationファイルを作成して、migrateした時はコメントが書き出されたのに、作業同じブランチで2つ目のmigrationファイルを作成して、migrateしたら、コメントが書き出されませんでした。いろいろ試しましたが、最終的には一度コメントを削除してから、migrateしたら、上手く行きました。
やったこと
1. 設定ファイルを確認
$ bundle exec rails g annotate:installで生成されたlib/tasks/auto_annotate_models.rakeのAnnotateの設定を確認します。auto_annotate_models.rake'skip_on_db_migrate' => 'false',
falseになっているのでmigrate時にコメントが書き出される設定になっています。(ここは問題なし)2. 手動でAnnotateを実行
migarate時に実行されないので手動でAnnotateを実行します。
これでもコメントが書き出されない。。$ bundle exec annoatate --models3. gemを再インストール
それでもコメントが書き出されない場合は、gemを最インストールします。
再インストールすると直った場合もありました。4. annotateのコメントを削除する
私の場合、いろいろ試してもコメントが書き出されなかったので、コメントを削除することにしました。
$ bundle exec annotate --deletemodelからコメントが削除されたことを確認して、migrateもしくは手動で実行。
$ bundle exec rails db:migrate or $ bundle exec annoatate --models削除して、migrateすると全てのモデルに書き出されました。
おわりに
migrate時にannotateが実行されない原因は結局わかりませんでしたが、ひとまずコメントが書き出されました。
もし、原因がわかる方がいましたら、コメント頂けますと幸いです。
- 投稿日:2020-10-06T13:18:52+09:00
【Ruby on Rails】お問合せフォームの作成
目標
このような問い合わせフォームを作成します。
今回は開発環境でgmail宛に送信する方法です。
開発環境
ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina前提
※ ▶◯◯ を選択すると、説明等が出てきますので、
よくわからない場合の参考にしていただければと思います。bootstrap4導入済み
【Ruby on Rails】enumを日本語表記にする方法
お問い合わせ件名の選択に使用します。_i18nが使用されている箇所です。流れ
1 mailerを作成、編集
2 テーブルの作成
3 config/enviroments/development.rbの編集
4 環境変数の設定
5 controllerの作成
6 routing編集
7 viewの編集
8 googleアカウントで安全性の低いアプリのアクセスを許可するmailerを作成、編集
ターミナル$ rails g mailer ContactMailer送信先と件名を指定します。
ENV['TOMAIL']は後ほど設定します。app/mailers/thanks_mailer.rbclass ContactMailer < ApplicationMailer def send_mail(contact) @contact = contact mail to: ENV['TOMAIL'], subject: '【お問い合わせ】' + @contact.subject_i18n end endテーブルの作成
ターミナル$ rails g model Contactカラムは必要なものを追加してください。
enumの設定については次回記事で解説します。db/migrate/xxxxxxxxxxxxx_create_contacts.rbclass CreateContacts < ActiveRecord::Migration[5.2] def change create_table :contacts do |t| t.string :name, null: false t.string :email, null: false t.string :phone_number, null: false t.integer :subject, default: 0, null: false t.text :message, null: false t.timestamps end end endconfig/enviroments/development.rbの編集
config/enviroments/development.rb... config.action_mailer.raise_delivery_errors = false ↓ true に変更 config.action_mailer.raise_delivery_errors = true ... config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { port: 587, address: 'smtp.gmail.com', domain: 'smtp.gmail.com', user_name: ENV['SMTP_USERNAME'], password: ENV['SMTP_PASSWORD'], enable_starttls_auto: true } end環境変数の設定
今回はGitHub上にアドレスとパスワードをアップしないよう
gem "dotenv-rails"を使用します。Gemfilegem "dotenv-rails"ターミナル$ bundle install次にGemfileと同じ階層に .envファイルを作成
.envTOMAIL=送信先のアドレス SMTP_USERNAME=送信元のgmailアドレス SMTP_PASSWORD=送信元のgmailアドレスのパスワード
失敗談【SMTP_USERNAME】
はじめにSMTP_USERNAMEをMAILと設定しようとしたところ、
何度やってもエラーになりました。
なので、設定する際にはこのように表記をした方がいいかもしれません。
命名規則を勉強した上でまた更新したいと思います。.gitignoreファイルの一番下に下記を追加。
.gitignore/.envcontrollerの作成
ターミナル$ rails g controller contactsapp/controllers/contacts_controller.rbclass Public::ContactsController < ApplicationController def new @contact = Contact.new end # 確認画面を作成する場合はこのような記述になるかと思います。 # newアクションから入力内容を受け取り、 # 送信ボタンを押されたらcreateアクションを実行します。 def confirm @contact = Contact.new(contact_params) if @contact.invalid? render :new end end # 入力内容に誤りがあった場合、 # 入力内容を保持したまま前のページに戻るのが当たり前になっているかと思いますが、 # backアクションを定義することで可能となります。 def back @contact = Contact.new(contact_params) render :new end # 実際に送信するアクションになります。 # ここで初めて入力内容を保存します。 # セキュリティーのためにも一定時間で入力内容の削除を行ってもいいかもしれません。 def create @contact = Contact.new(contact_params) if @contact.save ContactMailer.send_mail(@contact).deliver_now redirect_to done_path else render :new end end # 送信完了画面を使用する場合お使いください。 def done end private def contact_params params.require(:contact) .permit(:email, :name, :phone_number, :subject, :message ) end endrouting編集
config/routesresources :contacts, only: [:new, :create] post 'contacts/confirm', to: 'contacts#confirm', as: 'confirm' post 'contacts/back', to: 'contacts#back', as: 'back' get 'done', to: 'contacts#done', as: 'done'viewの編集
app/views/contact_mailer配下に
send_mail.html.erb
send_text.html.erb を作成してください。
※なぜ2つ作成するかはわからないので、未来の自分に託したいと思います。。。
内容は同じ内容を記入してくださいapp/views/contact_mailer/send_mail.html.erb,send_text.html.erb<%= @contact.name %> 様 から問い合わせがありました。<br> 【Tel】:<%= @contact.phone_number %><br> 【Mail】:<%= @contact.email %><br> 【用件】:<%= @contact.subject_i18n %><br> 【お問い合わせ内容】<br> <span style="white-space: pre-wrap;"><%= @contact.message %></span>実際の入力フォームを作成していきます。
※名前とmessageのみ記述します。app/views/contacts/new.html.erb<%= form_for(@contact, url: confirm_path) do |f| %> <div class="form-group"> <%= f.label :name, 'お名前*' %> <%= f.text_field :name, autofocus: true, class: 'form-control' %> </div> <div class="form-group"> <%= f.label :message, 'メッセージ*' %> <%= f.text_area :message, size: '10x10', class: 'form-control' %> </div> <div> <%= f.submit '入力内容確認' %> </div> <% end %>確認画面から入力画面に戻るコードを記述します。
※名前とmessageのみ記述します。app/views/contacts/confirm.html.erb<table> <tbody> <tr> <td class="text-center" style="width: 30%;">お名前</td> <td><%= @contact.name %></td> </tr> <tr> <td class="text-center">メッセージ</td> <td style="white-space: pre-wrap;"><%= @contact.message %></td> </tr> </tbody> </table> <%= form_for(@contact) do |f| %> <%= f.hidden_field :name %> <%= f.hidden_field :email %> <%= f.hidden_field :phone_number %> <%= f.hidden_field :subject %> <%= f.hidden_field :message %> <div><%= f.submit '送信' %></div> <% end %> <%= form_for @contact, url: back_path do |f| %> <%= f.hidden_field :name %> <%= f.hidden_field :email %> <%= f.hidden_field :phone_number %> <%= f.hidden_field :subject %> <%= f.hidden_field :message %> <div><%= f.submit '入力画面に戻る' %></div> <% end %>
補足【<%= @contact.name %>】
new画面のフォームでconfirmアクションに飛ばしているため、
paramsでは@contactに値が入っている状態が作れています。
したがって保存せずとも表示が可能です。
補足【送信】
@contactで表示はされているものの、保存は出来ていない状態です。
このままcreateアクションに飛ばしてしまうと保存されない状態でparamsを投げてしまいます。
そこでf.hidden_fieldを使い、それぞれparamsを再代入し、createに飛ばしています。
googleアカウントで安全性の低いアプリのアクセスを許可する
Google 安全性の低いアプリのアクセスを有効にする方法
こちらの記事がわかりやすかったので参考にしてください。参考
- Railsとメール送信あれやこれや
- Ruby OnRailsで環境変数を使用する方法 ※英語です
- シンプルにお問い合わせ機能を実装する
- rails でお問合せフォームを作り、自分のメールアドレスに送信させる方法
まとめ
個人のアドレスや電話番号を扱うことになるため、
プライバシーポリシーや定期的な情報の削除は必須だと思います。
しかし、メールを相手に自動的に送信できるのは様々なこtにおいて優先順位は高めだと思いますので、
しっかり理解したほうがいいかもしれません。またtwitterではQiitaにはアップしていない技術や考え方もアップしていますので、
よければフォローして頂けると嬉しいです。
詳しくはこちら https://twitter.com/japwork
- 投稿日:2020-10-06T11:39:58+09:00
Mysqlの構文を使って計算した結果をWHEREで取り出す
はじめに
はじめての投稿です。
20代は不動産付け、30代未経験、就活一社でそのままその自社開発企業に内定頂きました駆け出しエンジニアです。
今日はMysqlについて書きます。土地のデータから一定の条件を満たすものを取り出すというプログラムです。
坪単価100万円以下の土地をピックアップします。create TABLEから。
-- テーブルをcreate (カラム:id :m2 :price) CREATE TABLE rands( id INT NOT NULL AUTO_INCREMENT, m2 INT, price INT, PRIMARY KEY (id) ); -- 次にデータをテーブルに入れ込みます INSERT INTO rands (m2, price) VALUES (200, 100000000), (300, 30000000), (500, 40000000), (100, 50000000); -- *(データぜんぶ)をSELECTしてrandsテーブルからWHEREで条件にあう形でデータを取り出します。 -- 「坪単価」なので定数3.30578を入れます。 SELECT * FROM rands WHERE price / m2 * 3.30578 < 1000000 ;今回はテーブルを作り、データを入れ込むところからやりましたが、
もし、データが蓄積されているような場合であれば、SELECT * FROM テーブル名 WHERE 条件;で取ってくることができますね。
以上です。※ INT:整数ですよ、という宣言です。
※ NOT NULL:NULLを許さないよという記述です。主キーなのにNULLではこまりますので。
※ AUTO_INCREMENT:自動でidを連番で入れ込んでくれる便利なメソッドです。
※ PRIMARY KEY (id):idカラムのデータが主キーですよ〜という宣言です。これがないとAUTO_INCREMENTは使えません。
※ INSERT INTO:これから先に宣言するデータを入れ込みますよ〜という記述です。
※ VALUES:値のことです。上で言うと、(m2,price)となっているので、(m2 => 200, price => 1000000)と言う意味です。
- 投稿日:2020-10-06T11:39:58+09:00
Mysqlの構文を使ってデータをWHEREで取り出す
はじめに
はじめての投稿です。
20代は不動産付け、30代未経験、就活一社でそのままその自社開発企業に内定頂きました駆け出しエンジニアです。
今日はMysqlについて書きます。土地のデータから一定の条件を満たすものを取り出すというプログラムです。
坪単価100万円以下の土地をピックアップします。create TABLEから。
-- テーブルをcreate (カラム:id :m2 :price) CREATE TABLE rands( id INT NOT NULL AUTO_INCREMENT, m2 INT, price INT, PRIMARY KEY (id) ); -- 次にデータをテーブルに入れ込みます INSERT INTO rands (m2, price) VALUES (200, 100000000), (300, 30000000), (500, 40000000), (100, 50000000); -- *(データぜんぶ)をSELECTしてrandsテーブルからWHEREで条件にあう形でデータを取り出します。 -- 「坪単価」なので定数3.30578を入れます。 SELECT * FROM rands WHERE price / m2 * 3.30578 < 1000000 ;今回はテーブルを作り、データを入れ込むところからやりましたが、
もし、データが蓄積されているような場合であれば、SELECT * FROM テーブル名 WHERE 条件;で取ってくることができますね。
以上です。※ INT:整数ですよ、という宣言です。
※ NOT NULL:NULLを許さないよという記述です。主キーなのにNULLではこまりますので。
※ AUTO_INCREMENT:自動でidを連番で入れ込んでくれる便利なメソッドです。
※ PRIMARY KEY (id):idカラムのデータが主キーですよ〜という宣言です。これがないとAUTO_INCREMENTは使えません。
※ INSERT INTO:これから先に宣言するデータを入れ込みますよ〜という記述です。
※ VALUES:値のことです。上で言うと、(m2,price)となっているので、(m2 => 200, price => 1000000)と言う意味です。
- 投稿日:2020-10-06T11:25:56+09:00
配列の線形探索問題
はじめに
プログラミングとアルゴリズムに関しては初心者の私が(恐らく)線形探索という問題をといたので大したことない(自分は嬉しい)けど記事にしてみました。
問題とソース
配列の中に合致した数字がある場合それをそれを配列のindex番号と共に出力。ない場合はありませんと出力。
array = [3, 5, 9 ,12, 15, 21, 26, 34, 42, 51, 55, 56, 62, 65, 74, 123] def search(a,array) count = 0 array.each_with_index do |num, i| if num == a puts "#{i}番目にあります。" else count = count + 1 end end if count == array.length puts "その数は含まれません" end end search(5,array)このほかにもelse以降は記述せずにその代わりにreturnとして条件がtrueのときの処理を返し、繰り返しの処理の外でない場合の出力をする方法があるらしい。そうすれば、繰り返し処理を含むif文のようなメソッドが作れる。
最後に
初投稿ということもあり勝手に緊張と嬉しさがありますが、恐縮にも投稿させていただきました。
- 投稿日:2020-10-06T07:21:05+09:00
RSpec でGoogleMap のピンをクリックしたい
はじめに
RSpecで GoogleMap のピンをクリックするテストを書きたかったがハマってしまったので備忘録です。
記載しているソース、エラーコードは出力されたものを不要なものを省いた状態で載せていますので参考にする場合は注意してください。環境
- macOS 10.15.6
- Ruby 2.5.7
- Rails 5.2.3
- gem capybara
- gem selenium-webdriver
参考URL
https://qiita.com/jnchito/items/607f956263c38a5fec24
https://qiita.com/kon_yu/items/52a0f5f0016564486061
https://stackoverflow.com/questions/31479958/capybara-rspec-not-finding-and-clicking-css-element
https://code-kitchen.dev/html/map-area/
目標
GoogleMap 内のピンをクリックしてinfowindow が表示されるのかテストしたい
問題
- capybara でピンがクリックできなかった
- img タグをクリックすると
Selenium::WebDriver::Error::ElementClickInterceptedError:- map タグをクリックすると
Selenium::WebDriver::Error::ElementNotInteractableError:原因
- 検索したい要素に
display:none,disabled,hiddenになっているとcapybara で検索できない- 検索したい要素の高さが0px なのでcapybara で認識できない
解決策
visible: falseオプション付きでarea タグをfind するとクリックできるgooglemap.html<div id="map"> //地図全体 <img src="https://maps.gstatic.com/mapfiles/api-3/images/spotlight-poi2_hdpi.png" usemap="#gmimap0"> //ピン画像 <map name="gmimap0" id="gmimap0"> <area> //コイツをクリックすればok </map> </div>googlemap_spec.rbrequire 'rails_helper' RSpec.describe "GoogleMap", type: :system do describe "GoogleMap が表示されているページ" do context "GoogleMap の動作確認", js: true do it "ピンをクリックするとinfowindow が表示されること" do pin = find("map#gmimap0 area", visible: false) pin.click expect(page).to have_css "div.infowindow" # infowindow クラスの有無をテスト end end end endダメなパターン その1
ピンのimg タグを探してしてクリックする
- そもそもimg タグはクリックできない
Selenium::WebDriver::Error::ElementClickInterceptedError:- area タグはクリック可能というヒントが表示される
googlemap_spec.rb# ピンのimg タグを探してクリック describe "GoogleMap が表示されているページ" do context "GoogleMap の動作確認", js: true do it "ピンをクリックするとinfowindow が表示されること" do pin = find("img[src$='spotlight-poi2_hdpi.png']") #ピンのイメージを検索 pin.click expect(page).to have_css "div.infowindow" end end end# img タグはクリックできないよエラー # area タグはクリックできるよ Failures: 1) GoogleMap が表示されているページ GoogleMap 表示内容 ピンをクリックするとinfowindow が表示されること Failure/Error: find("img[src$='spotlight-poi2_hdpi.png']").click Selenium::WebDriver::Error::ElementClickInterceptedError: element click intercepted: Element <img alt="" src="https://maps.gstatic.com/mapfiles/api-3/images/spotlight-poi2_hdpi.png" draggable="false" style="position: absolute; left: 0px; top: 0px; width: 27px; height: 43px; user-select: none; border: 0px; padding: 0px; margin: 0px; max-width: none;"> is not clickable at point (699, 750). Other element would receive the click: <area log="miw" coords="13.5,0,4,3.75,0,13.5,13.5,43,27,13.5,23,3.75" shape="poly" title="" style="cursor: pointer; touch-action: none;"> (Session info: headless chrome=85.0.4183.121) [Screenshot]: スクリーンショットが保存されているディレクトリへのパス # 0 chromedriver 0x00000001083091b9 chromedriver + 4911545 . . . # 24 libsystem_pthread.dylib 0x00007fff6c49bb8b thread_start + 15 Finished in 9.05 seconds (files took 0.55005 seconds to load) 1 example, 1 failure Failed examples:ダメなパターン その2
map タグを探してクリックする
- map タグが見つからない
- エラーコードにヒントは無し
googlemap_spec.rb# map タグを探してクリック describe "GoogleMap が表示されているページ" do context "GoogleMap の動作確認", js: true do it "infowindow が表示されること テスト" do pin = find("map#gmimap0").click #mapタグを検索 pin.click expect(page).to have_css "div.infowindow" end end end# map タグは見つからないよエラー Failures 1) GoogleMap が表示されているページ GoogleMap 表示内容 ピンをクリックするとinfowindow が表示されること Failure/Error: pin = find("map#gmimap0").click Capybara::ElementNotFound: Unable to find visible css "map#gmimap0" [Screenshot]: スクリーンショットが保存されているディレクトリへのパス Finished in 7.84 seconds (files took 0.54001 seconds to load) 1 example, 1 failure Failed examples:ダメなパターン その3
map タグを
visible: falseで探してクリックする
- map タグはゼロサイズだからクリックできない
Selenium::WebDriver::Error::ElementNotInteractableError:googlemap_spec.rb# map タグにvisible: false を付けてfind describe "GoogleMap が表示されているページ" do context "GoogleMap 表示内容", js: true do it "infowindow が表示されること テスト" do pin = find("map#gmimap0", visible: false).click #非表示要素の検索オプションを付加してmap タグを検索 pin.click expect(page).to have_css "div.infowindow" end end end# map タグはゼロサイズだからクリックできないよのエラー Failures: 1) GoogleMap が表示されているページ GoogleMap 表示内容 infowindow が表示されること Failure/Error: pin = find("map#gmimap0", visible: false).click Selenium::WebDriver::Error::ElementNotInteractableError: element not interactable: element has zero size (Session info: headless chrome=85.0.4183.121) [Screenshot]: スクリーンショットが保存されているディレクトリへのパス # 0 chromedriver 0x0000000107a8c1b9 chromedriver + 4911545 . . # 21 libsystem_pthread.dylib 0x00007fff7034eb8b thread_start + 15 Finished in 8.19 seconds (files took 0.52799 seconds to load) 1 example, 1 failure学び
- capybara で非表示要素を検索したい場合は
visible: falseオプションでfind するとクリックできる- エラーコードにヒントが隠されているので見逃さない
- 投稿日:2020-10-06T03:11:55+09:00
VagrantにHerokuを入れる際に起きたエラーと解決方法と参考記事
はじめに
ドットインストールで構築したローカル環境でアプリを開発し、公開するため、いざherokuを入れようとしたらエラーが起きまくりました。
【初心者向け】railsアプリをherokuを使って確実にデプロイする方法【決定版】
この記事を参考にしていました。
しかし、herokuを入れる際に、この記事の開発環境が全く違うため、その先に進めずエラーと戦うことになりました。パスがない問題
エラー解決に必死だったため、エラーのコピーは取っていませんが、このようなエラーがでたと思います。
↓エラーの最後の文 Your path is missing /usr/local/bin, you need to add this to use this installer.
/usr/local/binのパスが無いと言われているため、パスを作る必要があるみたいです。パスの確認
$ echo $PATHパスを確認してみますが
/usr/local/bin:はあっても/usr/local/binがないと駄目みたいです。解決方法の前に、解決する中で知った知識
sudoとは、僕の解釈では権限の高いコマンド。
$と#で違いもあり、#のほうが権限が高い。($の方で実行できないことが#で実行できたから)解決方法:偉い権限を使ったり、
viエディタで書き換えたりする。パスの設定に必要な
.bash_profileの設定とsudoの設定は以下を参考にしました。
仮想環境構築後に設定しておきたいこと -メモ-
sudoの設定はこちらの方が親切です。
CentOS で sudo 時に実行ユーザーのPATHを引き継ぐ$ sudo visudoの実行後に、viエディタの操作が必要だったため以下を参考にしました。
viエディタの使い方パスが通ったと思ったら、シンタックスエラー問題
$ heroku --versionherokuのバージョン確認をすると、以下のエラーが...
/usr/local/lib/heroku/node_modules/@oclif/command/lib/index.js:3 const path = require("path"); ^^^^^ SyntaxError: Use of const in strict mode. at Module._compile (module.js:439:25) at Object.Module._extensions..js (module.js:474:10) at Module.load (module.js:356:32) at Function.Module._load (module.js:312:12) at Module.require (module.js:364:17) at require (module.js:380:17) at Object.<anonymous> (/usr/local/lib/heroku/bin/run:5:1) at Module._compile (module.js:456:26) at Object.Module._extensions..js (module.js:474:10) at Module.load (module.js:356:32)Node.jsのバージョンが古すぎたみたいです。
$ node -v確認してみたところ
v0.10.48でした。解決方法:キャッシュを消して、新しいバージョンを入れる。
いろいろと試したため、ダイレクトな解決方法にならない場合はすみません。
npmがインストールされていなかったので、インストールしました。ついでにnodeもインストール。
Herokuを入れるためにnpmが必要かどうかはわかりませんが、とりあえずインストールしました。$ sudo yum install nodejs npm偉い権限の方に行く。
$ sudo -s左のやつが
$から#に変わる。インストールするために必要なやつ(正直、よくわかってないですが
setup_11.xの数字の部分でインストールするバージョンを指定。)# curl --silent --location https://rpm.nodesource.com/setup_11.x | bashここから、インストールとアンインストールを繰り返したため、手順が間違っていたらすみません。
以下を参考にしました。先の読んでおいて欲しいです。
yumでのnodejsのバージョンアップにはまった話と解決方法古い方のrpmを削除。
# rm /etc/yum.repos.d/nodesource-el.repoアンインストールする。
# yum -y remove nodejsキャッシュを削除。
# yum clean allインストールする。
# yum -y install nodejsバージョンを確認。
# node -v v11.15.0成功です!
そして、偉い権限のところから出る。# su vagrantherokuのバージョンも確認
$ heroku -v heroku/7.44.0 linux-x64 node-v11.15.0他の参考記事です。
https://qiita.com/daskepon/items/16a77868d38f8e585840
https://inaba.hatenablog.com/entry/2018/11/13/023933ほぼ同じエラーが出ていた記事
https://teratail.com/questions/256490感謝
参考にさせていただいたサイト管理人の皆様、誠にありがとうございました。
- 投稿日:2020-10-06T02:35:01+09:00
before_action使うべし!!
before_actionってなに?
ここでbefore_actionがなんなのか簡単に説明させて頂きます。
before_actionとは、before_actionを使用するとcontrollerで定義された
アクションが実行される前に、共通の処理を行うことができるメソッドです。
つまり、railsのcontrollerアクションを実行する前に処理を行いたい時、同じ記述の処理をまとめたい時に使用します。言葉で説明されても「どゆこと?」、「イメージができない。。。」と
思いますので下記に例を上げて説明したいと思います。before_action記述例
[例] before_actionclass コントローラ名 < ApplicationController before_action :処理させたいメソッド名また、ここではオプションを同時に使用します。
全部のアクションにbefore_actionを使用する必要はないので、
どのアクションで実行する前に処理を行わせたいのか指定します。
そこで使用するオプションがonlyオプションです。onlyオプションってなに?(記述例)
resourcesと同様にonlyオプションを使用することによって、どのアクションの実行前に、処理を実行させるかなど制限するとこが可能です。
『resources』とは、7つのアクションのルーティングをまとめて設定ができるメソッドのことです。
[例] before_actionclass コントローラ名 < ApplicationController before_action :処理させたいメソッド名, only: [:アクション名, :アクション名]上記のようにbefore_actionの後ろにonlyオプションを記述して使用します。
例のように記述することでアクションを指定することができます。基本は上記のように記述します。
この例を参照してもどう書いたらいいの?となると思いますので、
次はcontrollerの全体的な記述例を上げて説明します。controller全体記述例
ここでは、controllerの全体の記述例を上げて説明していきたいと思います。
先にbefore_actionを使用していない時の記述例です。
下記の例を見て頂きたいのですが、同じ記述がありますね。使用しない場合
[例] controllerclass TweetsController < ApplicationController def index @tweets = Tweet.all end def new @tweet = Tweet.new end def create Tweet.create(tweet_params) end def destroy tweet = Tweet.find(params[:id]) tweet.destroy end def edit @tweet = Tweet.find(params[:id]) ⬅️ ここの箇所!! end def update tweet = Tweet.find(params[:id]) tweet.update(tweet_params) end def show @tweet = Tweet.find(params[:id]) ⬅️ ここの箇所!! end private def tweet_params params.require(:tweet).permit(:name, :image, :text) end endtweetsコントローラーのeditアクションとshowアクションを見ると、
@tweet = Tweet.find(params[:id])が繰り返し記述されています。
この箇所の記述をまとめていきたいと思います。上記の例だともうひとつ被っている記述がありますが、
今回は一箇所だけbefore_actionを使用してまとめていきますので、
気にしないで下さい。w次は使用した時の記述例です。
使用する場合
editアクションとshowアクションの処理"のみ"を実行させて記述を
まとめていきたいと思います。
今回は、before_actionで呼び出すメソッドをtest_tweetとしましょう![例] controllerclass TweetsController < ApplicationController before_action :set_tweet, only: [:edit, :show] def index @tweets = Tweet.all end def new @tweet = Tweet.new end def create Tweet.create(tweet_params) end def destroy tweet = Tweet.find(params[:id]) tweet.destroy end def edit end def update tweet = Tweet.find(params[:id]) tweet.update(tweet_params) end def show end private def tweet_params params.require(:tweet).permit(:name, :image, :text) end def test_tweet @tweet = Tweet.find(params[:id]) end endbefore_actionを使用することで、editアクションとshowアクションの
実行前に、test_tweetというメソッドを呼び出しています。そうすることで、2つのアクションの共通の処理である
@tweet = Tweet.find(params[:id])が動きます。上記の例のようにbefore_actionを使用してまとめてあげることで繰り返し
記述することがないのでスッキリして見やすくなりましたね!!まとめ
・before_actionは、コントローラで定義されたアクションが実行される前に、
指定した共通の処理を行うことができるメソッドのこと
・onlyオプションは、resourcesと同様にonlyオプションを使用することによって、どのアクションの実行前に、処理を実行させるかなど制限すること!!これらの事からコードを記述する際は、同じ記述を繰り返さずに見やすく記述していくのがいいですね!!
- 投稿日:2020-10-06T02:30:44+09:00
Ruby on rails 学習記録 -2020.10.05
Webフォームのデータ送信方式
GETメソッド:URLに含める
http://example.net?id=3&content=helloPOSTメソッド:リクエストメッセージに含める
article[name]=paiza
article[content]=hello+worldRailsサーバーのログなどが、画面上部に消えてしたったら、マウスのホイールが役に立つ。
ホイール操作できない場合
Mac:CTRLキー + Altキー + 上下矢印キー
Windows: CTRLキー + 上下矢印キーRailsのルーター機能
Routesの設定内容の確認
rails routes
GETとPOSTの確認ができるルーターの振り分けを設定
config/routes.rbRails.application.routes.draw do get 'welcome/index' resources :articles root 'welcome#index' endwelcome#indexがトップページになる
ビューのテンプレート
ヘルパーメソッド:ビューを作るときに利用できる専用コマンド
<%= link_to 'Edit', edit_article_path(@article) %> | <%= link_to 'Back', articles_path %>Railsアプリのページ間のリンクを記述できる。
<%= link_to 'text', path %>これは、次のhtmlに変換できる。
<a href="path">text</a>投稿フォームの動作
部分テンプレート:複数のビューを記述するテンプレート
呼び出すには、renderメソッドを利用する。<%= render 'form', article: @article %>この場合、「_form.html.erb」が部分テンプレートのファイル名になる。
また、article変数で、@articleのオブジェクトを利用できる。フォームを作成するヘルパーメソッド
form_for:投稿フォームのように、Modelの新規作成・更新に使用する
form_tag:検索フォームのように、Modelを更新しない場合に使用するform_forメソッド
<%= form_for(@article) do |f| %> <div class="field"> <%= f.label :name %> <%= f.text_field :name %> </div> <div class="field"> <%= f.label :content %> <%= f.text_field :content %> </div> <div class="actions"> <%= f.submit %> </div> <% end %>controllerのデータの書き込み
ストロングパラメーター:
データベースに安全にアクセスするために、書き込みできるカラムをリストアップ
controllerのarticle_paramsメソッドに記述する。articles_controller.rb(一部)# Never trust parameters from the scary internet, only allow the white list through. def article_params params.require(:article).permit(:content, :name, :feeling) end検索フォームの追加
ビューに検索フォームの追加
index.html.erb(一部)<%= form_tag('/articles', method: 'get') do %> <%= label_tag(:name_key, 'Search name:') %> <%= text_field_tag(:name_key) %> <%= submit_tag('Search') %> <%= link_to 'Clear', articles_path %> <% end %> <br>コントローラーにindexメソッドの検索コードを追加
articles_controller.rb(一部)# GET /articles # GET /articles.json def index if params[:name_key] @articles = Article.where('name LIKE ?', "%#{params[:name_key]}%") else @articles = Article.all end end
- 投稿日:2020-10-06T01:26:03+09:00
Passenger 6.0.5 で ActiveRecord 使った Sinatra アプリが死んだ
何の話?
CentOS 8 + Apache 2.4 + Phusion Passenger 6.0 で,ActiveRecord を使った Sinatra アプリを動かしていた。
Ruby のバージョンは 2.7.1 だが,たぶんこのバージョンはあまり関係無い。Passenger 6.0.4 では動いていたのに 6.0.6 では,本番(production)環境で起動すらしなくなった。
手許(ローカル)ではちゃんと動作している。Apache のエラーログ(/var/log/httpd/error_log)には以下のように出ていた。
'production' database is not configured. Available: [] (ActiveRecord::AdapterNotSpecified)原因
どうも Passenger 6.0.5 で導入された変更が原因のようだ。
ここに issue が上がっている。
https://github.com/phusion/passenger/issues/2281同様の問題は Sinatra 以外でも起こりうるが,Rails では起こらない(たぶん)。
死んだコード
件の Sinatra アプリは,SQLite3 データベースにあらかじめ入れておいたデータを検索して表示するだけのごく簡単なもの。
データベースファイルは,とくに production 用とか development 用とか分けていない。接続のところは
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: db_file)てな感じに書いていた。
ローカル変数db_fileには,SQLite3 ファイルのパスが代入されている。修正
どうも,
ActiveRecord::Base.configurationsをセットしてやらなければならないようなので,上記をActiveRecord::Base.configurations = { "production" => {"adapter" => "sqlite3", "database" => db_file }, "development" => {"adapter" => "sqlite3", "database" => db_file }, } ActiveRecord::Base.establish_connectionのように変えた。
これで本番環境でも動くようになった。
- 投稿日:2020-10-06T00:56:55+09:00
pay.jpの導入
Pay.jpの導入方法
クレジットカード決済の代行サービスで、簡単なオープンAPIで導入する事ができる
Why
コード量も非常に少なく、Javascriptで簡単にフォーム送信までできるのでご紹介します(割愛しながらの説明になります)
アプリケーションの作成
ターミナル% cd ~/projects(好きなように) % rails _6.0.0_ new payjp_practice -d mysql % cd payjp_practice % rails db:createOrderモデル作成
ターミナル% rails g model orderOrdersテーブル作成
db/migrate/**************_create_orders.rbclass CreateOrders < ActiveRecord::Migration[6.0] def change create_table :orders do |t| t.integer :price ,null: false t.timestamps end end end忘れずに!
ターミナル% rails db:migrateバリデーション
app/models/order.rbclass Order < ApplicationRecord validates :price, presence: true endルーティング
config/routes.rbRails.application.routes.draw do root to: 'orders#index' resources :orders, only:[:create] endordersコントローラー
ターミナル% rails g controller ordersapp/controllers/orders_controller.rbclass OrdersController < ApplicationController def index end def create end endビューを作成
app/views/orders/index.html.erb<%= form_with model: @order, id: 'charge-form', class: 'card-form',local: true do |f| %> <div class='form-wrap'> <%= f.label :price, "金額" %> <%= f.text_field :price, class:"price", placeholder:"例)2000" %> </div> <%= f.submit "購入" ,class:"button" %> <% end %>CSS記述
app/assets/stylesheets/style.css.card-form{ width: 500px; } .form-wrap{ display: flex; flex-direction: column; } .exp_month{ resize:none; } .exp_year{ resize:none; } .input-expiration-date-wrap{ display: flex; } .button{ margin-top: 30px; height: 30px; width: 100px; }コントローラー編集
app/controllers/orders_controller.rbclass OrdersController < ApplicationController def index @order = Order.new end def create @order = Order.new(order_params) if @order.valid? @order.save return redirect_to root_path else render 'index' end end private def order_params params.require(:order).permit(:price) end end部分テンプレート
app/views/orders/index.html.erb<%= form_with model: @order, id: 'charge-form', class: 'card-form',local: true do |f| %> <%= render 'layouts/error_messages', model: @order %> <div class='form-wrap'> <%# 省略 %>ビュー記述
app/views/orders/index.html.erb<%= form_with model: @order, id: 'charge-form', class: 'card-form',local: true do |f| %> <%= render 'layouts/error_messages', model: @order %> <div class='form-wrap'> <%= f.label :price, "金額" %> <%= f.text_field :price, class:"price", placeholder:"例)2000" %> </div> <div class='form-wrap'> <%= f.label :number, "カード番号" %> <%= f.text_field :number, class:"number", placeholder:"カード番号(半角英数字)", maxlength:"16" %> </div> <div class='form-wrap'> <%= f.label :cvc ,"CVC" %> <%= f.text_field :cvc, class:"cvc", placeholder:"カード背面4桁もしくは3桁の番号", maxlength:"3" %> </div> <div class='form-wrap'> <p>有効期限</p> <div class='input-expiration-date-wrap'> <%= f.text_field :exp_month, class:"exp_month", placeholder:"例)3" %> <p>月</p> <%= f.text_field :exp_year, class:"exp_year", placeholder:"例)24" %> <p>年</p> </div> </div> <%= f.submit "購入" ,class:"button" %> <% end %>turbolinks削除&コード追加
app/views/layouts/application.html.erb<%# 省略 %> <%= stylesheet_link_tag 'application', media: 'all' %> <%= javascript_pack_tag 'application' %> <%# 省略 %>app/javascript/packs/application.js// 省略 require("@rails/ujs").start() // require("turbolinks").start() // コメントアウトする require("@rails/activestorage").start() require("channels") // 省略payjp.js読み込み
app/views/layouts/application.html.erb<%# 省略 %> <%= csrf_meta_tags %> <%= csp_meta_tag %> <script type="text/javascript" src="https://js.pay.jp/v1/"></script> <%= stylesheet_link_tag 'application', media: 'all' %> <%= javascript_pack_tag 'application' %> <%# 省略 %>トークン化準備
app/javascript/packs/application.js// 省略 require("@rails/ujs").start() // require("turbolinks").start() require("@rails/activestorage").start() require("channels") require("../card") // 省略イベント発火
app/javascript/card.jsconst pay = () => { const form = document.getElementById("charge-form"); form.addEventListener("submit", (e) => { e.preventDefault(); console.log("フォーム送信時にイベント発火") }); }; window.addEventListener("load", pay);公開鍵設定
app/javascript/card.jsconst pay = () => { Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵 const form = document.getElementById("charge-form"); form.addEventListener("submit", (e) => { e.preventDefault(); console.log("フォーム送信時にイベント発火") }); }; window.addEventListener("load", pay);フォームの情報取得
app/javascript/card.jsconst pay = () => { Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵 const form = document.getElementById("charge-form"); form.addEventListener("submit", (e) => { e.preventDefault(); const formResult = document.getElementById("charge-form"); const formData = new FormData(formResult); const card = { number: formData.get("order[number]"), cvc: formData.get("order[cvc]"), exp_month: formData.get("order[exp_month]"), exp_year: `20${formData.get("order[exp_year]")}`, }; }); }; window.addEventListener("load", pay);カードの情報トークン化
app/javascript/card.jsconst pay = () => { Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵 const form = document.getElementById("charge-form"); form.addEventListener("submit", (e) => { e.preventDefault(); const formResult = document.getElementById("charge-form"); const formData = new FormData(formResult); const card = { number: formData.get("order[number]"), cvc: formData.get("order[cvc]"), exp_month: formData.get("order[exp_month]"), exp_year: `20${formData.get("order[exp_year]")}`, }; Payjp.createToken(card, (status, response) => { if (status == 200) { const token = response.id; console.log(token) } }); }); }; window.addEventListener("load", pay);一度pay.jpが用意しているテスト用のカード情報を入力してチェックしておきましょう!
カード番号 4242424242424242(16桁)
CVC 123
有効期限 登録時より未来(04/25など)トークンの値をフォームに含める
app/javascript/card.jsconst pay = () => { Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵 const form = document.getElementById("charge-form"); form.addEventListener("submit", (e) => { e.preventDefault(); const formResult = document.getElementById("charge-form"); const formData = new FormData(formResult); const card = { number: formData.get("order[number]"), cvc: formData.get("order[cvc]"), exp_month: formData.get("order[exp_month]"), exp_year: `20${formData.get("order[exp_year]")}`, }; Payjp.createToken(card, (status, response) => { if (status == 200) { const token = response.id; const renderDom = document.getElementById("charge-form"); const tokenObj = `<input value=${token} name='token'>`; renderDom.insertAdjacentHTML("beforeend", tokenObj); debugger; } }); }); }; window.addEventListener("load", pay);トークンの値を非表示
app/javascript/card.jsconst pay = () => { Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵 const form = document.getElementById("charge-form"); form.addEventListener("submit", (e) => { e.preventDefault(); const formResult = document.getElementById("charge-form"); const formData = new FormData(formResult); const card = { number: formData.get("order[number]"), cvc: formData.get("order[cvc]"), exp_month: formData.get("order[exp_month]"), exp_year: `20${formData.get("order[exp_year]")}`, }; Payjp.createToken(card, (status, response) => { if (status == 200) { const token = response.id; const renderDom = document.getElementById("charge-form"); const tokenObj = `<input value=${token} name='token' type="hidden"> `; renderDom.insertAdjacentHTML("beforeend", tokenObj); debugger; } }); }); }; window.addEventListener("load", pay);クレジットカードの情報を削除
app/javascript/card.jsconst pay = () => { Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵 const form = document.getElementById("charge-form"); form.addEventListener("submit", (e) => { e.preventDefault(); const formResult = document.getElementById("charge-form"); const formData = new FormData(formResult); const card = { number: formData.get("order[number]"), cvc: formData.get("order[cvc]"), exp_month: formData.get("order[exp_month]"), exp_year: `20${formData.get("order[exp_year]")}`, }; Payjp.createToken(card, (status, response) => { if (status == 200) { const token = response.id; const renderDom = document.getElementById("charge-form"); const tokenObj = `<input value=${token} name='token' type="hidden"> `; renderDom.insertAdjacentHTML("beforeend", tokenObj); } document.getElementById("order_number").removeAttribute("name"); document.getElementById("order_cvc").removeAttribute("name"); document.getElementById("order_exp_month").removeAttribute("name"); document.getElementById("order_exp_year").removeAttribute("name"); }); }); }; window.addEventListener("load", pay);フォームの情報をサーバーサイドに送信
app/javascript/card.jsconst pay = () => { Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵 const form = document.getElementById("charge-form"); form.addEventListener("submit", (e) => { e.preventDefault(); const formResult = document.getElementById("charge-form"); const formData = new FormData(formResult); const card = { number: formData.get("order[number]"), cvc: formData.get("order[cvc]"), exp_month: formData.get("order[exp_month]"), exp_year: `20${formData.get("order[exp_year]")}`, }; Payjp.createToken(card, (status, response) => { if (status == 200) { const token = response.id; const renderDom = document.getElementById("charge-form"); const tokenObj = `<input value=${token} name='token' type="hidden"> `; renderDom.insertAdjacentHTML("beforeend", tokenObj); } document.getElementById("order_number").removeAttribute("name"); document.getElementById("order_cvc").removeAttribute("name"); document.getElementById("order_exp_month").removeAttribute("name"); document.getElementById("order_exp_year").removeAttribute("name"); document.getElementById("charge-form").submit(); }); }); }; window.addEventListener("load", pay);ストロングパラメーター編集
app/controllers/orders_controller.rb#省略 private def order_params params.require(:order).permit(:price).merge(token: params[:token]) end endOrderモデルに追記
app/models/order.rbclass Order < ApplicationRecord attr_accessor :token validates :price, presence: true endGem導入
Gemfile# 省略 gem 'payjp'決済処理の記述とリファクタリング
app/controllers/orders_controller.rbclass OrdersController < ApplicationController def index @order = Order.new end def create @order = Order.new(order_params) if @order.valid? pay_item @order.save return redirect_to root_path else render 'index' end end private def order_params params.require(:order).permit(:price).merge(token: params[:token]) end def pay_item Payjp.api_key = "sk_test_***********" # 自身のPAY.JPテスト秘密鍵を記述しましょう Payjp::Charge.create( amount: order_params[:price], # 商品の値段 card: order_params[:token], # カードトークン currency: 'jpy' # 通貨の種類(日本円) ) end endバリデーション
app/models/order.rbclass Order < ApplicationRecord attr_accessor :token validates :price, presence: true validates :token, presence: true end環境変数(Mac Catalina以降の場合)
ターミナル% vim ~/.zshrc # iを押してインサートモードに移行し、下記を追記する。既存の記述は消去しない。 export PAYJP_SECRET_KEY='sk_test_************' export PAYJP_PUBLIC_KEY='pk_test_************' # 編集が終わったらescキーを押してから:wqと入力して保存して終了ターミナル# 編集した.zshrcを読み込み直して、追加した環境変数を使えるようにする % source ~/.zshrc秘密鍵代入した環境変数の呼び込み
app/controllers/orders_controller.rb#省略 def pay_item Payjp.api_key = ENV["PAYJP_SECRET_KEY"] Payjp::Charge.create( amount: order_params[:price], card: order_params[:token], currency:'jpy' ) endJavaScriptで環境変数の呼び込み
ターミナル% touch config/initializers/webpacker.rbconfig/initializers/webpacker.rbWebpacker::Compiler.env["PAYJP_PUBLIC_KEY"] = ENV["PAYJP_PUBLIC_KEY"]app/javascript/card.jsconst pay = () => { Payjp.setPublicKey(process.env.PAYJP_PUBLIC_KEY); // 省略まとめ
簡単と言っておきながら意外と記述は多かったかもしれません。ですが、APIの中でも比較的簡単な決済機能の導入なので抑えておくといいかもしれません。オリジナルでカラム等追加する場合がほとんどだと思いますので、ゆっくり順に書くことをお勧めします!以上!
- 投稿日:2020-10-06T00:08:15+09:00
【No.007】組織の管理画面と組織へのログイン処理
Issue
PR
【No.006】組織の管理画面とログインのおおまかなデザイン概念等は
組織としていたが、実装は会社とすることにした概要
組織の管理画面と組織へのログイン処理を追加する
ToDo詳細
- Orgモデル・テーブル追加
Terminalbin/rails g model org name:string org_type:integer
- orgs_controllerを追加
Terminalbin/rails g controller orgs要らないファイルは削除しておく
Terminal-> % bin/rails g controller orgs Running via Spring preloader in process 22619 create app/controllers/orgs_controller.rb invoke slim create app/views/orgs invoke test_unit create test/controllers/orgs_controller_test.rb <- これは削除した invoke helper create app/helpers/orgs_helper.rb <- これは削除した invoke test_unit invoke assets invoke coffee invoke scss create app/assets/stylesheets/orgs.scss <- これは削除した
- orgs#indexのrouteを設定し、ビュー追加
config/routes.rbRails.application.routes.draw do root 'orders/ordering_org_sides#index' # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html resource :orgs, only: %i[index] #<-追加 namespace :orders do resources :ordering_org_sides, only: %i[index] end endTerminaltouch app/views/orgs/index.html.slimapp/views/orgs/index.html.slim.bg-white.shadow.m-auto.sm:rounded-md.mt-5(class="w-2/4") ul - @orgs.each do |org| / TODO:Sassで場合分けできるようにする - border_t = org == @orgs.first ? '' : 'border-t border-gray-200' li.(class=border_t) = link_to [org], class: 'block hover:bg-gray-50 focus:outline-none focus:bg-gray-50 transition duration-150 ease-in-out' do .flex.items-center.px-4.py-4.sm:px-6 .min-w-0.flex-1.flex.items-center .min-w-0.flex-1.px-4.md:grid.md:grid-cols-2.md:gap-4 div .text-sm.leading-5.font-medium.text-indigo-600.truncate = org.name div i.fas.fa-sign-in-alt.fa-lg.bg-gray-50.text-gray-500
- orgs#showのrouteを設定し、ビュー追加
config/routes.rbRails.application.routes.draw do root 'orders/ordering_org_sides#index' # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html resource :orgs, only: %i[index, show] #<-showも追加 namespace :orders do resources :ordering_org_sides, only: %i[index] end endTerminaltouch app/views/orgs/show.html.slimapp/views/orgs/show.html.slim.bg-white.shadow.m-auto.mt-5.overflow-hidden.sm:rounded-lg(class="w-1/2") .px-4.py-5.border-b.border-gray-200.sm:px-6 h3.text-lg.leading-6.font-medium.text-gray-900 | 会社詳細 p.mt-1.max-w-2xl.text-sm.leading-5.text-gray-500 | 会社詳細を説明します。 div dl .bg-gray-100.px-4.py-5.sm:grid.sm:grid-cols-3.sm:gap-4.sm:px-6 dt.text-sm.leading-5.font-medium.text-gray-500 | 会社名 dd.mt-1.text-sm.leading-5.text-gray-900.sm:mt-0.sm:col-span-2 = @org.name .bg-white.px-4.py-5.sm:grid.sm:grid-cols-3.sm:gap-4.sm:px-6 dt.text-sm.leading-5.font-medium.text-gray-500 | 会社タイプ dd.mt-1.text-sm.leading-5.text-gray-900.sm:mt-0.sm:col-span-2 = @org.org_type
- 右斜上にそれぞれの導線・リンクを配置
app/views/orders/ordering_org_sides/index.html.slim.bg-gray-100 nav.bg-white.shadow-sm .max-w-7xl.mx-auto.px-4.sm:px-6.lg:px-8 .flex.justify-between.h-16 .flex .flex-shrink-0.flex.items-center .hidden.sm:ml-6.space-x-8.sm:flex a.inline-flex.items-center.px-1.pt-1.border-b-2.border-indigo-500.text-sm.font-medium.leading-5.text-gray-900.focus:outline-none.focus:border-indigo-700.transition.duration-150.ease-in-out[href="#"] | Sample_1 a.inline-flex.items-center.px-1.pt-1.border-b-2.border-transparent.text-sm.font-medium.leading-5.text-gray-500.hover:text-gray-700.hover:border-gray-300.focus:outline-none.focus:text-gray-700.focus:border-gray-300.transition.duration-150.ease-in-out[href="#"] | Sample_2 a.inline-flex.items-center.px-1.pt-1.border-b-2.border-transparent.text-sm.font-medium.leading-5.text-gray-500.hover:text-gray-700.hover:border-gray-300.focus:outline-none.focus:text-gray-700.focus:border-gray-300.transition.duration-150.ease-in-out[href="#"] | Sample_3 a.inline-flex.items-center.px-1.pt-1.border-b-2.border-transparent.text-sm.font-medium.leading-5.text-gray-500.hover:text-gray-700.hover:border-gray-300.focus:outline-none.focus:text-gray-700.focus:border-gray-300.transition.duration-150.ease-in-out[href="#"] | Sample_4 .hidden.sm:ml-6.sm:flex.sm:items-center button.p-1.border-2.border-transparent.text-gray-400.rounded-full.hover:text-gray-500.focus:outline-none.focus:text-gray-500.focus:bg-gray-100.transition.duration-150.ease-in-out[aria-label="Notifications"] svg.h-6.w-6[stroke="currentColor" fill="none" viewbox="0 0 24 24"] path[stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"] .ml-3.relative div button#user-menu.flex.text-sm.border-2.border-transparent.rounded-full.focus:outline-none.focus:border-gray-300.transition.duration-150.ease-in-out[aria-label="User menu" aria-haspopup="true"] i.fas.fa-user-circle.fa-2x.text-gray-700 .origin-top-right.absolute.right-0.mt-2.w-48.rounded-md.shadow-lg .py-1.rounded-md.bg-white.shadow-xs = link_to [:orgs], class: 'block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out' do | 会社切替 / TODO:リンクを@orgに書き換える = link_to '/', class: 'block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out' do | 会社詳細JSは別PRで対応予定。
- seedにorgsを追加
db/seeds.rborgs = Org.create( [ {name: '会社_a', org_type: 0}, {name: '会社_b', org_type: 1}, {name: '会社_c', org_type: 0} ] )動作確認
準備
bin/rails db:migrate:reset bin/rails db:reset受入基準
- 下図のように画面遷移する












