20210103のRubyに関する記事は30件です。

モジュールと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

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

Railsのパンくずリストで2種類以上のトップページを実装する方法

こんばんは
アロハな男、やすのりです!

今日はポートフォリオで使用しているパンくずリスト用Gemgretelを導入時に少し躓いた、『トップページを2種類以上実装する方法』を説明していきたいと思います!

もし僕と同じことを実装したい方がいらっしゃったら参考にしてください!!

結論

2種類以上のトップページを実装する場合は、

config/breadcrumbs.rb
crumb :root do
  link "Home", root_path
end

という初期設定を

config/breadcrumbs.rb
crumb :top_page do
  link "トップページ", root_path
end

crumb :mypage_top do |user|
  link "マイページトップ", user_path(user.id)
end

という様な記述に修正し、rootという設定を無くす!!

なぜトップページが2種類以上必要?

まずそもそもなぜ通常だと1つでいいトップページが2種類以上必要なのかについてですが、これは私のポートフォリオの作り方で『その方が見栄えが良さそうだったから』です。

...ここに関しては理論立てた説明はありません

例えば、ポートフォリオでの検索結果ページ詳細ページに遷移した際はもちろんトップページからの遷移歴が表示されます。
Image from Gyazo

しかし、マイページ内のユーザー情報編集ページに遷移した場合の遷移歴は、
Image from Gyazo
この様に、トップページへのリンクはなく、マイページトップへのリンクしかありません。

これがもし遷移歴にトップページも表示させる様にすると、
Image from Gyazo
となってしまい、少し表示が多く見づらくなってしまうなと思いました。
※トップページへのリンクはヘッダー部分に設置してあるため、マイページから出られなくなる等といったことはありません。

トップページを2種類用意するには?

gretelを使用する際に基本は遷移先のリンクと表示用文字列だけを記述し、親ページを設定したい時は別途設定を記述します。

config/breadcrumbs.rb
crumb :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.rb
crumb :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.rb
crumb :root do
  link "トップページ", root_path
end

crumb :search_word do |search_word|
  link "'#{search_word}' の検索結果", search_path
end

元々は検索結果ページに何も設定しなくてもrootに紐づいていたので自動的にトップページを表示してくれていましたが、名前を変更(今回であればtop_page)に変更してしまっているので、紐付けが解除され親ページが存在しない状態になってしまっています。

なので、

config/breadcrumbs.rb
crumb :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種類以上実装するということ自体そんなに頻繁にすることではないと思いますが、もし実装する際にこの記事が参考になれば幸いです!

この件に関するご指摘・アドバイスはどんどんいただきたいと思っていますので、コメントお待ちしています!!

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

RSpecにてmodelのテストを書く

modelテストの準備

今回はPostモデルにタイトルカラムがあるかどうかと文字数のテストをする。

app/models/post.rb
class 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.rb
RSpec.configure do |config|
  config.include FactroyBot::Syntax::Methods
end

③spec/rails_helper.rbの編集

spec/rails_helper.rb
require '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.rb
FactoryBot.define do
  factory :user do
    name {Faker::Name.name}
    email {Faker::Internet.email}
    password {'password'}
    password_confirmation {'password'}
  end
end
spec/factories/post.rb
FactoryBot.define do 
  factory :post do
    body {Faker::Lorem.characters(number: 20)}
    user
  end
end

テストコードの作成

spec/models/post_spec.rb
require '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
end

RSpecの実行

$ bin/rspec spec/models/post_spec.rb
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

個人アプリ開発日記 #3

目次

1.はじめに
2.内容
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. おわりに

アプリ制作は初めてのため初心者ですが、中身の細かい実装をしながらでも、
視野が狭くなりすぎないように適度にアプリ全体を俯瞰することを意識しています!

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

Microposts機能(rails)

この記事では、Micropostsにおける投稿機能について、簡単に理解したのでその確認として理解を深めることを目的としている。

投稿機能は以下の手順デコーディングを行う

  1. Micropostモデル、contentカラム、user_idカラム(外部キー)を作る
  2. UserテーブルとMicropostテーブルを関連付ける
  3. UsersコントローラーのshowアクションにMicropostの中身を表示させるための「@microposts」を記載する
  4. Usersフォルダーのviewのshow.html.erbにも@micropostsを記載する
  5. Micropostsコントローラーを作り、_micropost.html.erbにmicropostテーブルのcontentカラムの中身を表示させるファイルを作る
  6. 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
