- 投稿日:2021-01-03T23:40:40+09:00
モジュールとMixin メモ
調べながらメモする
モジュールとは
Rubyの基本概念は、クラスがありオブジェクトがある
ある一連の振る舞いを一箇所にまとめたものがモジュール(Module)しかし、クラスとは違いオブジェクトを生成することはできない
モジュールは、あくまで
一連の振る舞い(メソッドの集まり)、または処理をまとめたものそれをクラスで読み込んで使用する
基本形は、ほぼクラスと同じ
メソッドだけでなく、定数やクラスを宣言することも可能使いかた
使用したいクラスにて、取り込む(includeする)
そうすると、作成したオブジェクトからメソッドを読み込むことができる色々なクラスにて同じ振る舞いのメソッドを
簡単に追加することができる同じ名前をつけても衝突しない
それぞれの世界を名前空間といい
名前空間が異なれば、同じ名前でも良いMixin
クラスにモジュールを取り込むことをMixinと呼ぶ
多重継承もどき
Rubyは、Javaなどと同じように多重継承を許していない言語
つまり2つの親クラスを同時に継承できないよって、
1つのクラスを継承してしまった後、
変更を加えるには子クラスに手を加えるしかないところが
Mix-inを使うと、あたかも親クラスのように振る舞うことができるまた1度モジュールを定義すれば、他のクラスでも使えるのでとても便利
まとめ
・クラス→オブジェクトを生成することができる
・モジュール→オブジェクトを生成することができない
・モジュールはいわばメソッドの集まりのようなもので
様々なクラスにて読み込むことで使用することができる
・複数のモジュールを読み込んで使用することも可能
・クラスにモジュールを取り込むことをMixinと呼ぶ
・Mixinすることで、多重継承のような動きに見せることができる参考記事
現場Rails
https://style.potepan.com/articles/7667.html動作確認の際に参考にしようと思う記事
https://qiita.com/tanaka-t/items/32a3329fbc1eb0840ea0
https://qiita.com/pink_bangbi/items/2c2f17516cd3a7b4eeac
https://medium.com/@jiraffestaff/ruby-mixin%E3%81%AE%E4%BD%BF%E3%81%84%E3%81%A9%E3%81%93%E3%82%8D-%E8%A8%AD%E8%A8%88%E7%9A%84%E3%81%AA%E8%80%83%E3%81%88%E6%96%B9-9b8d2e529669
- 投稿日:2021-01-03T23:12:55+09:00
Railsのパンくずリストで2種類以上のトップページを実装する方法
こんばんは
アロハな男、やすのりです!今日はポートフォリオで使用しているパンくずリスト用Gem
gretel
を導入時に少し躓いた、『トップページを2種類以上実装する方法』を説明していきたいと思います!もし僕と同じことを実装したい方がいらっしゃったら参考にしてください!!
結論
2種類以上のトップページを実装する場合は、
config/breadcrumbs.rbcrumb :root do link "Home", root_path endという初期設定を
config/breadcrumbs.rbcrumb :top_page do link "トップページ", root_path end crumb :mypage_top do |user| link "マイページトップ", user_path(user.id) endという様な記述に修正し、
root
という設定を無くす!!なぜトップページが2種類以上必要?
まずそもそもなぜ通常だと1つでいいトップページが2種類以上必要なのかについてですが、これは私のポートフォリオの作り方で『その方が見栄えが良さそうだったから』です。
...ここに関しては理論立てた説明はありません
例えば、ポートフォリオでの
検索結果ページ
や詳細ページ
に遷移した際はもちろんトップページからの遷移歴が表示されます。
しかし、
マイページ
内のユーザー情報編集ページ
に遷移した場合の遷移歴は、
この様に、トップページ
へのリンクはなく、マイページトップ
へのリンクしかありません。これがもし遷移歴に
トップページ
も表示させる様にすると、
となってしまい、少し表示が多く見づらくなってしまうなと思いました。
※トップページへのリンクはヘッダー部分に設置してあるため、マイページから出られなくなる等といったことはありません。トップページを2種類用意するには?
gretel
を使用する際に基本は遷移先のリンクと表示用文字列
だけを記述し、親ページを設定したい時は別途設定を記述します。config/breadcrumbs.rbcrumb :root do link "トップページ", root_path end crumb :mypage_top do |user| link 'マイページトップ', user_path(user.id) end crumb :mypage_edit do |user| link '会員情報の確認・変更', edit_user_registration_path #ユーザー情報編集ページがマイページトップの中に位置する様に設定している。 parent :mypage_top, user endただし、何も設定をしていない場合でも
root
の設定と紐づいてしまいます。
そのためユーザー情報編集ページ
までの遷移歴を表示する際に紐づいているトップページ
まで表示されてしまっているわけです。『なら、
root
との紐付けを切り離すために、名前を変えちゃえばいいんだ!!』
ということで冒頭でお見せしたコードの出番ですが、config/breadcrumbs.rbcrumb :top_page do link "トップページ", root_path end crumb :mypage_top do |user| link "マイページトップ", user_path(user.id) end crumb :mypage_edit do |user| link '会員情報の確認・変更', edit_user_registration_path #ユーザー情報編集ページがマイページトップの中に位置する様に設定している。 parent :mypage_top, user endというコード構成になります。
このコード構成にして再度ユーザー情報編集ページ
を表示させることで、紐づいていないトップページ
の表示が消えてマイページトップ
からの遷移歴になるわけです。注意点
この実装方法をした場合に、1つ注意しなければならないことがあります。
それは、『
検索結果ページ
に親ページを設定する必要がある』ということです。config/breadcrumbs.rbcrumb :root do link "トップページ", root_path end crumb :search_word do |search_word| link "'#{search_word}' の検索結果", search_path end元々は
検索結果ページ
に何も設定しなくてもroot
に紐づいていたので自動的にトップページ
を表示してくれていましたが、名前を変更(今回であればtop_page
)に変更してしまっているので、紐付けが解除され親ページが存在しない状態になってしまっています。なので、
config/breadcrumbs.rbcrumb :top_page do link "トップページ", root_path end crumb :search_word do |search_word| link "'#{search_word}' の検索結果", search_path #新たに設定したtop_pageというページを親ページとして設定する。 parent :top_page endとコードを修正することで、元通りのパンくずリスト表示にすることができます。
最後に
トップページを2種類以上実装するということ自体そんなに頻繁にすることではないと思いますが、もし実装する際にこの記事が参考になれば幸いです!
この件に関するご指摘・アドバイスはどんどんいただきたいと思っていますので、コメントお待ちしています!!
- 投稿日:2021-01-03T23:03:24+09:00
RSpecにてmodelのテストを書く
modelテストの準備
今回はPostモデルにタイトルカラムがあるかどうかと文字数のテストをする。
app/models/post.rbclass Post < AplicationRecord belongs_to :user validates :title, presence: true, length: {maximum: 20} endファイルの作成
①spec配下にmodelsフォルダとfactoriesフォルダを作成し、テストしたいモデルのファイルを作成する。今回は spec/models/post_spec.rbを作成
②FactroyBotを使用可能にする。
使用すると user = create(:user)のようにDB登録やモデルのビルドができるようになる。
spec配下にsupportフォルダとfactory_bot.rbファイルを作成し、下記を記述。
ユーザーがログインしている状態でないと投稿出来ないことをテストするためpost_spec.rb user_spec.rbの2ファイルを作成する。spec/support/factroy_bot.rbRSpec.configure do |config| config.include FactroyBot::Syntax::Methods end③spec/rails_helper.rbの編集
spec/rails_helper.rbrequire 'spec_helper' ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../config/environment', __dir__) abort("The Rails environment is running in production mode!") if Rails.env.production? require 'rspec/rails' require 'support/factory_bot' begin ActiveRecord::Migration.maintain_test_schema! rescue ActiveRecord::PendingMigrationError => e puts e.to_s.strip exit 1 end RSpec.configure do |config| config.fixture_path = "#{::Rails.root}/spec/fixtures" config.use_transactional_fixtures = true config.infer_spec_type_from_file_location! config.filter_rails_from_backtrace! #今回の記事では関係ないがsystem_specにおいてヘッドレスモードでchromeを起動するための記述 config.before(:each) do |example| if example.metadata[:type] == :system if example.metadata[:js] driven_by :selenium_chrome_headless, screen_size: [1400, 1400] else driven_by :rack_test end end end #capybaraに関する記述 config.include Capybara::DSL endダミーデータの作成
spec/factories/user.rbFactoryBot.define do factory :user do name {Faker::Name.name} email {Faker::Internet.email} password {'password'} password_confirmation {'password'} end endspec/factories/post.rbFactoryBot.define do factory :post do body {Faker::Lorem.characters(number: 20)} user end endテストコードの作成
spec/models/post_spec.rbrequire 'rails_helper' RSpec.describe 'post model test', type: :model do describe 'validation test' do #factoriesで作成したダミーデータを使用する let(:user) {FactoryBot.create(:user)} let!(:post) {build(:post, user_id: user.id)} #test_postを作成し、空欄での登録ができるか確認 subject {test_post.valid?} let(:test_post) {post} it 'titleカラムが空欄でないこと' do test_post.title = '' is_expected.to eq false; end it '21文字以上であればfalse' do post.title = Faker::Lorem.characters(number: 21) expect(post).to eq false; end end endRSpecの実行
$ bin/rspec spec/models/post_spec.rb
- 投稿日:2021-01-03T22:18:05+09:00
個人アプリ開発日記 #3
目次
1. はじめに
前回の個人アプリ開発日記#2では、このアプリを開発したいと思ったきっかけについて述べました。
そして3記事目の今回は、私が現在作っている「フレンドマッチングアプリ」と
既存のアプリについて比較します!理由は、将来的にリリースすることも視野に入れているので、
せっかく作ったものの「これって○○と似てない?」「○○と何が違うの?」
というような残念なことにしたくないからです。とは言えまだまだ詰めが甘い点は多いですが、自分なりに差別化を図りながら制作しているつもりなので、
思考整理も兼ねて投稿します。2. 内容
まずは、既存のアプリを友人、他人、1:1、1:nの4つの切り口で、簡単なマトリックスにしてみました。
友人 他人 1:1 チャット(LINE, WeChat, Snapchat) マッチング(Tinder, Pairs, with, タップル) 1:n SNS(Facebook, Instagram, Twitter) メディア(YouTube, TikTok, SHOWROOM LINEのオープンチャット、Twitterの趣味垢、InstagramやFacebookのDM/
メッセンジャーなどを含めると、もっと細分化してしまいますが、
ざっくりしたところでは上記のように大別できるのではないでしょうか。私のフレンドマッチングアプリは、1:1で他人がターゲットなのでマッチング系に属することになります。
しかし、既存のマッチング系は顔写真でスワイプで好みを選択したり、恋愛・婚活寄りが多いと感じています。
「つながりを、もっと身近に」というコンセプトは以前の記事にも書いたのですが、あくまで年齢や出身地、
血液型などの項目別もしくは、ハッシュタグ検索を主としています。また「友達が欲しいけど作る機会に恵まれていない人」をペルソナに設定しているため、友達や雑談相手
探しに利用することをイメージしています。ただし、フレンドマッチングアプリ自体は当然先行がいて
("Tantan"というアプリ名で顔写真でスワイプする点はTinderと同一)、マッチングアプリ世界王者の
Tinderも当初の恋愛系からライフスタイル全般のマッチングに裾野を広げていますので、
独自性を追求すべくこれからも動きを注視したいと考えています。5. おわりに
アプリ制作は初めてのため初心者ですが、中身の細かい実装をしながらでも、
視野が狭くなりすぎないように適度にアプリ全体を俯瞰することを意識しています!
- 投稿日:2021-01-03T21:42:32+09:00
Microposts機能(rails)
この記事では、Micropostsにおける投稿機能について、簡単に理解したのでその確認として理解を深めることを目的としている。
投稿機能は以下の手順デコーディングを行う
- Micropostモデル、contentカラム、user_idカラム(外部キー)を作る
- UserテーブルとMicropostテーブルを関連付ける
- UsersコントローラーのshowアクションにMicropostの中身を表示させるための「@microposts」を記載する
- Usersフォルダーのviewのshow.html.erbにも@micropostsを記載する
- Micropostsコントローラーを作り、_micropost.html.erbにmicropostテーブルのcontentカラムの中身を表示させるファイルを作る
- Homeページをアップデートする
1.Micropostモデル、contentカラム、user_idカラム(外部キー)を作る
$ rails g model Micropost content:string user_id:integer ↓ $ rails db:migrate:reset db:seed↑データベースをいじったので一度migrateファイルをリセットしておく
Microposは誰の投稿化を知るために必ずuser_idを持たなければいけないので以下のコードを打ち込む
class Micropost < ActiveRecord::Base *** validates :user_id, presence: true endUserテーブルとMicropostテーブルを関連付けをする
models/micropost.rb class Micropost < ApplicationRecord *** belongs_to :user validates :user_id, presence: true endmodels/user.rb class User < ApplicationRecord *** has_many :microposts, *dependent: :destroy ... end最新の投稿が投稿欄の一番上に表示されるようにする
class Micropost < ApplicationRecord belogns_to :user *** default_scope -> { order('created_at DESC')} validates :user_id, presence: true end*dependent: :destroyを使うことによりuserが退会した場合にそのuserが投稿した内容も同時に削除されるようにする
投稿文字数をTwitterのように140文字以内でしか投稿できないようにする
class Micropost < ApplicationRecord belongs_to :user default_scope -> {order('created_at DESC')} *** validates :content, presence: true, length: { maximum: 140 } validates :user_id, presence: true end3. UsersコントローラーのshowアクションにMicropostの中身(content)を表示させるための「@microposts」を記載する
class UsersController < ApplicationController /// def show @user = User.find(params[:id]) @microposts = @user.microposts.paginate(page: params[:page], per_page: 12) #userに紐づくすべての投稿をpaginateで取得する。 endviews/users/show.html.erb<% provide(:title, @user.name)%> <div class="container pt-4"> <div class="row"> <aside class="span"> <section> <h1> <%= gravatar_for @user %> <%= @user.name%> </h1> </section> </aside> <div class="col-9"> <div> <% if @user.microposts.any?%> #モデルにデータが存在する→true/存在しない→falseを返すメソッド <h3>Microposts (<%= @user.microposts.count %>)</h3> <ol class="microposts"> <%= render @microposts%> </ol> <%= will_paginate @microposts%> <% end %> </div> </div>5. Micropostsコントローラーを作り、_micropost.html.erbにmicropostテーブルのcontentカラムの中身を表示させるファイルを作る
terminalrails g controller Microposts=>createアクションとdestroyアクションを作るためにmicropostsコントローラーを作る
views/microposts/_micropost.html.erb<li> <h4><%= micropost.content%></h4> #micropostテーブルのcontentカラムの内容を表示 <span class="timestamp"> Posted <%= time_ago_in_words(micropost.created_at) %> ago. #created_atで作成日時を表示する際に、Twitterのように「~前」と表示する方法 ※ex:約19時間前 </span> </li>=> showページでmicroposts投稿内容と作成日を表示させる
▼Fakerでダミーデータを作成する
db/seeds.rbusers = User.order(:created_at).take(7) 50.times do content = Faker::Lorem.sentence(3) users.each { |user| user.microposts.create!(content:content) } end$rails db:migrate:reset db:seed=> データベースの中身を変更したのでリセットする
createとdestroyのルーティングをresourcesメソッドを使って定義する
config/routes.rb/// resources :users resources :sessions, only: [:new, :create, :destroy] resources :microposts, only: [:create, :destroy] ///6. Homeページをアップデートする
views/static_pages/home.html.erb<% provide(:title, 'Home')%> <% if logged_in?%> <div class="row"> <aside class="col-3"> <section> <%= render 'shared/user_info' %> </section> <section> <%= render 'shared/micropost_form' %> </section> </aside> <!-- Microposts here --> </div> <% else %> <h1 style="text-align:center;">Awesome Blog</h1> <p> Welcome to Awesome Blog!<br> </p> <p> This Blog is made for all people who want to get good shape of the body!! </p>=>userがログインしているときは投稿機能とユーザーの情報を表示するがログインしていないときはAwesome Blogを表示するようにする
views/shared/_user_info.html.erb<%= link_to gravatar_for(@user, size: 52), @user%> <h1> <%= @user.name%> #usersデーブルのnameカラム </h1> <span> <%= pluralize(@user.microposts.count, "micropost")%> #pluralizeヘルパー...最初の引数に整数があると、それに基づいて2番目の引数である英単語が複数形に変化したものが渡される。 </span>views/shared/_micropost_form.html.erb<%= form_for(@micropost) do |f| %> #特定のモデルに特化したフォームを作りたい → form_for <div> <%= f.text_area :content, placeholder: "Compose new micropost...", class: "w-100"%> </div> <%= f.submit "Post", class: "btn btn-block btn-primary" %> <% end %>=>form_forメソッドを使うとモデルのインスタンスを渡すだけで自動的にRailsが処理してくれる(RESTfulなリソースを使っている場合)
form_for(@micropost)の@micropostがすでに保存されていた場合にはupdateアクション、未保存の時はcreateアクションを、Railsは自動的に選んでくれる※@micropostは後でhomeコントローラーで定義するmicropostsコントローラーのcreate,destroyアクションにアクセスできるのはログインを正しくしたユーザだけに制限する
controllers/microposts_controller.rbbefore_action :only_loggedin_users, only: [:create, :destroy] def create end def destroy end=>before_actionをすることによりURLから直接アクセスされることを防ぐ(ログインしていなければログインページにとぶ)
micropostsコントローラーの中身を記載していく
controllers/microposts_controller.rbbefore_action :only_loggedin_users, only: [:create, :destroy] def create @micropost = current_user.microposts.build(micropost_params) #microposts_paramsとは下のprivateで定義したもの #.build≒.new if @micropost.save flash[:success] = "Successfully saved!" redirect_to root_url else flash[:danger] = "Invalid content. Try again." redirect_to root_url end end def destroy end private def micropost_params params.require(:micropost).permit(:content) end=>投稿内容を@micropostに格納し保存するに成功したらflashメッセージを表示させ(flash[:success]=)homeページに投稿を反映させる(redirect_to root_url)
controllers/static_pages_controllerclass StaticPagesController < ApplicationController def home @micropost = current_user.microposts.build if logged_in? #1対多の場合では、「関連付けメソッド名.build」となる end def about end def contact end end=>現在ログインしているユーザーモデルに紐づく投稿のオブジェクトを作成する
▼すべての投稿を表示する
views/static_pages/home.html.erb<% if logged_in?%> <div class="row"> <aside class="col-3"> <section> <%= render 'shared/user_info' %> </section> <section> <%= render 'shared/micropost_form' %> </section> </aside> <!-- Microposts here --> <div class="col-9"> <h3>Micropost Feed</h3> *** <%= render 'shared\feed'%> </div> </div> <% else %>=>_feed.html.erbに投稿一覧を表示するようにする
controllers/static_pages_controllerdef home #@micropost = current_user.microposts.build if logged_in? if logged_in? @micropost = current_user.microposts.build @feed_items = current_user.feed.paginate(page: params[:page], per_page: 12) #user.rbで定義したfeedメソッドをの取得結果を@feed_itemsに格納する @user = current_user end end=>homeアクションのリファクタリングを行う。
models/user.rb#feed/// def feed Micropost.where("user_id = ?", id) end ///=>この部分がいまいち理解できない
views/shared/_feed.html.erb<% if @feed_items.any? %> <ol> <%= render partial: 'shared/feed_item', collection: @feed_items %> </ol> <%= will_pagenate @feed_items %> <% end %>views/shared/_feed_item.html.erb<li class="col-12 my-4"> <%= link_to gravatar_for(feed_item.user), feed_item.user %> <span class="user"> <%= link_to feed_item.user.name, feed_item.user %> </span> <h4><%= feed_item.content %></h4> <span class="timestamp"> Posted <%= time_ago_in_words(feed_item.created_at) %> ago. </span> </li>controllers/microposts_controller.rb#create/// def create @micropost = current_user.microposts.build(micropost_params) if @micropost.save flash[:success] = "Successfully saved!" redirect_to root_url else *** @feed_items = [] flash[:danger] = "Invalid content. Try again." redirect_to root_url end end ///=>@feed_items = []を加えることで@feed_itemsの中に何も入っていない(nil)の時に発生するエラーを防ぐ
削除機能を作る
views/microposts/_microposts.html.erb<li> <h4><%= micropost.content%></h4> <span class="timestamp"> Posted <%= time_ago_in_words(micropost.created_at) %> ago. </span> *** <% if current_user?(micropost.user) %> <%= link_to "delete", micropost, method: :delete, data: { confirm:"Are you sure?"} title: micropost.contentn %> <% end %> *** </li>=>ログインしているユーザーのshowページにだけ削除機能があらわれるようにする
views/shared/_feed_item.html.erb<li class="col-12 my-4"> <%= link_to gravatar_for(feed_item.user), feed_item.user %> <span class="user"> <%= link_to feed_item.user.name, feed_item.user %> </span> <h4><%= feed_item.content %></h4> <span class="timestamp"> Posted <%= time_ago_in_words(feed_item.created_at) %> ago. </span> *** <% if current_user?(feed_item.user) %> <%= link_to "delete", feed_item, method: :delete, data: { confirm: "You sure?"} title: feed_item.content %> <% end %> *** </li>destroyアクションの中身を記載する
controllers/microposts_controller.rb#destroydef destroy Micropost.find(params[:id]).destroy redirect_to root_url end
- 投稿日:2021-01-03T21:11:49+09:00
【Ruby】購入機能実装〜ログイン中・販売済商品に対しての分岐〜
解決したいこと
①出品者が自分の出品商品を購入できてしまう
②出品者が完売商品に対して商品の編集・削除ができてしまう
③完売商品に対してもう一度購入ができてしまうif文の分岐すごく苦手なんです。
どこをどう考えたらいいのかわからないので、まずは紙に書き出してみる。出品者
自分の出品物の販売中商品だけ編集&削除OK
購入は全てNG購入者
販売中商品のみ購入OK
編集&削除は全てNGだいぶ絞れたので実際にコードにしてみます。
1<% if user_signed_in? %> 2 <% if current_user.id == @item.user_id %> 3 <% unless @item.purchase.present? %> 4 <%= link_to '商品の編集', edit_item_path(@item.id), method: :get, class: "item-red-btn" %> 5 <p class='or-text'>or</p> 6 <%= link_to '削除', item_path(@item.id), method: :delete, class:'item-destroy' %> 7 <% end %> 8 <% end %>1行目
まずはログイン済ユーザーかどうかをチェック。2行目
そのユーザーが出品者かどうかをチェック。3行目
完売商品ではないかをチェック。4〜6行目
3行目がunlessなので完売商品ではなければ(偽)編集&削除ボタンを表示させる。時間がかかったのはこの後、どうしても購入ボタンが表示されてしまう。
elsifで分岐・・・いや違う。
もう一度if文で分岐・・・これも違う。
そもそも最初の分岐で売れてるか売れてないかを判断したらいいんじゃないか・・・?
そう思い、生み出した奇跡のコードがこちら。<% if current_user.id == @item.user_id && @item.purchase.present? %>わけがわからないですね?
もう一度書いた図を見てみる。
OKなのは販売中商品のみということか。
しかし販売中商品に購入ボタンをつけてしまうと出品者にも購入ボタンが表示されてしまう。
そこでハッと気付く。2行目
そのユーザーが出品者かどうかをチェック。ここでelseを使えばユーザーではあるけど出品者ではない=購入者ということではないのかな?
1<% if user_signed_in? %> 2 <% if current_user.id == @item.user_id %> 3 <% unless @item.purchase.present? %> 4 <%= link_to '商品の編集', edit_item_path(@item.id), method: :get, class: "item-red-btn" %> 5 <p class='or-text'>or</p> 6 <%= link_to '削除', item_path(@item.id), method: :delete, class:'item-destroy' %> 7 <% end %> 8 <% else %> 9 <% unless @item.purchase.present? %> 10 <%= link_to '購入画面に進む', item_orders_path(@item.id),class:"item-red-btn"%> 11 <% end %> 12 <% end %> 13<% end %>8行目
ログイン中ユーザーだけど出品者じゃない分岐。9行目
3行目と同じく出品物が完売ではないかをチェック。10行目
9行目がunlessなので完売商品ではなければ(偽)購入ボタンを表示させる。
うまいこといきました。
自己解決できるとまた一つ階段を昇れた気がします。
※違っている点、もっと簡単に書けるよというものがあればご指摘頂ければ幸いです。
- 投稿日:2021-01-03T21:11:49+09:00
【Ruby】購入機能実装〜ログイン中・完売商品に対しての分岐〜
解決したいこと
①出品者が自分の出品商品を購入できてしまう
②出品者が完売商品に対して商品の編集・削除ができてしまう
③完売商品に対してもう一度購入ができてしまうif文の分岐すごく苦手なんです。
どこをどう考えたらいいのかわからないので、まずは紙に書き出してみる。出品者
自分の出品物の販売中商品だけ編集&削除OK
購入は全てNG購入者
販売中商品のみ購入OK
編集&削除は全てNGだいぶ絞れたので実際にコードにしてみます。
1<% if user_signed_in? %> 2 <% if current_user.id == @item.user_id %> 3 <% unless @item.purchase.present? %> 4 <%= link_to '商品の編集', edit_item_path(@item.id), method: :get, class: "item-red-btn" %> 5 <p class='or-text'>or</p> 6 <%= link_to '削除', item_path(@item.id), method: :delete, class:'item-destroy' %> 7 <% end %> 8 <% end %>1行目
まずはログイン済ユーザーかどうかをチェック。2行目
そのユーザーが出品者かどうかをチェック。3行目
完売商品ではないかをチェック。4〜6行目
3行目がunlessなので完売商品ではなければ(偽)編集&削除ボタンを表示させる。時間がかかったのはこの後、どうしても購入ボタンが表示されてしまう。
elsifで分岐・・・いや違う。
もう一度if文で分岐・・・これも違う。
そもそも最初の分岐で売れてるか売れてないかを判断したらいいんじゃないか・・・?
そう思い、生み出した奇跡のコードがこちら。<% if current_user.id == @item.user_id && @item.purchase.present? %>わけがわからないですね?
もう一度書いた図を見てみる。
OKなのは販売中商品のみということか。
しかし販売中商品に購入ボタンをつけてしまうと出品者にも購入ボタンが表示されてしまう。
そこでハッと気付く。2行目
そのユーザーが出品者かどうかをチェック。ここでelseを使えばユーザーではあるけど出品者ではない=購入者ということではないのかな?
1<% if user_signed_in? %> 2 <% if current_user.id == @item.user_id %> 3 <% unless @item.purchase.present? %> 4 <%= link_to '商品の編集', edit_item_path(@item.id), method: :get, class: "item-red-btn" %> 5 <p class='or-text'>or</p> 6 <%= link_to '削除', item_path(@item.id), method: :delete, class:'item-destroy' %> 7 <% end %> 8 <% else %> 9 <% unless @item.purchase.present? %> 10 <%= link_to '購入画面に進む', item_orders_path(@item.id),class:"item-red-btn"%> 11 <% end %> 12 <% end %> 13<% end %>8行目
ログイン中ユーザーだけど出品者じゃない分岐。9行目
3行目と同じく出品物が完売ではないかをチェック。10行目
9行目がunlessなので完売商品ではなければ(偽)購入ボタンを表示させる。
うまいこといきました。
自己解決できるとまた一つ階段を昇れた気がします。
※違っている点、もっと簡単に書けるよというものがあればご指摘頂ければ幸いです。
2021 1/4
一部修正。1<% if user_signed_in? && @item.purchase == nil %> 2 <% if current_user.id == @item.user_id %> 3 <%= link_to '商品の編集', edit_item_path(@item.id), method: :get, class: "item-red-btn" %> 4 <p class='or-text'>or</p> 5 <%= link_to '削除', item_path(@item.id), method: :delete, class:'item-destroy' %> 6 <% else %> 7 <%= link_to '購入画面に進む', item_orders_path(@item.id),class:"item-red-btn"%> 8 <% end %> 9<% end %>1行目
unlessで繰り返されていたものを1行目に追記。
@item.purchase == nilで、「商品の購入情報がnil(まだ購入されていない)」と表現。4行ほど短く、コンパクトにすることができました。
奥が深い。
- 投稿日:2021-01-03T21:01:57+09:00
【Ruby】FizzBuzz問題
概要
Rubyの勉強として FizzBuzz問題 を while と each を使用して解きました。解き方は他にもありますが、今回は個人的に使用頻度の低い while と、よく使用する each で問題を解いてみました。
目次
実践
- 問題
- 解答(while)
- 解答(each)
補足
まとめ
参考文献
実践
問題
1~100
の数字をターミナルに出力してください。【条件】
- 値が3の倍数のとき、
"Fizz"
と出力- 値が5の倍数のとき、
"Buzz"
と出力- 値が3と5の倍数のとき、
"FizzBuzz"
と出力解答(while)
def fizz_buzz num = 0 while (num <= 100) do # 100まで繰り返す条件 num += 1 # 繰り返すたびに1を足していく if (num % 15) == 0 # 15の倍数のとき p 'FizzBuzz' elsif (num % 3) == 0 # 3の倍数のとき p 'Fizz' elsif (num % 5) == 0 # 5の倍数のとき p 'Buzz' else # それ以外の時 p num end end end fizz_buzz解答(each)
def fizz_buzz (1..100).each do |num| # 1~100のループ if (num % 15) == 0 # 15の倍数のとき p 'FizzBuzz' elsif (num % 3) == 0 # 3の倍数のとき p 'Fizz' elsif (num % 5) == 0 # 5の倍数のとき p 'Buzz' else # それ以外のとき p num end end end fizz_buzz補足
値が3と5の倍数のとき、
"FizzBuzz"
と出力num % 3 == 0 && num % 5 == 0解答では
(num % 15) == 0
このように書いていますが、上記のように置き換えることができます。3の倍数 or 5の倍数( 15の倍数 ) という条件を先に書く理由
問題文にもあるとおり、 「3と5の倍数」のとき、FizzBuzz なので 3の倍数でもあり5の倍数( 15の倍数 ) でなくてはいけません。この条件を最後に書いてしまうと、その前に 「3の倍数」 と 「5の倍数」 の条件が評価されてしまうのではじめに 15の倍数 を追加しています。
まとめ
- FizzBuzz問題の解き方は他にもあり上記で上げた以外でも解くことができる
- 今回のような条件があるときには 15の倍数 をはじめに追加しないと評価されない
参考文献
- 投稿日:2021-01-03T19:35:39+09:00
文字を数える
任意の文字列の中に指定した文字がいくつあるか数えて、その数を出力するメソッドを作ります。
"go"という文字がいくつあるかで作成してみます。
scanメソッドが使えそうです。scanメソッドは対象の要素から指定した文字列を数えて、配列として返すメソッドです。
参考:
Ruby 3.0.0 リファレンスマニュアル, scandef count_go(str) puts str.scan("go") endこれで"go"を配列として取得できました。最後にその数を取得するための記述をおないます。
def count_go(str) puts str.scan("go").length endこれで、数で出力することができました。
- 投稿日:2021-01-03T19:22:31+09:00
RSpecの準備
この投稿は、自己学習の備忘録です。
railstutorialを一通り学習し、テストをminitestからRSpecへ書き換えようと思いたったことがこの記事を執筆しようとしたキッカケです。
私自身にとって初めての投稿ですが、もしこの投稿がご覧頂いた方のヒントになっていれば幸いです。
rails new時のオプション
$ rails new --skip-test --skip-bunldeデフォルのminitestとbundle install をスキップしておく。
gemの追加
Gemfilegroup :development, :test do gem 'rspec-rails' gem 'spring-commands-rspec' gem 'capybara' gem 'factory_bot_rails' gem 'faker' gem 'selenium-webdriver' gem 'database_cleaner' end$ bundle install --without production --path=vender/bundle↑PCないのシステムのgemと混同しないようにするため
RSpec用の初期ファイルのインストール
$ rails g rspec:install Running via Spring preloader in process 9045 create .rspec create spec create spec/spec_helper.rb create spec/rails_helper.rbSpringを使用したRSpecの導入
springを使用することでRSpecの実行時間を短縮することができる。
そのために先ほど追加したgem 'spring-commands-rspec'を使用する。
RSpecのstubファイル(./bin/rspec)を作成する。$ bundel exec spring binstub rspecbin/rspecファイルが作成され、RSpecを実行するとspringが使えるようになる。
*'spring-commands-rspec':bin/rspecのコマンドを実行するときに必要となる。RSpecはbin/コマンドをつけることでSpringと言うRailsに組み込まれているアプリを起動させて処理を高速化できる。RSpec自体はrspecと入力するだけで使用可能。
config/environments/test.rbの編集
config/environments/test.rbconfig.active_support.deprecation = :stderr ↓非推奨レポートをどこに出力するかの設定に変更 config.active_support.deprecation = :silenceとりあえずrails db:migrate RAILS_ENV=testを実行
$ rails db:migrate RAILS_ENV=test.rspecファイルの編集
.rspec--require spec_helper --format documentation :テストを通過した文言が表示されるようになる --color今回は以上!
Qiitaでの記事の書き方結構独特なんですね
最低でも月に2記事は投稿できるように頑張ります!
頑張るぞ、2021年!
- 投稿日:2021-01-03T19:14:04+09:00
また複雑な条件分岐
簡単な取り調べゲームをしたいと思います。
容疑者はaとb、どちらも本当のことを証言、または、どちらも嘘の証言をしていたらTrue。
どちらかが本当のことを証言しているが、もう片方が嘘の証言をしていたらFalse。
このようなプログラムを作ろうと思います。まずはメソッドを定義します。
def investigation(a, b) endあとは条件分岐の記述を行えば良さそうです。以下のような論理演算子を組み合わせて記述していきます。
# aもbもtrueの場合にtrue a && b # aかbのどちらかがtrueの場合にtrue a || b # aがtrueの場合にfalse、aがfalseの場合にtrue !a条件分岐処理を記述していきます。
def investigation(a, b) if (a && B ) || (!a && !b) puts "True" else puts "False" end endこれでゲームが作成できました。
- 投稿日:2021-01-03T18:55:19+09:00
任意の素数2つからOpenSSL で使える rsa 秘密鍵を作成する ruby スクリプト
背景
ちょっとした RSA 秘密鍵を、本来的なその変数である素数
p
,q
と、公開乗数e
を指定して自動で作成したいと思いたち、それを行なうスクリプトを記述した。その際に、 Ruby は
1. 整数(Integer)がデフォルトで任意精度
2. OpenSSL が標準ライブラリに含まれるので、 DER まわりの処理が他のライブラリを必要としないで、親和性が高かったので、これを用いた。
スクリプト本体
create-rsa-private.rb#!/usr/bin/env ruby require 'openssl' prime1 = ARGV[0].to_i prime2 = ARGV[1].to_i e = (ARGV[2] || 0x10001).to_i # 以上から、 rsa の key を作る。 n = prime1 * prime2 module ExtendedEuclid # args: a, b # return: [gcd(a,b), x, y] # s.t. x*a + y*b = gcd(a,b) def self.call(a, b) return [a, 1, 0] if b == 0 # a = q*b + r # <=> r = a - q*b q, r = a.divmod(b) # s*b + t*r == gcd gcd, s, t = call(b, r) # s*b + t*(a- q*b) == gcd # t*a + (s - t*q)*b == gcd [gcd, t, s - t*q] end end carmichael = (prime1 - 1).lcm(prime2 - 1) gcd, _, d = ExtendedEuclid.call(carmichael, e) raise "e and lcm(p-1, q-1) not coprime" unless gcd == 1 d = d % carmichael exp1 = d % (prime1 - 1) exp2 = d % (prime2 - 1) gcd, _, prime2_inv = ExtendedEuclid.call(prime1, prime2) raise "p and q not coprime" unless gcd == 1 prime2_inv = prime2_inv % prime1 priv_key = OpenSSL::ASN1::Sequence.new( [0, n, e, d, prime1, prime2, exp1, exp2, prime2_inv].map(&OpenSSL::ASN1::Integer.method(:new)) ) print(priv_key.to_der)利用法
# 生成されるのは der なので $ ./create-rsa-private.rb 103 131 > private.der # pem が良い場合は openssl で変換する $ openssl rsa -in ./private.der -inform DER > private.pem # RSA 秘密鍵として適格か確認 $ openssl rsa -in ./private.pem -text -noout -check Private-Key: (14 bit) modulus: 13231 (0x33af) publicExponent: 65537 (0x10001) privateExponent: 673 (0x2a1) prime1: 101 (0x65) prime2: 131 (0x83) exponent1: 73 (0x49) exponent2: 23 (0x17) coefficient: 64 (0x40) RSA key ok補足
RSA 秘密鍵の定義
https://tools.ietf.org/html/rfc3447
の ASN1 に定義が書いてあり、それをそのまま計算すれば良い。具体的には、
version: 0 modulus: n == p * q publicExponent: e == 65537 (==0x10001) privateExponent: d == e^-1 mod LCM(p-1, q-1) prime1: p prime2: q exponent1: d mod (p-1) exponent2: d mod (q-1) coefficient: q^(-1) mod pな ASN1 の SEQUENCE であれば良い。
素数以外をつっこみたいとき
上で記述した秘密鍵の定義上、
p
とq
が互いに素であって、かつlcm(p-1, q-1)
とe
が互いに素であれば、例えば「合成数をベースにしてしまった RSA 秘密鍵」も割と問題なく作れたりする。擬素数をつっこんで RSA がそのときどのように動作するのか調べたいときにも使える。(自分はむしろこっちがやりたくてこのスクリプトを書いていた)
- 投稿日:2021-01-03T18:52:55+09:00
[Rails]deviseのカスタマイズ。ユーザー登録、編集後のリダイレクトページを変える方法と、編集時のパスワード入力をスキップする方法
駆け出しエンジニアの備忘録です。初投稿です。
またいつか「あれどうやるんだっけ?」となった時に再度調べるのも効率が悪いので、ここに残していこうと思います。
間違いなどがあればご報告をお願い致します。デフォルトはrootパス
僕の環境だとrootはトップページにリダイレクトしてしまうので、どちらもユーザーの詳細画面に遷移するようにしたい。
deviseの導入などの準備は割愛いたします。1.コントローラーの作成
$ rails g devise:controllers usersいくつか作成されたコントローラーの中で今回使うのはregistrations_controller.rb
ここに追記する
controllers/users/registrations_controller.rbclass Users::RegistrationsController < Devise::RegistrationsController # 以下を追記 protected def after_sign_up_path_for(resource) flash[:notice] = '登録完了!編集ボタンからプロフィール画像と自己紹介文を登録できます。' user_path(resource) #ここにリダイレクト先のパスを指定 end def after_update_path_for(resource) user_path(resource) #ここにリダイレクト先のパスを指定 end endこれでコントローラーの準備はok
2.routes.rbの編集
作成したコントローラーに
class Users::RegistrationsController < Devise::RegistrationsController
という記述があるように、元々deviseで使われているDevise::RegistrationsControllerを継承した新しいUsers::RegistrationsControllerを作成した、ということなので、ここに記述をしてくことで追加の設定を反映していくことができる。
(継承してるということに全然気付いておらず、「コントローラーを新しく作るってことはこのコメントアウトされてるnewアクションとかupdateアクションとか全部自分で触る必要あるのか!?」って焦ったけどそのままでも大丈夫だった)
ただしこのままではUsers::RegistrationsControllerを使うことができないので、
以下を記述して使うことを明示する必要がある。routes.rbdevise_for :users, controllers: { registrations: 'users/registrations' }これにより作成したコントローラが使えるようになりました。
ちゃんと指定したパスにリダイレクトされるはずです。編集時のパスワードの入力をスキップする設定
デフォルトだと更新しようとしても設定中のパスワードを入力するように要求されてしまう。
パスワードの入力無しで更新できるようにします。
先程と同様に、コントローラーに以下を追記します。controllers/users/registrations_controller.rbdef update_resource(resource, params) resource.update_without_password(params) endあとはviewにあるcurrent_passwordのフォーム部分を削除すればokです。
参考にさせていただいた記事(参考というより殆どそのまま)
https://qiita.com/Tatty/items/a9759755e562ac4693ec
https://note.com/ruquia7/n/n4838547cb054
https://qiita.com/machamp/items/f6a7b003fcda3f04094a
- 投稿日:2021-01-03T18:44:37+09:00
任意の文字列を削除する
任意の文字列に対してn番目の文字を消し、
その消した文字列を出力するメソッドを作成したいと思います。メソッドのの呼び出し方は、
missing_char(string, num)で呼び出したいと思います。
出力例としては、missing_char('kitten', 1) => itten missing_char('kitten', 2) => ktten missing_char('kitten', 4) =>kitenこのように出力したいと考えています。
sliceメソッドを使用すると配列や文字列から指定した要素を取り出すことができます。
例)string = "hoge" str = string.slice(1) #strに代入した文字列を出力 puts str #=>"o" #sliceメソッドでは文字列は元のまま出力される puts string #=>"hoge"sliceメソッドを使用して、記述していきます。
初めにmissing_charメソッドを作成します。その仮引数に(string, num)を設定します。
def missing_char(string, num) string.slice(num - 1) puts string #stringに"hoge", numに2が渡されているが、元の文字列は変化しない #=>hoge endこの記述では、任意の文字列から指定した要素は取り出すことはできますが、文字列を出力しても、変化はありません。
そこでslice!メソッドを使用します。slice!メソッドは元の配列や文字列を変化させるメソッドです。
Ruby 3.0.0 リファレンスマニュアル, slice!
例)string = "hoge" str = string.slice!(1) puts str #=>"o" puts string #=>"hge" #"o"が取り除かれるこのメソッドを使用すると、
def missing_char(string, num) string.slice!(num - 1) puts string end元の文字列が変化して出力されるようになりました。
- 投稿日:2021-01-03T18:41:44+09:00
【CircleCI】ECS自動デプロイ - CircleCI編 -
参考文献
- [Terraform][Backends][v0.9]tfstateファイルの管理方法
- グループ会社のインフラをECS/Fargateに移行して振り返る
- [AWS][Terraform][Fargate]ECSでコンテナをALB配下に置く
- circleci/aws-ecs@1.4.0
- AWS ECR/ECS へのデプロイ
CircleCIコマンド
◆ config.ymlチェック
$ yamllint .circleci/config.yml◆ CircleCI jobチェック
$ circleci orb validate .circleci/config.yml $ circleci local execute -c .circleci/config.yml --job build $ circleci build --job rspec .circleci/config.yml.circleci/config.yml
.circleci/config.ymlversion: 2.1 orbs: aws-ecr: circleci/aws-ecr@6.12.2 aws-ecs: circleci/aws-ecs@1.3.0 /// executors: ジョブのステップ実行する環境を定義 /// executors: default: docker: - image: circleci/ruby:2.7.1-node-browsers-legacy environment: BUNDLE_JOBS: 3 BUNDLE_RETRY: 3 BUNDLE_PATH: vendor/bundle RAILS_ENV: test DATABASE_HOST: '127.0.0.1' DB_USERNAME: 'root' DB_PASSWORD: 'XXXXXX' - image: circleci/mysql:5.7 environment: MYSQL_DATABASE: sample_dev MYSQL_USER: 'root' MYSQL_ROOT_PASSWORD: 'XXXXXX' docker_build: machine: docker_layer_caching: true /// commands: ジョブ内で実行する一連のステップをマップとして定義 /// commands: bundle_install_rspec: steps: - run: name: Which bundler? command: bundle -v /// ジョブのキャッシュを復元することで、ジョブ高速化 /// - restore_cache: keys: - cache-gem-{{ checksum "Gemfile.lock" }} - cache-gem- - run: name: Bundle Install command: bundle check || bundle install - save_cache: key: cache-gem-{{ checksum "Gemfile.lock" }} paths: - vendor/bundle - run: name: Database create command: DISABLE_SPRING=true bin/rake db:create --trace - run: name: Database setup command: DISABLE_SPRING=true bin/rake db:schema:load --trace - run: name: Run rspec command: | TZ=Asia/Tokyo \ bundle exec rspec --profile 10 \ --out test_results/rspec.xml \ --format progress \ $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) /// Vueインストール /// vue-installation: steps: - restore_cache: keys: - cache-yarn-{{ checksum "yarn.lock" }} - cache-yarn- - run: name: Yarn Install command: yarn install - save_cache: key: cache-yarn-{{ checksum "yarn.lock" }} paths: - node_modules /// jobs:実行処理 /// jobs: rspec: working_directory: ~/rspec executor: default steps: - checkout - bundle_install_rspec - vue-installation deploy_app: working_directory: ~/app executor: default steps: - setup_remote_docker - checkout /// workflows:全てのジョブのオーケストレーション /// workflows: version: 2 build-and-deploy: jobs: - rspec - deploy_app: requires: - rspec - aws-ecr/build-and-push-image: requires: - deploy_app account-url: AWS_ECR_ACCOUNT_URL aws-access-key-id: AWS_ACCESS_KEY_ID aws-secret-access-key: AWS_SECRET_ACCESS_KEY region: AWS_DEFAULT_REGION repo: "${AWS_RESOURCE_NAME_PREFIX}" dockerfile: docker/dev/app/Dockerfile tag: "${CIRCLE_SHA1}" - aws-ecs/deploy-service-update: requires: - aws-ecr/build-and-push-image aws-region: AWS_DEFAULT_REGION family: "${AWS_RESOURCE_NAME_PREFIX}-service" cluster-name: "${AWS_RESOURCE_NAME_PREFIX}-cluster" container-image-name-updates: "container=${AWS_RESOURCE_NAME_PREFIX}-container,image-and-tag=${AWS_ECR_ACCOUNT_URL}/${AWS_RESOURCE_NAME_PREFIX}:${CIRCLE_SHA1}" - aws-ecs/run-task: requires: - aws-ecs/deploy-service-update cluster: "${AWS_RESOURCE_NAME_PREFIX}-cluster" aws-region: AWS_DEFAULT_REGION task-definition: "${AWS_RESOURCE_NAME_PREFIX}-task-definition" count: 1 launch-type: FARGATE awsvpc: true subnet-ids: subnet-XXXXXX,subnet-XXXXXX security-group-ids: sg-XXXXXX,sg-XXXXXX overrides: "{\\\"containerOverrides\\\":[{\\\"name\\\": \\\"${AWS_RESOURCE_NAME_PREFIX}-container\\\",\\\"command\\\": [\\\"bundle\\\", \\\"exec\\\", \\\"rake\\\", \\\"db:migrate\\\", \\\"RAILS_ENV=test\\\"]}]}"
- 投稿日:2021-01-03T18:40:13+09:00
Ruby/GTK3でGUIのJSON Viewerを適当に作る
お正月の気まぐれに小ネタを作ったので記録を残します
Ruby/GTK3を使ってGUIのJSONビュワーを作ってみました。
Ruby/GTK3 の使い方は、ruby-gnomeのsampleディレクトリの中をみれば大体わかります。今回は以下のサンプルを参考にしました。
ポイントは1点で、まずTreeStoreでモデルを作って、次にGtk::TreeViewを作成します。
あとはテンプレ通りに下のYardのコードの使い回しです。なんとなく透けてたりするのは、Gladeの設定でやっています。
- 投稿日:2021-01-03T18:28:39+09:00
Ruby/FiddleのFiddle::Closure::BlockCallerはRubyのガーベッジコレクションから保護しないとダメ
タイトルのとおりです。
RubyからC言語を呼び出すFiddleライブラリでコールバック関数は
Fiddle::Closure::BlockCaller
を使います。Fiddle::Closure::BlockCaller.new(TYPE_INT, [TYPE_VOIDP, TYPE_VOIDP]){|x, y| x.to_s(1) <=> y.to_s(1) }ところが、
Fiddle::Closure::BlockCaller
はRubyのオブジェクトなので、気をつけないとガーベッジコレクションに回収されてしまいます。ガーベッジコレクションに回収されると、メモリが開放されてしまい、呼び出すことができなくなります。そしてSegmentation Faultが発生します。Rubyでは関数の代わりにブロックがあります。ブロックを利用して、動的に
Fiddle::Closure::BlockCaller
のインスタンスを生成するということやりたくなるはずで、この際に注意してオブジェクトへの参照を残しておかないと、GCがせっかく生成した回収していきます。具体的には、レシーバのポインタのインスタンス変数にFiddle::Closure::BlockCaller
を追加してやるなどします。普段Rubyを使う上で、GCやメモリ管理など考えないので、かなり注意していないと、この罠にはまります。
この記事は以上です。
- 投稿日:2021-01-03T18:03:02+09:00
Ruby ディレクトリ内のファイルロックを試す
フルコードは以下。
ディレクトリ内ファイルロックを試すRubyプログラム - sun610.web.fc2.comポイントは
- File::LOCK_EX|File::LOCK_NB を使うこと (参考)
- Dir.globのアスタリスクの仕様
- 投稿日:2021-01-03T17:48:14+09:00
Sidekiq-limit_fetchが仕事しない
ruby on railsの開発において並列処理の実行数を管理したかったが、うまくいかなかったのでその原因のお話
Sidekiq-limit_fetchとは
Sidekiq-limit_fetchはsidekiqでの並列処理の実行数を管理するためのgemです。
https://github.com/brainopia/sidekiq-limit_fetch
Gemfilegem 'sidekiq-limit_fetch'sidekiq.yml:queues: - queue1 - queue2 :limits: queue1: 2 queue2: 5上記の場合、queue1は並列実行可能数が2、queue2は並列実行可能数が5となります。
Sidekiq-limit_fetchが仕事しなかったお話
上記のようにsidekiq-limit_fetchのgemを使用しているにも関わらず並行実行数が指定数を超えることが起きました。
この原因としては他のgemによるlimitの設定関数のオーバーライドでした。
Gemfilegem 'serverspec'上記のgemが原因でSidekiq-limit_fetchに記載されたset関数が正常に処理されませんでした。
serverspecはサーバーのテストを自動化するためのgemです。
そのため、serverspecをGemfileから削除することでsidekiq-limit_fetchは仕事をしてくれるようになりました。しかし、並列実行数が稀に超過する現象は引き続き発生したため、自身でジョブの実行数を管理するコードを書く方が安全だということがわかりました。
- 投稿日:2021-01-03T17:43:52+09:00
【Docker】ECS自動デプロイ - Docker編 -
参考文献
- [Terraform][Backends][v0.9]tfstateファイルの管理方法
- グループ会社のインフラをECS/Fargateに移行して振り返る
- [AWS][Terraform][Fargate]ECSでコンテナをALB配下に置く
- circleci/aws-ecs@1.4.0
- AWS ECR/ECS へのデプロイ
ツリー図
docker/dev├── app │ ├── Dockerfile │ ├── nginx │ │ ├── app.conf │ │ └── nginx.conf │ └── supervisor │ ├── app.conf │ └── supervisord.conf ├── db │ ├── data │ └── mysql_initDockerコマンド
◆ dockerコンテナ削除
$ docker rm -f `docker ps -a -q`◆ dockerイメージ削除
$ docker rmi `docker images -q`◆ dockerイメージ作成
$ docker build -f docker/dev/app/Dockerfile -t sample_dev .◆ ECRプッシュコマンド
$ docker tag XXXXXX sample/dev //Docker hubリポジトリ $ docker images $ docker push sample/dev $ docker tag sample/dev:latest XXXXXX.dkr.ecr.XXXXXX.amazonaws.com/ecs-sample:latest $ docker push XXXXXX.dkr.ecr.XXXXXX.amazonaws.com/ecs-sample:latest◆ dockerコンテナ内実行コマンド
$ bundle install $ bundle exec rake db:create db:migrate // supervisor起動 $ /usr/bin/supervisorctl restart app // production用データベース作成 $ bundle exec rails db:migrate RAILS_ENV=production // アセットプリコンパイル $ bundle exec rake assets:precompile RAILS_ENV=production◆ database.ymlコマンド
$ export RAILS_DATABASE_USERNAME=test $ export RAILS_DATABASE_PASSWORD=password $ export RAILS_DATABASE_HOST=rds.XXXXXX.XXXXXX.rds.amazonaws.com $ export RAILS_DATABASE_PORT=3306◆ nginx && pumaコマンド
// ポート確認 $ ps -ef | grep nginx $ ps aux | grep nginx // nginx停止コマンド $ nginx -s stop // PID関連コマンド $ touch /var/run/nginx.pid $ touch /run/nginx.pid // ポート占有確認 $ sudo lsof -i:80 $ ps ax | grep rails // puma起動 $ bundle exec puma -C config/puma.rb $ bundle exec pumactl start◆ supervisorコマンド
// supervisor起動 $ /etc/init.d/supervisor start $ supervisord -c /etc/supervisor/supervisord.conf // supervisorのsocketコマンド $ sudo touch /var/run/supervisor.sock $ sudo chmod 777 /var/run/supervisor.sock $ supervisorctl help stop // supervisordの設定の「/var/run」を「/dev/shm」 に変更する $ sed -i "s/\/var\/run/\/dev\/shm/g" /etc/supervisor/supervisord.confDockerfile
DockerfileFROM ruby:2.7.1 ENV APP_ROOT /var/www/sample_dev ENV LANG C.UTF-8 ENV TZ Asia/Tokyo /// ディレクトリ作成 /// RUN mkdir -p $APP_ROOT RUN mkdir -p /root/tmp WORKDIR $APP_ROOT /// Node.js、Nginx, supervisorインストール /// RUN apt-get update -y && \ apt-get upgrade -y && \ apt-get install -y --no-install-recommends \ bash \ build-essential \ git \ libcurl4-openssl-dev \ libghc-yaml-dev \ libqt5webkit5-dev \ libxml2-dev \ libxslt-dev \ libyaml-dev \ linux-headers-amd64 \ default-mysql-client \ nginx \ nodejs \ openssl \ ruby-dev \ ruby-json \ tzdata \ vim \ supervisor \ zlib1g-dev && \ apt-get clean -y && \ rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/* /// supervisor用logディレクトリ作成 /// RUN mkdir -p /var/log/supervisor /// nginx.conf, conf.d/app.conf作成 /// COPY app/nginx/nginx.conf /etc/nginx/nginx.conf COPY app/nginx/app.conf /etc/nginx/conf.d/app.conf /// supervisord.conf, conf.d/app.conf作成 /// COPY app/supervisor/supervisord.conf /etc/supervisor/supervisord.conf COPY app/supervisor/app.conf /etc/supervisor/conf.d/app.conf /// シンボリックリンク(ヘルスチェックログ) /// /// 各nginxアクセスログ /// RUN ln -sf /dev/stdout /var/log/nginx/access.log RUN ln -sf /dev/stdout /var/log/nginx/app.access.log RUN ln -sf /dev/stderr /var/log/nginx/app.error.log /// アプリケーションログ(pumaログ) /// RUN ln -sf /dev/stdout $APP_ROOT/log/development.log RUN ln -sf /dev/stdout $APP_ROOT/log/production.log CMD [ "/usr/bin/supervisord" ]docker-compose.ymlversion: '3' services: app: build: context: . dockerfile: app/Dockerfile volumes: - ~/sample_dev:/var/www/sample_dev /// ホスト側で80番ポートの許可が必要 /// /// nginxでバーチャルホストを設定する /// ports: - 80:80 environment: MYSQL_ROOT_PASSWORD: XXXXXX depends_on: - db tty: true stdin_open: true db: image: mysql:5.7 ports: - 3306:3306 volumes: # mysql初期化 - ./db/mysql_init:/docker-entrypoint-initdb.d - ./db/data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: XXXXXXNginx
nginx.confuser root; worker_processes 1; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; keepalive_timeout 65; include /etc/nginx/conf.d/*.conf; }app.conf/// アクセスログ、エラーログ設定 /// access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log warn; upstream app { server unix:///var/www/sample_dev/tmp/sockets/puma.sock; } server { listen 80; server_name dev.sample.com; /// URLのパス設定 /// location / { /// リバースプロキシ設定 /// proxy_pass http://app; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; } }puma
config/puma.rbthreads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 }.to_i threads threads_count, threads_count # ポートを開放しておかないと、socketのlistenが行われない #port ENV.fetch('PORT') { 3000 } environment ENV.fetch('RAILS_ENV') { 'development' } plugin :tmp_restart app_root = File.expand_path('../..', __FILE__) # nginxのhttpディレクティブでsocket通信を行う bind "unix://#{app_root}/tmp/sockets/puma.sock" stdout_redirect "#{app_root}/log/puma.stdout.log", "#{app_root}/log/puma.stderr.log", truesupervisor
supervisord.conf/// supervisor.sock作成 /// [unix_http_server] file=/var/run/supervisor.sock [supervisord] /// nodaemon=true: supervisorがforground(最前面)プロセスで振舞う /// nodaemon=true logfile=/var/log/supervisor/supervisord.log pidfile=/var/tmp/supervisord.pid /// rpcinterfaceを有効にすると、supervisorctlが有効になる /// [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface /// supervisorctlを使ってプロセス管理を可能にする /// [supervisorctl] serverurl=unix:///var/run/supervisor.sock [include] files = /etc/supervisor/conf.d/*.confapp.conf[program:app] command=bundle exec puma -C ./config/puma.rb autostart=true autorestart=true stopsignal=TERM user=root directory=/var/www/sample_dev/ stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 [program:nginx] command=/usr/sbin/nginx -g "daemon off;" autostart=true autorestart=true stopsignal=TERM user=root stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0
- 投稿日:2021-01-03T17:31:44+09:00
Gemfileを非エンジニアが理解するまでの過程
Gemfileってなぁに?
ポートフォリオ作成にあたり、あれこれ環境構築していました。
Gemfileってなんやねん。
チュートリアルではわからないままやっていたわけですが、ここいらでまとめておきたい。
Gemfileとはなんぞや \_ヘヘ(∀`*)カタカタ
『Gemfile』とはRailsアプリで利用するgemの一覧を管理するファイルです。
引用gem...??
gemとはrubyのライブラリのことです。
引用・・・ライブラリ??
便利なプログラムの部品をいっぱい集めて、ひとまとめにしたファイルのこと
引用・・・・芋づる式にわからない単語がでてくる。
キーワード: Gemfile, Gemfile.lock, bundler, ライブラリ, gem
Rubyのライブラリ 「gem」
よくある処理をプログラムの部品としていっぱい詰め込んだものをライブラリという。
ライブラリという英語を訳すと図書館なので「本」を例に考えてみる。
「おいしい料理を作る」というハウツー本
「家事を楽にやる方法」といハウツー本
「人生を楽しく生きる方法」というハウツー本これらをライブラリ。つまり「gem」と呼ぶ。
こういった先人の知恵が本という形で無いと、「美味しいごはんを食べるための方法」を自分で作り上げる必要があるし、「家事の効率的な方法」も自分で編みだす必要がある。
でもライブラリであれば、それをそのまま持ってくるだけで、これらの技術を使えるようになる。実際のRailsアプリケーションの場合であれば
「データーベースに接続する」
「テストをする」
「見た目を整える」
「ログイン機能の実装」
「ページネーション機能をつける」こういった「gem」が用意されている。
bundler
先程の本の例で考える。
たとえば「美味しい料理を作る」という本を手に入れて、美味しい料理を作ろうと思うが、この本だけでは実行できない。
この本の中では「包丁の使い方2.0」という本を参照することになっている。
しかし今参照している「包丁の使い方」はパン切り包丁の本なので、「美味しい料理の作り方」の方法は踏襲できない。このようにgemには他のgemを利用しているケースがあり、依存関係にあるgemを用意する必要がある。
この依存関係のgemを一括管理できるのが「bundler」。
※ちなみに「bundler」もgemです。このbundlerは依存関係にあるGemの一括管理だけでなく、バージョンの管理もできるようになる。
「包丁の使い方2.0」では「美味しい料理」をつくる本を使って美味しい料理は作れるが、
「包丁の使い方1.0」では美味しい料理は作れなくなることがある。Gemfile
そしてやっと「Gemfile」がでてくる。
このbundlerでインストールされるgemを管理する場所が「Gemfile」
Railsアプリケーションで使う「gem」の一覧を管理するファイル。
Gemfilesource 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.1.6' # Use sqlite3 as the database for Active Record gem 'sqlite3' # Use Puma as the app server gem 'puma', '~> 3.7' # Use SCSS for stylesheets gem 'sass-rails', '~> 5.0' # Use Uglifier as compressor for JavaScript assets gem 'uglifier', '>= 1.3.0' # Use CoffeeScript for .coffee assets and views gem 'coffee-rails', '~> 4.2' # See https://github.com/rails/execjs#readme # for more supported runtimes # gem 'therubyracer', platforms: :ruby # Use CoffeeScript for .coffee assets and views gem 'coffee-rails', '~> 4.2' # Turbolinks makes navigating your web application faster. # Read more: https://github.com/turbolinks/turbolinks gem 'turbolinks', '~> 5.x' # Build JSON APIs with ease. # Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.0' # Use Redis adapter to run Action Cable in production # gem 'redis', '~> 3.0' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] # Adds support for Capybara system testing and selenium driver gem 'capybara', '~> 2.13' gem 'selenium-webdriver' end group :development do # Access an IRB console on exception pages or by using # <%= console %> anywhere in the code. gem 'web-console' gem 'listen', '>= 3.0.5', '< 3.2' # Spring speeds up development by keeping your application running # in the background. Read more: https://github.com/rails/spring gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' end # Windows環境ではtzinfo-dataというgemを含める必要があります gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]一番上のsourceは「gem」をどこから取ってくるかが指定されている。
例でいうところの「図書館」としておこう。その図書館からいろんな本「gem」を取ってくる。
Rails, sqlite2, pumaなどなど。
各gemの機能はおいおい調べるとして、gemの第2引数でバージョンの指定もできる
1 2 引数なし 最新verを入れる ~>1.1 1.1以降〜2.0以前のverに制限 >=1.0 1.0のverに制限 途中で出てくるgroupはgemをインストール場所を指定できる。
開発環境、テスト環境でだけ使うという指定もこのgroupでできる。Gemfile.lock
このファイルはbundlerでインストールされた「gem」の一覧が表示されている。
ここには依存関係にあるgemも表示される。Gemfileにgem railsと記載されているとrailsおよびrailsが依存するactioncableがインストールされる
引用まとめ
キーワード: Gemfile, Gemfile.lock, bundler, ライブラリ, gem
このキーワードたちをまたまた例を使ってまとめてみよう。
「奥様のご機嫌を取る」という目的がある(アプリケーション)
そのための方法は色々あって、自分で考えてもいいし、図書館(Gemfileにおけるsource)から色々な本(gem)を手に入れて着想を得てもいい。
Gemfileには「洗濯を手伝う方法」「美味しい料理の作り方」「ちゃんと奥様の話を聞く方法」など
「奥様の機嫌を取る」というアプリケーションのために必要な「本」と、その本が最新のものか・はたまた昔ものがほしいのか(バージョン管理、gemの第2引数)を設計図として書いておく。その設計図を元に図書館で本を手に入れてくる(bundle install)のだが、ここで司書さんが「この本もないとだめですね。この本はちょっと古いっすよ」とか依存関係を配慮していい感じで調整してくれる
そして実際に手に入れた本のリストやバージョンの一覧が書かれたリストができあがる(Gemfile.lock)
課題
大枠は理解できたが、これを実際にアプリケーションを作成するなかで導入していくかは、まだはっきりとしていない。
本は手に入れたが、どのように実践に取り入れるのかわからない状態。
ポートフォリオを作りながら学んでいく。引用・参考
https://qiita.com/kamohicokamo/items/ded4cad5be1778547640
https://nishinatoshiharu.com/fundamental-gem-knowledge/
https://wa3.i-3-i.info/word1473.html
- 投稿日:2021-01-03T17:17:13+09:00
緯度経緯をURLへ変換するサービス、マップクリエイター
マップクリエイターとは、どんなWebサービスですか?
Googleマップの任意の地点をWebページ化するサービスです。場所をWebページ化することで空間にテキストが紐付くので、検索サイトから検索した時スムーズにその場所がどこにあるのかが、日本国民全員に分かるようになります。マップクリエイターを作ろうと思ったきっかけは何だったのでしょうか?
旅行に行った時よくバスをよく利用するのですが、田舎のほうになるとバス停がどこにあるのか分からなかったり、バス停が分かってもどのバスがどこへ向かうのかが分からないことが多々あったので、それを上手い具合に解決したいと思って作りました。そこで、ITとかにあまり詳しくない人でも、特定の場所に対して簡単にテキストを埋め込めるサービスがあれば、Googleで検索した時にその場所の詳細がすぐ分かるだろうと思って作りました。
バス停だけにとどまらず、地元の名所やつっこんだ観光案内(雑学など)を書けば、地域復興にも繋がるかなと思います。
機能面でこだわった点などありますか?
Google マップでは場所によっては緯度経緯が表示されず、プラスコードというアルファベットだけの表示になることもあるので、プラスコードにも対応させました。また悪用防止のため、作成したページを削除する機能も付けました。(マップクリエイターは2018年に作ったwebサービスです)
- 投稿日:2021-01-03T17:08:23+09:00
【教材】Tailwind CSS & Rails6でQiitaを模写してみた【バックエンド演習に利用可】
この教材はこんな人におすすめ
- Tailwind CSSが流行っていると知っているけど学習が面倒くさい人
- Tailwind CSSを使ったビューの完成コードが見たい人
- フロント完成コードを元に機能実装(バックエンド)演習をしてみたい人
- 駆け出しエンジニアなので現役エンジニアが書いているコードが見たい人
完成コードをどうぞ
https://github.com/Fumiya-Soeno/tailwind
完成図
Tailwind CSSチートシート
こちらも一緒に眺めましょう。分からないクラス名はここで検索すれば載ってます。
https://nerdcave.com/tailwind-cheat-sheetFont Awesomeチートシート
fa fa-check
みたいなクラス名はTailwindCSSではなくFontAwesomeという別のCSSフレームワークです。アイコン表示に使ってます。
https://fontawesome.com/cheatsheetこれを教材として使う方法(インフラ・バックエンド向け)
問1:以下のQiita内部の仕組みを追加実装せよ
- 記事投稿/閲覧/編集/削除/LGTM(いいね)/記事コメント
- 記事キーワード検索/記事タグ付与/記事タグ検索
- ユーザー登録/プロフィール編集/ユーザーフォロー
- フォロー/記事コメント/LGTM(いいね)の通知
- 記事に画像添付/プロフィール画像設定
- 下書き保存/編集/削除
- 外部SNSリンク(API)
上記repoのようなビューのみコードを渡され『機能実装の部分を担当してね』みたいな仕事が現場によくあります。なので、上記の機能をMySQLやらActiveRecordなりで頑張って実装して下さい。該当箇所にはダミーコードがたくさん埋まってるのでこれをMySQLのデータに紐付ける作業です。
市場相場でいうとこの仕事で大体単価20~50万円くらいです。クラウドソーシングで受ければ多分20万円&納期1ヶ月だと思われます。経験1年のエンジニアなら20日くらいあれば納品可能な規模だと思います。これができれば平日稼働のみで月収20万はいけます多分。
問2:機能実装後、本番環境で動作確認せよ
インフラ学習したい方は、上記の機能実装後、これをAWSなりレンタルサーバFTPなどにあげてみると勉強になりそうです。画像投稿機能(AWSならバケット周り)もしっかり実装できていれば素晴らしいです。EC2のLinuxインスタンスとかdockerあたり使うと色々楽になるのでやってみましょう。こんだけできれば食うには困りません。
問3:広告(実物)を表示せよ
ここはおまけですが、画面右側の広告がダミー(画像)になっているので、これを実際の広告表示にできればあなたが個人サイトを開発すれば広告運用の練習にもなります。このスキルは独立に大変役立つので、余裕があればA8.netやGoogleアフィリエイトなどの広告表示に取り組んでみましょう。注意事項
- トラブル回避したいので、コードの再配布・自作発言はお控え下さい。
Railsを学びたい人向け
Mediumにてこの記事の筆者が学習ロードマップを無料公開中です
そもそもRailsが分からんという方は、上記を読んで頂ければ上に挙げた機能実装スキルは網羅できます。なお、Medium記事のclapボタンを50連打して頂けたら筆者は泣いて喜びます。
筆者連絡先
- 投稿日:2021-01-03T17:08:23+09:00
【無料教材】Tailwind CSS & Rails6でQiitaを模写してみた【バックエンド演習に利用可】
こんな人におすすめ
- Tailwind CSSが流行っていると知っているけど学習が面倒くさい人
- Tailwind CSSを使ったビューの完成コードが見たい人
- フロント完成コードを元に機能実装(バックエンド)演習をしてみたい人
- 駆け出しエンジニアなので現役エンジニアが書いているコードが見たい人
完成コードをどうぞ
https://github.com/Fumiya-Soeno/tailwind
完成図
これを教材として使う方法(インフラ・バックエンド向け)
問1:以下のQiita内部の仕組みを追加実装せよ
- 記事投稿/閲覧/編集/削除/LGTM(いいね)/記事コメント
- 記事キーワード検索/記事タグ付与/記事タグ検索
- ユーザー登録/プロフィール編集/ユーザーフォロー
- フォロー/記事コメント/LGTM(いいね)の通知
- 記事に画像添付/プロフィール画像設定
- 下書き保存/編集/削除
- 外部SNSリンク(API)
上記repoのようなビューのみコードを渡され『機能実装の部分を担当してね』みたいな仕事が現場によくあります。なので、上記の機能をMySQLやらActiveRecordなりで頑張って実装して下さい。該当箇所にはダミーコードがたくさん埋まってるのでこれをMySQLのデータに紐付ける作業です。
市場相場でいうとこの仕事で大体単価20~50万円くらいです。クラウドソーシングで受ければ多分20万円&納期1ヶ月だと思われます。経験1年のエンジニアなら20日くらいあれば納品可能な規模だと思います。これができれば平日稼働のみで月収20万はいけます多分。
問2:機能実装後、本番環境で動作確認せよ
インフラ学習したい方は、上記の機能実装後、これをAWSなりレンタルサーバFTPなどにあげてみると勉強になりそうです。画像投稿機能(AWSならバケット周り)もしっかり実装できていれば素晴らしいです。EC2のLinuxインスタンスとかdockerあたり使うと色々楽になるのでやってみましょう。こんだけできれば食うには困りません。
問3:広告(実物)を表示せよ
ここはおまけですが、画面右側の広告がダミー(画像)になっているので、これを実際の広告表示にできればあなたが個人サイトを開発すれば広告運用の練習にもなります。このスキルは独立に大変役立つので、余裕があればA8.netやGoogleアフィリエイトなどの広告表示に取り組んでみましょう。Tailwind CSSチートシート
こちらも一緒に眺めましょう。分からないクラス名はここで検索すれば載ってます。
https://nerdcave.com/tailwind-cheat-sheetFont Awesomeチートシート
fa fa-check
みたいなクラス名はTailwindCSSではなくFontAwesomeという別のCSSフレームワークです。アイコン表示に使ってます。
https://fontawesome.com/cheatsheet注意事項
- トラブル回避したいので、コードの再配布・自作発言はお控え下さい。
Railsを学びたい人向け
Mediumにてこの記事の筆者が学習ロードマップを無料公開中です
そもそもRailsが分からんという方は、上記を読んで頂ければ上に挙げた機能実装スキルは網羅できます。なお、Medium記事のclapボタンを50連打して頂けたら筆者は泣いて喜びます。
筆者連絡先
- 投稿日:2021-01-03T16:40:51+09:00
Serverless FrameworkとAWS Lambda with Rubyの環境にgemインストール
gemインストールが必要なAWS LambdaのRubyスクリプトをServerless Frameworkでデプロイする方法です。
手順概要
プラグインを入れれば簡単にできます。
serverless plugin install -n serverless-ruby-layer
Gemfile
作成- あとは普通にデプロイすると勝手にいろいろやってくれる
手順詳細
Serverless Frameworkのサービス作成
$ serverless create --template aws-ruby Serverless: Generating boilerplate... _______ __ | _ .-----.----.--.--.-----.----| .-----.-----.-----. | |___| -__| _| | | -__| _| | -__|__ --|__ --| |____ |_____|__| \___/|_____|__| |__|_____|_____|_____| | | | The Serverless Application Framework | | serverless.com, v2.16.1 -------' Serverless: Successfully generated boilerplate for template: "aws-ruby" Serverless: NOTE: Please update the "service" property in serverless.yml with your service nameファイルが3つ生成されます。
.gitignore handler.rb serverless.ymlプラグインインストール
serverless-ruby-layer
というプラグインをインストールします。$ serverless plugin install -n serverless-ruby-layer以下のファイルやディレクトリが増えます。
node_modules package.json package-lock.jsonServerless FrameworkがNode.jsで実装されているので、Rubyのプロジェクトなのに
node_modules
やpackage.json
が存在することになるようです。ソースコード
serverless.yml
serverless.yml
は以下の内容にします。plugins
のところの記述はプラグインをインストールすると勝手に追記されています。service: sample frameworkVersion: '2' provider: name: aws runtime: ruby2.7 region: ap-northeast-1 functions: hello: handler: handler.hello plugins: - serverless-ruby-layerGemfile
Gemfile
を作成し、以下の内容にします。gemのサンプルとして
holiday_japan
を使ってみます。日本の祝日を判定するgemです。source "https://rubygems.org" gem 'holiday_japan'Rubyソースコード
handler.rb
require 'json' require 'holiday_japan' def hello(event:, context:) holidayName = HolidayJapan.name(Date.new(2021, 8, 8)) puts(holidayName) # CloudWatch に "山の日" と書き出される endデプロイ
ここまで作成してから
serverless
コマンドでデプロイすると、Lambda本体だけでなく、serverless
コマンドが自動でgemインストールしたイメージを作成し、AWS LambdaのLayerとしてアップロードしてくれます。$ serverless deploy -v実行
デプロイ結果をAWSマネジメントコンソールで見ると次のように見えます。
Lambda
Layer
実行結果のCloudWatch Logs
- 投稿日:2021-01-03T16:05:05+09:00
【買付代行サービス個人開発 - No.012】ドロップダウンをJSで実装
概要
右上のドロップダウンメニューが常に表示されているので、ユーザーアイコンをクリック後、ドロップダウンメニューが表示されるようにする。
参考
tailwind ui
https://tailwindui.com/components/application-ui/elements/dropdowns
tippy.js
https://atomiks.github.io/tippyjs/v6/all-props/
https://on-ze.com/archives/7310
ToDo内容
Terminal# npm npm i tippy.js # Yarn yarn add tippy.js yarn add turbolinksapp/javascript/src/js/dropdown.jsimport tippy from 'tippy.js' import 'tippy.js/animations/perspective.css'; document.addEventListener('turbolinks:load', init) function init () { tippy('[data-dropdown]', { content (ref) { if (!ref.dataset.dropdownTemplate) { const id = ref.getAttribute('data-dropdown') const template = document.getElementById(id) ref.dataset.dropdownTemplate = template.innerHTML template.remove() } return ref.dataset.dropdownTemplate }, trigger: 'click', allowHTML: true, interactive: true, appendTo: () => document.body, animation: 'perspective', duration: 100, placement: 'bottom-end' }) }app/components/layout/navbar_component.html.slimbutton.flex.text-sm.border-2.border-transparent.rounded-full.focus:outline-none.focus:border-gray-300.transition.duration-150.ease-in-out( type="button" class="group h-full flex items-center px-4 text-left focus:outline-none focus:rounded-md" data-dropdown="navbar-dropdown" ) i.fas.fa-user-circle.fa-2x.text-gray-500 #navbar-dropdown.hidden .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 [@org], 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 | 会社詳細動作確認
準備
Terminal-1bin/rails s
Terminal-2bin/webpack-dev-server
受入基準
- 画面右上のドロップダウンメニューが、ユーザーアイコンのクリック後、表示される
- 投稿日:2021-01-03T15:36:54+09:00
VirtualBox,Vagrant, cyberduck を用いたRuby on Rails の環境構築
はじめに
本格的にプログラミングを始めるために新しくMacbookproを買い環境構築を行ったので、次に行うときのための備忘録として記事を書かせていただきます。
初心者のため間違えているところなどある可能性があります(ご指摘いただけると非常にありがたいです)。その点ご留意の上お読みください。全体の流れ
以下のような流れで行いました。
- VirtualBoxとVagrantで仮想環境構築
- 1で構築した仮想環境にrbenvをインストールし、rubyとrailsを導入
- cyberduckを用いて仮想環境に接続、vscodeでファイル編集を可能にする
使用したツールのバージョン
参考までに僕が使用したマシンのスペックおよびツールのバージョンを記載します。
マシンスペック
macOS Cataline 10.15.7
MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ: 2 GHz クアッドコアIntel Core i5
メモリ: 16 GB 3733 MHz LPDDR4X各種ツール
VirtualBox: 6.1.16
Vagrant: 2.2.14
cyberduck: 7.7.2VirtualBox,およびVagrantの導入
https://www.sejuku.net/blog/39936 を参考に行いました。Vagrantにcentos7を導入するところまでは全く同様で行けました。
rbenv, ruby, rails の導入
次に、rbenvを導入しました。初めはxcode, homebrew を導入したのち、homebrew管理下でrubyをインストールする、という方法を参考にしていたのですが、xcodeコマンドラインツールを導入しているときにエラーが出て解決法が分からず詰んだので直接rbenvを導入する方法に切り替えました。
初めの方法の際に参考にしていたサイトは以下に貼っておきます。homebrewを用いる方法
https://qiita.com/TAByasu/items/47c6cfbeeafad39eda07rbenvを用いる方法は、Vagrantの導入で参考にした上記サイトの続きをそのまま用いました。これで、Railsを導入するところまでは問題なく行けました。しかし、仮想環境上における作業領域(僕の場合は home/vagrant/app/score_app)で以下のコマンド
home/vagrant/app/score_appbin/rails sを用いてもローカルサーバーへの接続が行えませんでした。これは以下の方法で解決しました。
仮想環境から出て、仮想マシンを設置しているフォルダ(僕の場合は、/ユーザー/ユーザー名/MyVagrant/centos7)で、以下のコマンド
/user/user_name/MyVagrant/centos7vi Vagrantfileを実行し、Vagrantfileを編集。ipアドレスの設定を行いました。内容は以下のサイトの行った解決方法①、②を参照。
https://qiita.com/Ago0727/items/325df5e39e3406fa16d2
①のみでは接続できなかったのですが、②で接続できました。ただし、①でも書かれていますがVagrantを用いる場合は接続の際にオプションとして以下を設定しないと接続できないようです。
user/user_name/MyVagrant/centos7bin/rails s -b 0.0.0.0cyberduckへの接続
ここも基本的に次のサイトを参考に行えました。
https://qiita.com/Lassieena/items/603fe89df26b59ca06f7
ただし、SSH private key の設定、送信方式をSFTPにすることに気を付けておけばいけます。最後に、cyberduckの環境設定タブ(macであれば画面上部に表示される)から外部エディタにインストールしたvscodeを指定します。
以上で完了です!最後に
環境構築は様々な方法があり、適切な方法で行うのは初心者にはかなりハードルが高かったです(これで完全にできているのかも不安です)。今回僕がこの方法にたどりつくまでに2回ほど失敗して、どうしたら良いかわからず仮想環境ごと廃棄するのを2回行ったので、もしどうしようもなくなったらもう一度仮想環境から作り直すのもありかもしれません。また、一通りしてみて感じたことは、コマンドの詳細などは理解できないところも多いとは思いますが自分がどこのフォルダにいて、どこに各パッケージをインストールすべきか(しているのか)を把握して構築することが大切だと感じました。仮想環境の廃棄に関してはvagrant コマンド などで検索すれば出てくるので、もし使うときは調べてみるといいかもです。
- 投稿日:2021-01-03T14:00:16+09:00
「え!?いつのまに新刊出てたの!?」を解消するアプリを作りました【ポートフォリオ解説】
はじめに
実務未経験者がどうやって・どんなポートフォリオを制作したのか
ポートフォリオの解説→意識したこと・苦労したことの順で解説します。よければ読んでみてください
コンセプト
「え!?いつのまに新刊出てたの!?」を解消する
好きなマンガや小説など・好きな作家を登録しておくと新刊の発売3日前にメールでお知らせします
ちなみにアプリ名はブクシルです。「ブック」+「(新刊の発売を)知る」ですね。
我ながら気に入ってます。寒くないです。だれですか寒いって言ったの。使用イメージ
書籍の検索ができます(楽天API使用)
お気に入り登録ができます(作品のシリーズと作者名)
発売3日前にメールでお知らせします
開発背景
せっかくポートフォリオをつくるんだったら、だれかの役に立つものが作りたいと思い、以下の流れで作り始めました
- 周囲の知人・友人に「どんなサービスがあったら便利か」「日常生活で不便に感じていることはないか」とヒアリング
- 「気づいたら新刊が発売されていることがあるから事前に知りたい」との意見を聞く
- 自分自身、読書が趣味で同じ悩みを持っていたこともあり、それを解決できるアプリを作りたいとの想いで製作開始
使用技術
フロントエンド
HTML/CSS(Sass)
Bootstrap 4.5.2
jQuery 3.5.1バックエンド
Ruby 2.6.6
Ruby on Rails 6.0.3.3API
楽天書籍ブックスAPI、楽天ジャンル検索APIDBMS
PostgreSQL 13.1インフラ
AWS(VPC・EC2・RDS・S3・IAM・Route53・ACM)
独自ドメイン
HTTPS化DB設計
一番苦労したのがDB設計です。
開発前に軽くER図を作ってからスタートしましたが、当初の構想ではうまくリレーションできず、何回も作り直すことになりました。
最終的に下図の通りになりました。
開発で意識したこと
1. エラー解決方法
実務に入ったときに先輩社員や上司の時間を無駄に奪うことにならないように
まずは自分の力で解決することを強く意識しました。
具体的には、前後することもありますがおおよそ次の順番です。
- エラーメッセージやログをよく読む
- どこの段階でエラーが出ているか推測する
- bindnig.pryでデバッグする
- エラー内容を調べる。(なるべく一次情報から読み解くことを意識。)
- どうしても解決できなければメンターの方に質問する
2. 質問の仕方
Slackを使ってだいたい以下の構成で質問していました。
なるべく少ないやり取りで済み、相手の人の時間を無駄に奪わない内容にすることを意識しました。
- やりたいこと・困っていることを端的に表現
- 自分なりに試したこと (書いたコード・コマンド)
- 試した結果 (エラーメッセージ)
- わからないなりに怪しいと思っている部分を述べる
・・・違っている場合にメンターの方から意見を頂ける
→理解が深まり、以降の質問を減らせる- GitHubのソースコードを記載・・・思わぬところにエラーの原因があることも多いため
実務において1人でずっと悩み続けることはかえって損失になってしまうと思うので
上記のステップである程度独力でやって解決できなかった場合には、端的かつ過不足ない内容で質問することで時間的な損失を最小限にすることを心がけていきたいと思います。3. 模擬チーム開発
実務を想定して模擬チーム開発を意識しました。
・ Git, GitHubを用いたソース管理
・ 必要要件をIssuesに列挙し、タスク消化
・ featureやfixのブランチを切ってプルリクベースで開発ただ、Issuesはプルリクと結びつけられると後で知ったので、使い方が甘かったなと反省しています。
開発で特に苦労したところ
DB設計
上でも述べましたが一番苦労しました。
リレーションについてわかったつもりでも、いざ自分のアプリに落とし込むとなるとうまくいかず、何度も紙に書いては関係性を整理しました。楽天書籍ブックスAPIの適用
公式ドキュメントだけではどう扱うのかのイメージがつかず、調べた限りでは使用例も少なかったです。
少ない情報の中からどう自分のアプリに落とし込むのか、使用例からどこが一般化できるのか、実際にコードを書きながら試行錯誤を繰り返しました。感想
エラー解決がめちゃくちゃ楽しい。
エラーを乗り越えたときに両手を突き上げるほど興奮したことが何回かあります。
ゲームで強いボスを倒したときみたいな感じですね。
負けては次の手を考え実行する、を繰り返して最終的に勝つみたいな。プログラミング学習をはじめたころはエラーが怖かったと思うのですが、成長したのかなあと。
あとは仮設と検証を繰り返す作業が現職の研究業務と似ているなあと思ったりしていました。課題/今後実装したいこと
やりたいことはまだ残っているので、ひとつずつ良くしていきたいと思います。
・レスポンシブWebデザイン(スマホ対応)
・お気に入り登録/解除時にフラッシュ表示
・お気に入り登録/解除時の非同期処理
・LINEログイン
・LINEのMessaging APIを使用してLINEで通知さいごに
長い文章をここまで読んでくださってありがとうございました
良かったと思った方はLGTMして頂けると嬉しいです!
どなたかの参考になれば幸いです!
- 投稿日:2021-01-03T13:47:03+09:00
【Rails】 Gem Rubocop 静的コード解析ツール
Rubocopとは
Rubyのコーディング規約に準拠しているかどうか静的に解析するツールです。.rbファイルに記述してあるコードを全て解析し、インデントやメソッド名等、ターミナルに出力してくれます。
※rbファイルのみなので、.erbは解析してくれません。導入方法
Gemfileに以下を追記します。
gem 'rubocop', require: falseインストール
$ bundle install使用方法
https://docs.rubocop.org/rubocop/1.7/usage/basic_usage.html
コマンド
$ rubocop #解析して、結果をターミナルに出力$ rubocop -a #or $ rubocop --auto-correct #解析して、自動修正設定
.rubocop.ymlに設定を記述していきます。
(私の設定)rubocop.ymlAllCops: inherit_from: .rubocop_todo.yml AllCops: Exclude: - bin/* - config/**/* - db/schema.rb - vendor/**/* - Gemfile NewCops: enable Style/Documentation: Enabled: false Style/EmptyMethod: Enabled: false Style/FrozenStringLiteralComment: Enabled: false
- 投稿日:2021-01-03T13:06:40+09:00
GoogleBooksAPIを用いた書籍検索の結果をjson出力するRuby gemを作成してみた
はじめまして、新卒1年目プログラミング初心者のオオタです!!
社内で主にruby on railsを使っており、自分の中でまだrubyの知識が甘々なので勉強としてgemを作成してみました!今後何かに生かせると思ったので、それの備忘録です。作成したgemについて
今回作成したgemは、GoogleBooksAPIを利用して書籍検索を行い、それをjson出力してみました。(今後何かに使えればいいなと思います…w)
また、作成したgemは公開までやってないのでご了承ください。GoogleBooksAPIに関してはこちらの記事を参考にしました。
公式リファレンスはこちら作成手順
- 開発環境
ruby 2.6.2 Bundler 2.2.4 irb 1.0.0これらを使用するのでgemなどのインストールが必要です。
- githubの新規レポジトリ
gemはgit依存で動作するらしいので作成します。(どっかの記事に書いてあった)
1. gemを作る
bundlerのコマンドを用いて基盤となるものを作成します。
bundle gem gem_name
で作成できます。
今回は書籍検索なのでbook_searchという安直な名前で作成しました。(json出力だからbook_search_jsonとかでもよかったなーとか後悔してる)$ bundle gem book_searchこれでファイルが自動生成されます。
|--bin | |--console | |--setup |--lib | |--books_search.rb | |--books_search | | |--version.rb |--.gitignore |--.rubocop.yml |--books_search.gemspec |--CODE_OF_CONDUCT.md |--Gemfile |--Gemfile.lock |--LICENSE.txt |--README.md |--Rakefile(rubocopはgemで元々入っているので気にしないでください;;)
※ 注意事項
gemの名前をつける際に、間に-
をつけるとlib内のディレクトリ構成が変わってくるので注意が必要です。(自分はここで一回ハマりました。)
- 例
$ bundle gem books-search|--lib | |--books | | |--search | | |--search.rb | | | |--version.rbこんな感じになるので注意されたし。
2. gemspecに加筆する
自動生成されたgemspecにTODOでここに書いて!と言われる部分があるのでそこに加筆する。
そのままbundle install
するとエラー吐きます。自分が変更した部分はこちら
books_search.gemspec# 概要 spec.summary = "GoogleBooksAPI use." # 説明 spec.description = "It uses google to search for books." # 適当に自分のgithubアカウント spec.homepage = "https://github.com/yuki-ohta0086" # ここは"https://rubygems.org"でOK spec.metadata["allowed_push_host"] = "https://rubygems.org" # "source_code_uri"と"source_code_uri"は作成したgithubのレポジトリのURLを入れる spec.metadata["source_code_uri"] = "https://github.com/yuki-ohta0086/books_search" spec.metadata["source_code_uri"] = "https://github.com/yuki-ohta0086/books_search"TODOの部分はこれでOK。あとは環境に必要なgemを書いていく。
今回必要なのはbundler
とrake
が必要なので追加する。(テストまで書きたいのであればrspecやminitestを追加する。)books_search.gemspecspec.add_development_dependency "bundler", "~> 2.2" spec.add_development_dependency "rake", "~> 13.1" spec.add_development_dependency "rspec", "~> 3.0"全て書き終えたら
bundle install
する。$ bundle install成功したら一応pushしておきます。(念のため)
3. gemの内容を書く
ついに本題です。lib内にあるbooks_search.rbに処理内容を書いていきます!!
今回はキーワードを元にGoogleBooksAPIを用いてjsonを出力するだけなのでその処理を書いていきます。books_search.rbrequire 'net/http' require 'uri' require 'json' require_relative 'books_search/version' module BooksSearch class Error < StandardError; end # volume検索のAPIのURL GOOGLEAPI_URL = 'https://www.googleapis.com/books/v1/volumes?q=' # moduleなのでクラスメソッド作らないと呼び出せないので作成 class << self def get_book_json(params) uri = URI.parse(GOOGLEAPI_URL + params) resources = Net::HTTP.get(uri) puts resources end end endgetリクエストが欲しいので調べたら
net/http
というライブラリがあったのでそれを使用しました。処理内容としては
GOOGLEAPI_URL + params
で出来た文字列をパースしてuriに変換。- それをgetリクエストして帰ってきたjsonを出力
これだけです。
もし、ちゃんと利用するのであればこのjsonをrubyオブジェクトに変換していろいろ活用できると思います。
ただ今回はjson出力がみたいだけなのでこのままにしています。
もしやるのであればこんな感じになると思います。books_search.rbdef get_book_json(params) uri = URI.parse(GOOGLEAPI_URL + params) resources = Net::HTTP.get(uri) json = JSON.parse(resources) end4. 作成したgemをローカルにインストール
いよいよ自分のgemをインストールできます!!
bundle exec rake install
でローカルにインストールして確認してみます。$ bundle exec rake install books_search 0.1.0 built to pkg/books_search-0.1.0.gem. books_search (0.1.0) installed.rakeでビルドしてそのまま
gem install
してます。完了したらirbで確認します。
$ bundle exec irb irb(main):001:0> require 'books_search' => true irb(main):002:0> BooksSearch.get_book_json('ruby') { "kind": "books#volumes", "totalItems": 731, "items": [ { "kind": "books#volume", "id": "Dif2bl2KRUYC", "etag": "pmkr/T2kh1g", "selfLink": "https://www.googleapis.com/books/v1/volumes/Dif2bl2KRUYC", "volumeInfo": { "title": "プログラミングRuby 1.9 言語編", "authors": [ "Chad Fowler", "Andy Hunt" ], ~以下略~自分はこんな感じでできました!
完成したもの
もし実際のアップロードをするのであれば
$ rake releaseでできるみたいです。
詳しくはRubygemsにいろいろ書いていあります。(正直ここが一番わかりやすかった)GoogleBooksAPIででてきた出力ではimageのサムネイルだったりを出せるのでとても便利かなと思います。ただ、欠点として1日1000件までしか出せないようなので注意です。
感想
今回gemを初めて作ったのですが、意外と簡単に作れました!
また、rubyの仕組みや新しい知見もより増えたのでとてもよかったと思います。いろいろなことを自分で試してみて力をつけて行けたらなと思います!!
参考