end

UserテーブルとMicropostテーブルを関連付けをする

models/micropost.rb
class Micropost < ApplicationRecord
***  belongs_to :user
  validates :user_id, presence: true
end
models/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
end

3. 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で取得する。
   end
views/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カラムの中身を表示させるファイルを作る

terminal
rails 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投稿内容と作成日を表示させる
microposts.JPG

▼Fakerでダミーデータを作成する

db/seeds.rb
  users = 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

=> データベースの中身を変更したのでリセットする

ダミー.JPG

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コントローラーで定義する

user_info.JPG

micropostsコントローラーのcreate,destroyアクションにアクセスできるのはログインを正しくしたユーザだけに制限する

controllers/microposts_controller.rb
 before_action :only_loggedin_users, only: [:create, :destroy]

 def create
 end

 def destroy
 end

=>before_actionをすることによりURLから直接アクセスされることを防ぐ(ログインしていなければログインページにとぶ)

micropostsコントローラーの中身を記載していく

controllers/microposts_controller.rb
 before_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_controller
 class 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_controller
 def 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>

feed.JPG
?
feed_item.JPG

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#destroy
 def destroy
     Micropost.find(params[:id]).destroy
     redirect_to root_url
 end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】購入機能実装〜ログイン中・販売済商品に対しての分岐〜

解決したいこと

①出品者が自分の出品商品を購入できてしまう
②出品者が完売商品に対して商品の編集・削除ができてしまう
③完売商品に対してもう一度購入ができてしまう

if文の分岐すごく苦手なんです。
どこをどう考えたらいいのかわからないので、まずは紙に書き出してみる。

qiita.png

出品者

自分の出品物の販売中商品だけ編集&削除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なので完売商品ではなければ(偽)購入ボタンを表示させる。



うまいこといきました。
自己解決できるとまた一つ階段を昇れた気がします。
※違っている点、もっと簡単に書けるよというものがあればご指摘頂ければ幸いです。

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

【Ruby】購入機能実装〜ログイン中・完売商品に対しての分岐〜

解決したいこと

①出品者が自分の出品商品を購入できてしまう
②出品者が完売商品に対して商品の編集・削除ができてしまう
③完売商品に対してもう一度購入ができてしまう

if文の分岐すごく苦手なんです。
どこをどう考えたらいいのかわからないので、まずは紙に書き出してみる。

qiita.png

出品者

自分の出品物の販売中商品だけ編集&削除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行ほど短く、コンパクトにすることができました。
奥が深い。

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

【Ruby】FizzBuzz問題

概要

Rubyの勉強として FizzBuzz問題whileeach を使用して解きました。解き方は他にもありますが、今回は個人的に使用頻度の低い 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の倍数 をはじめに追加しないと評価されない

参考文献

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

文字を数える

任意の文字列の中に指定した文字がいくつあるか数えて、その数を出力するメソッドを作ります。

"go"という文字がいくつあるかで作成してみます。

scanメソッドが使えそうです。scanメソッドは対象の要素から指定した文字列を数えて、配列として返すメソッドです。
参考:
Ruby 3.0.0 リファレンスマニュアル, scan

def count_go(str)
  puts str.scan("go")
end

これで"go"を配列として取得できました。最後にその数を取得するための記述をおないます。

def count_go(str)
  puts str.scan("go").length
end

これで、数で出力することができました。

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

RSpecの準備 

この投稿は、自己学習の備忘録です。

railstutorialを一通り学習し、テストをminitestからRSpecへ書き換えようと思いたったことがこの記事を執筆しようとしたキッカケです。

私自身にとって初めての投稿ですが、もしこの投稿がご覧頂いた方のヒントになっていれば幸いです。

rails new時のオプション

$ rails new --skip-test --skip-bunlde

デフォルのminitestとbundle install をスキップしておく。

gemの追加

Gemfile
group :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.rb

Springを使用したRSpecの導入

springを使用することでRSpecの実行時間を短縮することができる。
そのために先ほど追加したgem 'spring-commands-rspec'を使用する。
RSpecのstubファイル(./bin/rspec)を作成する。

$ bundel exec spring binstub rspec

bin/rspecファイルが作成され、RSpecを実行するとspringが使えるようになる。

*'spring-commands-rspec':bin/rspecのコマンドを実行するときに必要となる。RSpecはbin/コマンドをつけることでSpringと言うRailsに組み込まれているアプリを起動させて処理を高速化できる。RSpec自体はrspecと入力するだけで使用可能。

config/environments/test.rbの編集

config/environments/test.rb
config.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年!

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

また複雑な条件分岐

簡単な取り調べゲームをしたいと思います。
容疑者は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

これでゲームが作成できました。

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

任意の素数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 であれば良い。

素数以外をつっこみたいとき

上で記述した秘密鍵の定義上、pq が互いに素であって、かつ lcm(p-1, q-1)e が互いに素であれば、例えば「合成数をベースにしてしまった RSA 秘密鍵」も割と問題なく作れたりする。

擬素数をつっこんで RSA がそのときどのように動作するのか調べたいときにも使える。(自分はむしろこっちがやりたくてこのスクリプトを書いていた)

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

[Rails]deviseのカスタマイズ。ユーザー登録、編集後のリダイレクトページを変える方法と、編集時のパスワード入力をスキップする方法

駆け出しエンジニアの備忘録です。初投稿です。
またいつか「あれどうやるんだっけ?」となった時に再度調べるのも効率が悪いので、ここに残していこうと思います。
間違いなどがあればご報告をお願い致します。

デフォルトはrootパス

僕の環境だとrootはトップページにリダイレクトしてしまうので、どちらもユーザーの詳細画面に遷移するようにしたい。
deviseの導入などの準備は割愛いたします。

1.コントローラーの作成

$ rails g devise:controllers users

いくつか作成されたコントローラーの中で今回使うのはregistrations_controller.rb

ここに追記する

controllers/users/registrations_controller.rb
class 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.rb
devise_for :users, controllers: { registrations: 'users/registrations' }

これにより作成したコントローラが使えるようになりました。
ちゃんと指定したパスにリダイレクトされるはずです。

編集時のパスワードの入力をスキップする設定

デフォルトだと更新しようとしても設定中のパスワードを入力するように要求されてしまう。
パスワードの入力無しで更新できるようにします。
先程と同様に、コントローラーに以下を追記します。

controllers/users/registrations_controller.rb
  def 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

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

任意の文字列を削除する

任意の文字列に対して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

元の文字列が変化して出力されるようになりました。

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

【CircleCI】ECS自動デプロイ - CircleCI編 -

参考文献

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.yml
version: 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\\\"]}]}"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby/GTK3でGUIのJSON Viewerを適当に作る

お正月の気まぐれに小ネタを作ったので記録を残します

image.png

Ruby/GTK3を使ってGUIのJSONビュワーを作ってみました。
Ruby/GTK3 の使い方は、ruby-gnomeのsampleディレクトリの中をみれば大体わかります。

今回は以下のサンプルを参考にしました。

ポイントは1点で、まずTreeStoreでモデルを作って、次にGtk::TreeViewを作成します。
あとはテンプレ通りに下のYardのコードの使い回しです。なんとなく透けてたりするのは、Gladeの設定でやっています。

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

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やメモリ管理など考えないので、かなり注意していないと、この罠にはまります。

この記事は以上です。

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

Ruby ディレクトリ内のファイルロックを試す

フルコードは以下。
ディレクトリ内ファイルロックを試すRubyプログラム - sun610.web.fc2.com

ポイントは

  • File::LOCK_EX|File::LOCK_NB を使うこと (参考)
  • Dir.globのアスタリスクの仕様
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Sidekiq-limit_fetchが仕事しない

ruby on railsの開発において並列処理の実行数を管理したかったが、うまくいかなかったのでその原因のお話

Sidekiq-limit_fetchとは

Sidekiq-limit_fetchはsidekiqでの並列処理の実行数を管理するためのgemです。

https://github.com/brainopia/sidekiq-limit_fetch

Gemfile
gem '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の設定関数のオーバーライドでした。

Gemfile
gem 'serverspec'

上記のgemが原因でSidekiq-limit_fetchに記載されたset関数が正常に処理されませんでした。
serverspecはサーバーのテストを自動化するためのgemです。
そのため、serverspecをGemfileから削除することでsidekiq-limit_fetchは仕事をしてくれるようになりました。

しかし、並列実行数が稀に超過する現象は引き続き発生したため、自身でジョブの実行数を管理するコードを書く方が安全だということがわかりました。

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

【Docker】ECS自動デプロイ - Docker編 -

参考文献

ツリー図

docker/dev
├── app
│   ├── Dockerfile
│   ├── nginx
│   │   ├── app.conf
│   │   └── nginx.conf
│   └── supervisor
│       ├── app.conf
│       └── supervisord.conf
├── db
│   ├── data
│   └── mysql_init

Dockerコマンド

◆ 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.conf

Dockerfile

Dockerfile
FROM 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.yml
version: '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: XXXXXX

Nginx

nginx.conf
user  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.rb
threads_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", true

supervisor

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/*.conf
app.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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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」の一覧を管理するファイル。

Gemfile
source '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]

Railsチュートリアルより引用

一番上の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://qiita.com/nishina555/items/1b343d368c5ecec6aecf#:~:text=Gemfile%E3%81%A8%E3%81%AF,%E7%AE%A1%E7%90%86%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%81%AE%E3%81%8B%EF%BC%9F%E3%80%8F&text=%E3%81%A7%E3%81%99%E3%81%AE%E3%81%A7%E3%80%81Rails%E3%82%A2%E3%83%97%E3%83%AA%E3%81%A7,%E3%81%84%E3%81%8F%E3%81%93%E3%81%A8%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82

https://nishinatoshiharu.com/fundamental-gem-knowledge/

https://wa3.i-3-i.info/word1473.html

https://pikawaka.com/rails/bundler

http://xxxcaqui.hatenablog.com/entry/2013/02/11/013421

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

緯度経緯をURLへ変換するサービス、マップクリエイター

マップクリエイターとは、どんなWebサービスですか?
Googleマップの任意の地点をWebページ化するサービスです。場所をWebページ化することで空間にテキストが紐付くので、検索サイトから検索した時スムーズにその場所がどこにあるのかが、日本国民全員に分かるようになります。

マップクリエイターを作ろうと思ったきっかけは何だったのでしょうか?
旅行に行った時よくバスをよく利用するのですが、田舎のほうになるとバス停がどこにあるのか分からなかったり、バス停が分かってもどのバスがどこへ向かうのかが分からないことが多々あったので、それを上手い具合に解決したいと思って作りました。

そこで、ITとかにあまり詳しくない人でも、特定の場所に対して簡単にテキストを埋め込めるサービスがあれば、Googleで検索した時にその場所の詳細がすぐ分かるだろうと思って作りました。

バス停だけにとどまらず、地元の名所やつっこんだ観光案内(雑学など)を書けば、地域復興にも繋がるかなと思います。

機能面でこだわった点などありますか?
Google マップでは場所によっては緯度経緯が表示されず、プラスコードというアルファベットだけの表示になることもあるので、プラスコードにも対応させました。また悪用防止のため、作成したページを削除する機能も付けました。

(マップクリエイターは2018年に作ったwebサービスです)

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

【教材】Tailwind CSS & Rails6でQiitaを模写してみた【バックエンド演習に利用可】

この教材はこんな人におすすめ

  • Tailwind CSSが流行っていると知っているけど学習が面倒くさい人
  • Tailwind CSSを使ったビューの完成コードが見たい人
  • フロント完成コードを元に機能実装(バックエンド)演習をしてみたい人
  • 駆け出しエンジニアなので現役エンジニアが書いているコードが見たい人

完成コードをどうぞ

https://github.com/Fumiya-Soeno/tailwind

  • git cloneして下さい
  • Rails6が必要になるので入れて下さい。エラー出たら解決してね
  • フロント志望の方はコード眺めるだけでも割と勉強になると思います

完成図

スクリーンショット 2021-01-03 14.09.52.png

Tailwind CSSチートシート

こちらも一緒に眺めましょう。分からないクラス名はここで検索すれば載ってます。
https://nerdcave.com/tailwind-cheat-sheet

Font 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連打して頂けたら筆者は泣いて喜びます。

筆者連絡先

https://twitter.com/soeno_onseo

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

【無料教材】Tailwind CSS & Rails6でQiitaを模写してみた【バックエンド演習に利用可】

こんな人におすすめ

  • Tailwind CSSが流行っていると知っているけど学習が面倒くさい人
  • Tailwind CSSを使ったビューの完成コードが見たい人
  • フロント完成コードを元に機能実装(バックエンド)演習をしてみたい人
  • 駆け出しエンジニアなので現役エンジニアが書いているコードが見たい人

完成コードをどうぞ

https://github.com/Fumiya-Soeno/tailwind

  • git cloneして下さい
  • Rails6が必要になるので入れて下さい。エラー出たら解決してね
  • フロント志望の方はコード眺めるだけでも割と勉強になると思います

完成図

スクリーンショット 2021-01-03 14.09.52.png

これを教材として使う方法(インフラ・バックエンド向け)

問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-sheet

Font Awesomeチートシート

fa fa-checkみたいなクラス名はTailwindCSSではなくFontAwesomeという別のCSSフレームワークです。アイコン表示に使ってます。
https://fontawesome.com/cheatsheet

注意事項

  • トラブル回避したいので、コードの再配布・自作発言はお控え下さい。

Railsを学びたい人向け

Mediumにてこの記事の筆者が学習ロードマップを無料公開中です

そもそもRailsが分からんという方は、上記を読んで頂ければ上に挙げた機能実装スキルは網羅できます。なお、Medium記事のclapボタンを50連打して頂けたら筆者は泣いて喜びます。

筆者連絡先

https://twitter.com/soeno_onseo

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

Serverless FrameworkとAWS Lambda with Rubyの環境にgemインストール

gemインストールが必要なAWS LambdaのRubyスクリプトをServerless Frameworkでデプロイする方法です。

手順概要

プラグインを入れれば簡単にできます。

  1. serverless plugin install -n serverless-ruby-layer
  2. Gemfile 作成
  3. あとは普通にデプロイすると勝手にいろいろやってくれる

手順詳細

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.json

Serverless FrameworkがNode.jsで実装されているので、Rubyのプロジェクトなのに node_modulespackage.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-layer

Gemfile

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

image.png

Layer

image.png

実行結果のCloudWatch Logs

image.png

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

【買付代行サービス個人開発 - No.012】ドロップダウンをJSで実装

Pull Request

概要

右上のドロップダウンメニューが常に表示されているので、ユーザーアイコンをクリック後、ドロップダウンメニューが表示されるようにする。

image

参考

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 turbolinks
app/javascript/src/js/dropdown.js
import 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.slim
button.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-1
bin/rails s
Terminal-2
bin/webpack-dev-server

受入基準

  • 画面右上のドロップダウンメニューが、ユーザーアイコンのクリック後、表示される
db48b1940ae84a018aa47c8312f8716c
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VirtualBox,Vagrant, cyberduck を用いたRuby on Rails の環境構築

はじめに

本格的にプログラミングを始めるために新しくMacbookproを買い環境構築を行ったので、次に行うときのための備忘録として記事を書かせていただきます。
初心者のため間違えているところなどある可能性があります(ご指摘いただけると非常にありがたいです)。その点ご留意の上お読みください。

全体の流れ

以下のような流れで行いました。

  1. VirtualBoxとVagrantで仮想環境構築
  2. 1で構築した仮想環境にrbenvをインストールし、rubyとrailsを導入
  3. 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.2

VirtualBox,および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/47c6cfbeeafad39eda07

rbenvを用いる方法は、Vagrantの導入で参考にした上記サイトの続きをそのまま用いました。これで、Railsを導入するところまでは問題なく行けました。しかし、仮想環境上における作業領域(僕の場合は home/vagrant/app/score_app)で以下のコマンド

home/vagrant/app/score_app
bin/rails s

を用いてもローカルサーバーへの接続が行えませんでした。これは以下の方法で解決しました。

仮想環境から出て、仮想マシンを設置しているフォルダ(僕の場合は、/ユーザー/ユーザー名/MyVagrant/centos7)で、以下のコマンド

/user/user_name/MyVagrant/centos7
vi Vagrantfile

を実行し、Vagrantfileを編集。ipアドレスの設定を行いました。内容は以下のサイトの行った解決方法①、②を参照。

https://qiita.com/Ago0727/items/325df5e39e3406fa16d2

①のみでは接続できなかったのですが、②で接続できました。ただし、①でも書かれていますがVagrantを用いる場合は接続の際にオプションとして以下を設定しないと接続できないようです。

user/user_name/MyVagrant/centos7
bin/rails s -b 0.0.0.0

cyberduckへの接続

ここも基本的に次のサイトを参考に行えました。

https://qiita.com/Lassieena/items/603fe89df26b59ca06f7

ただし、SSH private key の設定、送信方式をSFTPにすることに気を付けておけばいけます。最後に、cyberduckの環境設定タブ(macであれば画面上部に表示される)から外部エディタにインストールしたvscodeを指定します。
以上で完了です!

最後に

環境構築は様々な方法があり、適切な方法で行うのは初心者にはかなりハードルが高かったです(これで完全にできているのかも不安です)。今回僕がこの方法にたどりつくまでに2回ほど失敗して、どうしたら良いかわからず仮想環境ごと廃棄するのを2回行ったので、もしどうしようもなくなったらもう一度仮想環境から作り直すのもありかもしれません。また、一通りしてみて感じたことは、コマンドの詳細などは理解できないところも多いとは思いますが自分がどこのフォルダにいて、どこに各パッケージをインストールすべきか(しているのか)を把握して構築することが大切だと感じました。仮想環境の廃棄に関してはvagrant コマンド などで検索すれば出てくるので、もし使うときは調べてみるといいかもです。

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

「え!?いつのまに新刊出てたの!?」を解消するアプリを作りました【ポートフォリオ解説】

はじめに

実務未経験者がどうやって・どんなポートフォリオを制作したのか
ポートフォリオの解説→意識したこと・苦労したことの順で解説します。

よければ読んでみてください

コンセプト

「え!?いつのまに新刊出てたの!?」を解消する

好きなマンガや小説など・好きな作家を登録しておくと新刊の発売3日前にメールでお知らせします
ちなみにアプリ名はブクシルです。「ブック」+「(新刊の発売を)知る」ですね。
我ながら気に入ってます。寒くないです。だれですか寒いって言ったの。

使用イメージ

書籍の検索ができます(楽天API使用)

search

お気に入り登録ができます(作品のシリーズと作者名)

favorite

発売3日前にメールでお知らせします

output_0000.png

開発背景

せっかくポートフォリオをつくるんだったら、だれかの役に立つものが作りたいと思い、以下の流れで作り始めました

  1. 周囲の知人・友人に「どんなサービスがあったら便利か」「日常生活で不便に感じていることはないか」とヒアリング
  2. 「気づいたら新刊が発売されていることがあるから事前に知りたい」との意見を聞く
  3. 自分自身、読書が趣味で同じ悩みを持っていたこともあり、それを解決できるアプリを作りたいとの想いで製作開始

使用技術

フロントエンド
HTML/CSS(Sass)
Bootstrap 4.5.2
jQuery 3.5.1

バックエンド
Ruby 2.6.6
Ruby on Rails 6.0.3.3

API
楽天書籍ブックスAPI、楽天ジャンル検索API

DBMS
PostgreSQL 13.1

インフラ
AWS(VPC・EC2・RDS・S3・IAM・Route53・ACM)
独自ドメイン
HTTPS化

DB設計

一番苦労したのがDB設計です。
開発前に軽くER図を作ってからスタートしましたが、当初の構想ではうまくリレーションできず、何回も作り直すことになりました。
最終的に下図の通りになりました。
booksiru_ER_diagram

開発で意識したこと

1. エラー解決方法

実務に入ったときに先輩社員や上司の時間を無駄に奪うことにならないように
まずは自分の力で解決することを強く意識しました。
具体的には、前後することもありますがおおよそ次の順番です。

  1. エラーメッセージやログをよく読む
  2. どこの段階でエラーが出ているか推測する
  3. bindnig.pryでデバッグする
  4. エラー内容を調べる。(なるべく一次情報から読み解くことを意識。)
  5. どうしても解決できなければメンターの方に質問する

2. 質問の仕方

Slackを使ってだいたい以下の構成で質問していました。
なるべく少ないやり取りで済み、相手の人の時間を無駄に奪わない内容にすることを意識しました。

  1. やりたいこと・困っていることを端的に表現
  2. 自分なりに試したこと (書いたコード・コマンド)
  3. 試した結果 (エラーメッセージ)
  4. わからないなりに怪しいと思っている部分を述べる
    ・・・違っている場合にメンターの方から意見を頂ける
    →理解が深まり、以降の質問を減らせる
  5. GitHubのソースコードを記載・・・思わぬところにエラーの原因があることも多いため

実務において1人でずっと悩み続けることはかえって損失になってしまうと思うので
上記のステップである程度独力でやって解決できなかった場合には、端的かつ過不足ない内容で質問することで時間的な損失を最小限にすることを心がけていきたいと思います。

3. 模擬チーム開発

実務を想定して模擬チーム開発を意識しました。
・ Git, GitHubを用いたソース管理
・ 必要要件をIssuesに列挙し、タスク消化
・ featureやfixのブランチを切ってプルリクベースで開発

ただ、Issuesはプルリクと結びつけられると後で知ったので、使い方が甘かったなと反省しています。

開発で特に苦労したところ

DB設計
上でも述べましたが一番苦労しました。
リレーションについてわかったつもりでも、いざ自分のアプリに落とし込むとなるとうまくいかず、何度も紙に書いては関係性を整理しました。

楽天書籍ブックスAPIの適用
公式ドキュメントだけではどう扱うのかのイメージがつかず、調べた限りでは使用例も少なかったです。
少ない情報の中からどう自分のアプリに落とし込むのか、使用例からどこが一般化できるのか、実際にコードを書きながら試行錯誤を繰り返しました。

感想

エラー解決がめちゃくちゃ楽しい。
エラーを乗り越えたときに両手を突き上げるほど興奮したことが何回かあります。
ゲームで強いボスを倒したときみたいな感じですね。
負けては次の手を考え実行する、を繰り返して最終的に勝つみたいな。

プログラミング学習をはじめたころはエラーが怖かったと思うのですが、成長したのかなあと。
あとは仮設と検証を繰り返す作業が現職の研究業務と似ているなあと思ったりしていました。

課題/今後実装したいこと

やりたいことはまだ残っているので、ひとつずつ良くしていきたいと思います。

・レスポンシブWebデザイン(スマホ対応)
・お気に入り登録/解除時にフラッシュ表示
・お気に入り登録/解除時の非同期処理
・LINEログイン
・LINEのMessaging APIを使用してLINEで通知

さいごに

長い文章をここまで読んでくださってありがとうございました
良かったと思った方はLGTMして頂けると嬉しいです!
どなたかの参考になれば幸いです!

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

【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.yml
AllCops:
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

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

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を書いていく。
今回必要なのはbundlerrakeが必要なので追加する。(テストまで書きたいのであればrspecやminitestを追加する。)

books_search.gemspec
  spec.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.rb
require '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
end

getリクエストが欲しいので調べたらnet/httpというライブラリがあったのでそれを使用しました。

処理内容としては

  • GOOGLEAPI_URL + paramsで出来た文字列をパースしてuriに変換。
  • それをgetリクエストして帰ってきたjsonを出力

これだけです。

もし、ちゃんと利用するのであればこのjsonをrubyオブジェクトに変換していろいろ活用できると思います。
ただ今回はjson出力がみたいだけなのでこのままにしています。
もしやるのであればこんな感じになると思います。

books_search.rb
def get_book_json(params)
      uri = URI.parse(GOOGLEAPI_URL + params)
      resources = Net::HTTP.get(uri)
      json = JSON.parse(resources)
    end

4. 作成した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の仕組みや新しい知見もより増えたのでとてもよかったと思います。

いろいろなことを自分で試してみて力をつけて行けたらなと思います!!

参考

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