20200624のRubyに関する記事は18件です。

jqueryでsubmitボタンを毎回有効にする方法

テックキャンプでJavaScriptの学習中に学んだ事

jQueryを使って非同期通信の学習をしている最後に、送信ボタンを押してイベント発火後に
送信ボタンが無効化されているのを有効にする方法を記します。

$('#hoge').prop('disabled', false);

調べてみてわかった事は

  • Railsのver5.0以降はdisabledがデフォルトで設定されている事(連打防止等の為)
  • 他にも有効に出来る方法はある

例えば

$('#hoge').attr('disabled', false);

自分のコードで試したが、どちらでも有効でした

今度は連打防止などの方法も調べて使え様にしていきます

非同期通信が自由に扱える様になると、少ないビューファイルの中に沢山の動的要素を
取り入れる事が出来て、かつレスポンスも早そうなので、プログラミングを学びたての自分でも
魅力的だなって感じました。

難しいけど面白い

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

Array#sample と Random#rand ってどっちが速いんかな

バッチ処理で数十万回ループしてその中で毎回ランダムな数を選びたい場合、Array#sampleRandom#rand でどっちがパフォーマンスがいいのか気になったので調べてみました。

さっそく以下計測

range = (1..10000)
array = range.to_a
num = 1000000

Benchmark.bm 10 do |r|
  r.report 'Array#sample' do
    num.times do
      array.sample
    end
  end

  r.report 'Random#rand' do
    num.times do
      rand(range)
    end
  end
end

結果は

                 user     system      total        real
Array#sample  0.114216   0.002376   0.116592 (  0.120891)
Random#rand  0.198875   0.001285   0.200160 (  0.206403)

というわけで Array#sample がわずかに速かったです。ただ今回 range を渡しているのでもしかしたら内部的に to_a とかしているせいかもしれない…

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

Rails 基礎的なCRUD機能実装手順 scaffold

前置き

Ruby on Railsを使って、基礎的なCRUD機能を実装する手順をまとめる。
CRUDとはCreate、Read、Update、Destroyの頭文字を取った用語。

アプリ立ち上げ

ターミナルで、railsコマンドを使って、アプリを作成する。

rails new blog_app -d postgresql

このコマンドだと、新しくblog_appを作成している。
使い慣れているので、データベースはpostgreSQLにしている。何も指定しない場合、sqlite3がデータベースになる。その後、アプリのルートディレクトリに移動(cd blog_app)し、ターミナルで

rails db:create

を入力し、データベースを立ち上げる。
サーバーを立ち上げて、アプリが出来ていることをローカルで確認する。

rails s

rails.PNG

上の画面が出ればOK。ctrl+Cでサーバーの立ち上げを終了できる。

CRUD機能

アプリが立ち上げられたので、ブログを投稿(create)、読み取り(read)、更新(update)、削除(delete)できる機能を実装する。
実は、railsジェネレーターのscaffoldを使うとコマンド2つで完了する。

rails g scaffold blog title:string content:text
rails db:migrate

最初のコマンドで、Blogモデル、ビュー、コントローラーとルーターの作成が一気にできる。この場合、Blogモデルに、string(文字列)型のインスタンス変数titleと、text(文章)型のインスタンス変数contentを作成している。
2つ目のコマンドは、データベースにblogsテーブルを作成するために必要。

後書き

今度は、scaffoldを使わずにモデル、ビュー、コントローラーとルーターのコードについてまとめようと思います。

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

【Rails】CSVインポート機能の実装

目標

ezgif.com-video-to-gif.gif

開発環境

・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina

前提

下記実装済み。

Slim導入
Bootstrap3導入
投稿機能実装

実装

1.Gemを導入

Gemfile
# 追記
gem 'roo'
ターミナル
$ bundle

2.application.rbを編集

application.rb
require_relative 'boot'

require 'rails/all'
require 'csv' # 追記

Bundler.require(*Rails.groups)

module Bookers2Debug
  class Application < Rails::Application
    config.load_defaults 5.2
  end
end

3.モデル編集

book.rb
def self.import(file)
  CSV.foreach(file.path, headers: true) do |row|
    book = find_by(id: row["id"]) || new
    book.attributes = row.to_hash.slice(*updatable_attributes)
    book.save!(validate: false)
  end
end

def self.updatable_attributes
  ['id', 'title', 'body']
end

① インポートするデータに同じIDが見つかればそのレコードを呼び出し、見つかれなければ新しく作成する。

book = find_by(id: row["id"]) || new

② CSVファイルからデータを取得する。

book.attributes = row.to_hash.slice(*updatable_attributes)

③ バリデーションを通さずに保存する。

book.save!(validate: false)

④ CSVインポート時に受信するカラムを設定する。

def self.updatable_attributes
  ['id', 'title', 'body']
end

4.コントローラーを編集

books_controller.rb
def import
  Book.import(params[:file])
  redirect_to books_path
end

5.ルーティングを追加

routes.rb
resources :books do
  collection { post :import }
end

6.ビューを編集

books/index.html.slim
= form_tag import_books_path, multipart: true do
  = file_field_tag :file
  br
  = submit_tag "インポート", class: 'btn btn-success'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

uriライブラリ 簡易まとめ

URI.scheme

スキームを文字列で返す。

URI.host

ホストを文字列で返す。

URI.port

ポート番号を文字列で返す。

URI.query

クエリを文字列で返す。

URI.path

パスを文字列で返す。

URI.request_uri

path + '?' + queryを文字列で返す。 query が nil である場合は、自身の path を返す。

実践

Qiitaでrubyを検索した結果画面のurlをURIオブジェクトにパースしてみましょう。

require 'uri'

uri = URI.parse('https://qiita.com/search?q=ruby')
puts uri.schem
#=> https
puts uri.host
#=> qiita.com
puts uri.port
#=> 443
puts uri.path
#=> /search
puts uri.query
#=> q=ruby
puts uri.request_uri
#=> /search?q=ruby

join

引数に渡した文字列(変数)を連結してURIオブジェクトを生成する。

encode_www_form

引数で渡した値からURL-encoded form dataを生成する。
私値は配列やハッシュも可能。
GETでuriにパラメータを渡す時などに利用。

uri = URI.join('https://qiita.com','/search')
puts uri
# => https://qiita.com/search
params = {
name: 'hogehoge',
email: 'hogehoge@hogehoge.com'
}
uri.query = URI.encode_www_form(params)
puts uri
# => https://qiita.com/search?name=hogehoge&email=hogehoge%40hogehoge.com
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CentOS 7にRuby 2.7をインストール(SCL)

はじめに

Software Collection(SCL)を利用してCentOS7にRuby 2.7をインストール
参考:Quick Start — Software Collections

サポート

本手法で導入した場合、Red Hat Software Collections Product Life Cycle - Red Hat Customer Portalより、2023-05がEOLだと思われる。
それ以降に報告された脆弱性や不具合への対応は実施されない可能性がある。

LOG

レポジトリ登録

# yum install -y centos-release-scl

インストール

# cat /etc/redhat-release
CentOS Linux release 7.8.2003 (Core)

# yum install -y rh-ruby27 which
# scl enable rh-ruby27 bash
... 略

各種確認

# which ruby
/opt/rh/rh-ruby27/root/usr/bin/ruby

# ruby -v
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]

# yum info rh-ruby27
Loaded plugins: fastestmirror, ovl
Loading mirror speeds from cached hostfile
 * base: ty1.mirror.newmediaexpress.com
 * centos-sclo-rh: ty1.mirror.newmediaexpress.com
 * centos-sclo-sclo: ty1.mirror.newmediaexpress.com
 * extras: ty1.mirror.newmediaexpress.com
 * updates: ty1.mirror.newmediaexpress.com
Installed Packages
Name        : rh-ruby27
Arch        : x86_64
Version     : 2.7
Release     : 2.el7
Size        : 0.0
Repo        : installed
From repo   : centos-sclo-rh
Summary     : Package that installs rh-ruby27
License     : GPLv2+
Description : This is the main package for rh-ruby27 Software Collection.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【日本語化】 i18n rails 楽々日本語化 viewの表示のみ

【ゴール】

viewだけ日本語表示へと切り替え

【メリット】

■ UIの向上
■ 日本語なので可読性向上し、開発効率化

【開発環境】

■ Mac OS catalina
■ Ruby on Rails (5.2.4.2)
■ Virtual Box:6.1
■ Vagrant: 2.2.7

【実装】

アプリケーション作成

※ touchでfileを作成

mac.terminal
$ rails new japoanese
$ rails g controller homes index
$ cd config/locale
$ touch ja.yml

※下記追記後 bundle install

gemfile.追加
gem 'rails-i18n', '~> 5.1' 

※ホーム画面へhomes/indexを!!

routes.rb
root 'homes#index'

※インデント要注意、

locale/ja.yml
ja:
 homes:
    index:
      title: '題名'
      name: ’名前’
      text: ’文章’

## modelが関連すると書き方少し変化します。下記みたいな感じです。。。

  activerecord:
    models:
      user: "ユーザー"
      attributes:
        user:
        name: "名前"
        age: "年齢"

下記重要

config/application.rb
config.i18n.default_locale = :ja #追加

※ja.ymlで定義」したものを引っ張ってきます。

homes/index.htmnl.erb
<h2><%= t '.title'%></h2>
<h2><%= t '.name'%></h2>
<h2><%= t '.text'%></h2>

以上!!

【合わせて読みたい】

■もっと詳しい記事
https://qiita.com/shimadama/items/7e5c3d75c9a9f51abdd5

■エラーメッセージ
https://qiita.com/tanaka-yu3/items/63b189d3f15653cae263

■rails メソッド 初学者者向け
https://qiita.com/tanaka-yu3/items/89abad875187494bec53

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

下位10%のダメなエンジニアにだけ解けないパズルにチャレンジしてみた

タイトルにあるようなパズルが数年前流行ったとのこと、
見つけたのでチャレンジ。

パズル

まずはブラウザに直接打ち込んでみる。
http://challenge-your-limits.herokuapp.com/call/me
すると、下記のようなテキストが帰ってきた。

{"message":"Almost! It's not GET. Keep trying."}

なるほどGETではないとのことなので、
POSTでリクエストしてみよう。

curlでやってもいいが、今回はRubyでリクエストを投げてみる。

require 'net/http'
uri = URI.parse('http://challenge-your-limits.herokuapp.com/call/me')
http = Net::HTTP.new(uri.host, uri.port)
req = Net::HTTP::Post.new(uri.path)
response = http.request(req)
puts response.body
#=>{"message":"Great! Please register as /challenge_users"}

成功、そして次の課題が、
まずはパスを変えてそのままリクエスト。

require 'net/http'
uri = URI.parse('http://challenge-your-limits.herokuapp.com/challenge_users')
http = Net::HTTP.new(uri.host, uri.port)
req = Net::HTTP::Post.new(uri.path)
response = http.request(req)
puts response.body
#=>{"message":"Validation Error, [:name, \"can't be blank\"]"}

パラメータを渡せって指示がきました。
POSTなのでリクエストボディにデータを入れ込む。

require 'net/http'
require 'json'
params = {
   name: 'hogehoge'
}
uri = URI.parse('http://challenge-your-limits.herokuapp.com/challenge_users')
http = Net::HTTP.new(uri.host, uri.port)
req = Net::HTTP::Post.new(uri.path)
req.body = params.to_json
response = http.request(req)
puts response.body
#=> {"message":"Validation Error, [:name, \"can't be blank\"]"}

あれ?パラメータが渡せてない、
ここから熟考、、、そしてカンニング。笑

リクエストボディにjson形式でパラメータを入れているのが原因だった。
ボディはjsonでしょと思い込んでいたことを反省、、、

Net::HTTP::Postライブラリにはset_form_dataという便利なメソッドがあり、
引数にhashを渡せば文字列でパラメータをボディに入れてくれます。

require 'net/http'
params = {
   name: 'hogehoge'
}
# 省略
req.set_form_data(params)
puts req.body
#=> name=hogehoge
puts req.body.class
#=>  String

そして編集

require 'net/http'
params = {
   name: 'hogehoge'
}
uri = URI.parse('http://challenge-your-limits.herokuapp.com/challenge_users')
http = Net::HTTP.new(uri.host, uri.port)
req = Net::HTTP::Post.new(uri.path)
req.set_form_data(params)
response = http.request(req)
puts response.body
#=> {"message":"Validation Error, [:email, \"can't be blank\"]"}

パラメータ指定が永遠に続くとかないよね?
指示通りemailを追加

require 'net/http'
params = {
   name: 'hogehoge',
   email: 'hogehogehoge@hogehoge.com'
}
uri = URI.parse('http://challenge-your-limits.herokuapp.com/challenge_users')
http = Net::HTTP.new(uri.host, uri.port)
req = Net::HTTP::Post.new(uri.path)
req.set_form_data(params)
response = http.request(req)
puts response.body
#=> {"message":"Validation Error, [:email, \"is already taken\"]"}

uniq: trueかよとか思いつつ、
別のアドレスを入れると成功しました。

{"message":"Thanks! Please access to http://challenge-your-limits.herokuapp.com/challenge_users/token/**********  from your web browser."}

まだまだ下位10%だなぁと、自分の立ち位置を再確認できたよき機会でした。
POSTに関してリクエストパラメータの入れ込み知識がついたので、1歩前進かな。

日々精進。

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

deviseの使い方 導入から設定変更

はじめに

勉強のためにdeviseを使い、ログイン周りを作成しました。
デフォルトではユーザー名、パスワードで認証しますが、今回社員番号、パスワードで認証するよう設定を変更していきます。

環境

Ruby 2.5.3
Ruby on Rails 5.2.4
Devise 4.7.1

完成

スクリーンショット 2020-06-24 16.00.39.png
スクリーンショット 2020-06-24 16.00.56.png
bootstrapで見た目を整えていますが、このようになるように設定を変更していきます。

実装

Gemfile
gem 'devise'
$ bundle install

●deviseの設定

$ rails generate devise:install

こんな感じのメッセージがでます。
初心者なのでエラーメッセージかとびっくりしましたが、これが出れば成功です。

create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
===============================================================================

Some setup you must do manually if you haven't yet:

  1. Ensure you have defined default url options in your environments files. Here
    is an example of default_url_options appropriate for a development environment
    in config/environments/development.rb:

    config.action_mailer.default_url_options = { :host => 'localhost:3000' }

    In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
    For example:

    root :to => "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
    For example:

    <p class="notice"><%= notice %></p>
    <p class="alert"><%= alert %></p>

  4. You can copy Devise views (for customization) to your app by running:

    rails g devise:views

===============================================================================

●メッセージの内容
1.新規登録など認証メールを送った際に、メールの文中にある承認リンクURLを設定します。

config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

2.ルート設定
会員登録後などにルートに飛ぶ設定になっています。

3.フラッシュメッセージを埋め込みます。ログイン、ログアウトの際にフラッシュを表示させたい時に使用します。私は共通ビューに埋め込みました。

app/views/layouts/application.html.erb
<body>
 <p class="notice"><%= notice %></p>
 <p class="alert"><%= alert %></p>
</body>

4.ビューのカスタマイズをするために

$ rails g devise:views

●deviseの設定変更

config/initializers/devise.rb
43行目あたり
認証キーは社員番号を指定
- config.authentication_keys = [:email]
+ config.authentication_keys = [:employee_number]
55行目あたり
認証キーの値は大文字小文字を区別しない
- config.case_insensitive_keys = [:email]
+ config.case_insensitive_keys = [:employee_number]
60行目あたり
空白キーを取り除く
- config.strip_whitespace_keys = [:email]
+ config.strip_whitespace_keys = [:employee_number]

●ユーザーモデルを作る

$ rails g devise user
app/models/user.rb
# 以下3つのメソッドは、user名を認証キーとするので、
# 不必要なメソッドをオーバーライドして無効化しています。

def email_required?
    false
  end

  def email_changed?
    false
  end

  def will_save_change_to_email?
    false
  end

db/migrate/日付_devise_create_user.rb
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :employee_number,    null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.string   :current_sign_in_ip
      # t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at
    t.timestamps null: false
    end

     ここを変更!!
     - add_index :users, :email,      unique: true
     + add_index :users, :employee_number,      unique: true
      add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end
$ rials db:migrate

●コントローラーの変更

application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  # methodをオーバーライドする。
  def configure_permitted_parameters
    sign_up_params = [:employee_number, :password, :password_confirmation]
    sign_in_params = [:employee_number, :password, :remember_me] 

      # account_update, sign_in, sign_up, のフィールドを再定義
    devise_parameter_sanitizer.permit(:sign_up, keys: sign_up_params)
    devise_parameter_sanitizer.permit(:sign_in, keys: sign_in_params)
    devise_parameter_sanitizer.permit(:account_update, keys: account_update)
  end
 end

設定はこれで終わりです。あとはviewを変更すれば完成です。

参考

[Rails] deviseの使い方(rails5版)
Ruby on Rails 初心者が gem Devise 使ってみた。

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

RSpec でメソッド呼び出しの引数をブロックでチェック

RSpec の expect(foo).to receive(:bar).with(...) では引数のチェックがいくつかの方法で可能だが、ブロックで検証したいケースが結構ある。

with には matcher を渡すことができるので、カスタム matcher を定義してやればよい。
下記の例では Struct の特定の attribute のみ検証している

RSpec::Matchers.define :a_valid_argument do
  match {|actual| block_arg.call(actual) }
end

it "should call with valid argument" do
  jon = Struct.new(:name).new("Jon")

  expect($stdout).to receive(:write).with(
    a_valid_argument {|a| a.name == "Jon" },
  )

  $stdout.write(jon)
end

なお、with による引数の検証は引数ごとに行われるので複数の引数をとるメソッドの場合は、引数ごとに matcher を渡す必要がある。

it "should call with valid arguments" do
  jon = Struct.new(:name).new("Jon")
  dany = Struct.new(:name).new("Dany")

  expect($stdout).to receive(:write).with(
    a_valid_argument {|a| a.name == "Jon" },
    a_valid_argument {|a| a.name == "Dany" },
  )

  $stdout.write(jon, dany)
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby 様々な繰り返し処理について

前置き

Rubyの反復処理についてまとめる。

eachメソッド (配列・ハッシュ)

Rubyでは最も使う反復処理のメソッド。配列・ハッシュに対して便利。

each.rb
fruits = ["apple", "banana", "grape"]
fruits.each{ |fruit| puts fruit }

配列fruitsの要素を、インデックスの若い順から1つずつ取り出して、引数fruitに入れて{}内の処理を実行する。処理が一行だけなので、{}を使っているが、

each_do.rb
numbers = [1, 2, 3, 4, 5]
sum = 0
numbers.each do |number|
  if number % 2 == 0
    sum += number / 2
  else
    sum += number
  end
end
sum #=> 12

複数行の場合は、do endを使うと書きやすい。

timesメソッド(繰り返す回数)

繰り返す回数が決まっている処理に対して便利。

times.rb
sum = 0
10.times { |n| sum += n }
sum #=> 45

upto・downtoメソッド(数値)

初めと終わりの数値が決まっているときに便利。

upto.rb
15.upto(20) do |age|
  if age < 18
    puts "#{age}歳では選挙権はありません"
  else
    puts "#{age}歳は18歳以上なので選挙権があります"
  end
end

uptoは数値を増やす方向に処理を繰り返す。
指定した最初の数値15 から最後の数値20 まで処理を実行。

downto.rb
20.downto(15) do |age|
  if age < 18
    puts "#{age}歳では選挙権はありません"
  else
    puts "#{age}歳は18歳以上なので選挙権があります"
  end
end

downtoは数値を減らす方向に処理を繰り返す。
指定した最初の数値20 から最後の数値15 まで処理を実行。

while文・until文(終了条件)

終了条件が決まっているときに便利

while.rb
array = (1..100).to_a
sum = 0
i = 0
while sum < 1000
  sum += array[i]
  i += 1
end
sum #=> 1035

whileの横にある条件が真の間、while endの間の処理を繰り返す。
上のコードでは、合計値sumが1000を超えたときに処理を終了する。

until文は、while文とは逆で、条件が偽のときに処理を繰り返すので、

until.rb
array = (1..100).to_a
sum = 0
i = 0
until sum >= 1000
  sum += array[i]
  i += 1
end
sum #=> 1035

上のコードは、while.rbと同じ意味になる。

loopメソッド(無限ループ)

while trueでも無限ループを作れるが、loopメソッドを使うとよりシンプルに作れる。

loop.rb
numbers = [1, 2, 3, 4, 5, 6, 7]
loop do
  n = numbers.sample
  puts n
  break if n == 7
end

まとめ

繰り返し処理に使うメソッド・構文は、ケースにあったものを使うと便利。

参考文献

プロを目指す人のためのRuby入門
伊藤淳一 [著]

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

Ubuntu 16.04にパペットマスターとクライアントをインストールする

Puppetは、自動化からアップデートインストールまで、様々なアプリケーションに対応したオープンソースの設定管理システムです。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

序章

Puppetは、自動化からアップデートインストールまで幅広く利用できるオープンソースの設定管理システムです。Rubyで書かれており、UnixやWindowsのようなOSの設定を管理するために特別に設計されています。1台のサーバから数千台の物理サーバや仮想サーバを一元管理して簡単に導入・管理することができます。

Puppetは、クライアントサーバ型のアーキテクチャでもスタンドアロン型のアーキテクチャでも使用できます。クライアント・サーバ・アーキテクチャでは、サーバはマスターとして、クライアントはエージェントとして知られています。Puppetには、EnterpriseとOpen sourceの2つのバージョンがあります。どちらも多くのLinuxディストリビューションとWindowsをサポートしています。Puppetは、システム管理者が反復的な作業に費やす時間を削減し、より大きなビジネス価値を提供するプロジェクトに集中できるようにすることで、システム管理者を支援します。

特徴

  • PuppetはIdempotencyをサポートしており、同じマシン上で同じ設定を複数回実行することが容易になります。
  • 同じ問題を解決している全員のタスクを重複して実行する必要がなくなります。
  • すべてのタスクはネイティブコードで書かれており、簡単に共有することができます。
  • 繰り返し可能な変更を自動的に行うことができます。
  • 必要に応じて拡張機能を追加することで、余分な機能を追加することができます。

このガイドでは、オープンソースのPuppetをUbuntu 16.04サーバ上で、Alibaba Cloud Elastic Compute Service (ECS)インスタンスを使用してクライアント/サーバアーキテクチャでインストールし、設定する手順を説明します。

前提条件

  • Ubuntu 16.04がインストールされたパペットマスター用のAlibaba Cloud ECSインスタンス。
  • Ubuntu 16.04がインストールされたPuppetエージェント用のAlibaba Cloud ECSインスタンス。
  • パペットマスターに静的IPアドレス192.168.0.103が設定されている。
  • パペットエージェントに192.168.0.104の静的IPアドレスが設定されている。
  • パペットマスターには最低4GBのメモリとデュアルコアCPUが必要。
  • 両方のインスタンスにsudo権限を持つ非rootユーザが設定されている。

ホスト名の設定

始める前に、Server ノードと agent ノードで /etc/hosts と /etc/hostname ファイルを設定して、これらのファイルが相互に通信できるようにする必要があります。

Serverノードで/etc/hostsと/etc/hostnameファイルを開き、以下の変更を行います。

sudo nano /etc/hosts

ファイルの最後に以下の行を追加します。

192.168.0.0.103 puppet-server

sudo nano /etc/hostname

以下のようにファイルを変更します。

puppet-server

保存し、終了したらファイルを閉じます。

Agentノードで/etc/hostsと/etc/hostnameファイルを開き、以下の変更を行います。

sudo nano /etc/hosts

ファイルの最後に以下の行を追加します。

192.168.0.0.103 puppet-server

sudo nano /etc/hostname

以下のようにファイルを変更します。

puppet-agent

終了したら保存して閉じてください。

Puppetのインストール

Ubuntu 16.04のデフォルトリポジトリではPuppetサーバーは利用できません。そのため、MasterノードとAgentノードの両方にPuppet Labリポジトリを追加する必要があります。

それぞれのノードで以下のコマンドを実行して、Puppetのリポジトリをダウンロードしてインストールします。

wget https://apt.puppetlabs.com/puppetlabs-release-pc1-xenial.deb
sudo dpkg -i puppetlabs-release-pc1-xenial.deb
sudo apt-get update -y

次に、以下のコマンドでマスターノードにPuppetサーバパッケージをインストールします。

sudo apt-get install puppetserver -y

Puppetサーバをインストールした後、メモリの割り当てを設定する必要があります。マスターノードのメモリ量に応じてメモリ使用量をカスタマイズすることをお勧めします。これは/etc/default/puppetserverファイルを編集することで行うことができます。

sudo nano /etc/default/puppetserver

サーバーの容量に合わせて行を変更してください。

以下の行を変更してください。

JAVA_ARGS="-Xms2g -Xmx2g -XX:MaxPermSize=256m”から

JAVA_ARGS="-Xms512m -Xmx512m”へ。

保存してファイルを閉じ、以下のコマンドでPuppetサーバを起動し、起動時に起動できるようにします。

sudo systemctl start puppetserver
sudo systemctl enable puppetserver

Puppetサーバーの状態は以下のコマンドで確認できます。

sudo systemctl status puppetserver

すべてが正常な場合は、以下のような出力が表示されるはずです。

● puppetserver.service - puppetserver Service
   Loaded: loaded (/lib/systemd/system/puppetserver.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2017-10-28 18:47:26 IST; 12min ago
  Process: 887 ExecStart=/opt/puppetlabs/server/apps/puppetserver/bin/puppetserver start (code=exited, status=0/SUCCESS)
 Main PID: 963 (java)
   CGroup: /system.slice/puppetserver.service
           └─963 /usr/bin/java -Xms256m -Xmx256m -Djava.security.egd=/dev/urandom -XX:OnOutOfMemoryError=kill -9 %p -cp /opt/puppetlabs/server/

Puppet Agentのインストール

これでPuppetサーバーは稼働しています。いよいよAgentノードにPuppet agentをインストールする時が来ました。

Puppet agentをインストールする前に、AgentノードにPuppet Labリポジトリがインストールされていることを確認してください。次に、以下のコマンドを実行するだけでPuppet agentをインストールします。

sudo apt-get install puppet-agent -y

Puppet Agentをインストールしたら、puppet設定ファイルを編集し、puppetマスター情報を設定する必要があります。

これは以下のコマンドで行うことができます。

sudo nano /etc/puppetlabs/puppet/puppet.conf

以下の行を追加します。

[main]
certname = puppet-agent
server = puppet-server
environment = IT

保存してファイルを閉じ、以下のコマンドでPuppet Agentサービスを起動し、起動時に起動できるようにします。

sudo systemctl start puppet
sudo systemctl enable puppet

パペットサーバー上のパペットエージェント証明書に署名する

Puppetが初めてAgentノードを実行するとき、Puppetはパペットサーバに証明書署名要求を送信します。クライアント・サーバ型のアーキテクチャでは、エージェントノードを制御するために、パペットマスターサーバが各エージェントノードの証明書要求を承認する必要があります。

パペットサーバ上で、以下のコマンドですべての署名されていない証明書要求をリストアップします。

sudo /opt/puppetlabs/bin/puppet cert list

エージェントノードのホスト名で1つのリクエストが表示されているはずです。

"puppet-agent" (SHA256) 7C:28:E8:AF:09:23:55:19:AF:C1:EE:C3:66:F2:02:73:AD:7F:53:17:28:CE:B0:26:AE:C7:6C:67:16:05:6F:2E

次に、以下のコマンドで証明書要求に署名します。

sudo /opt/puppetlabs/bin/puppet cert sign puppet-agent

以下のような出力が表示されるはずです。

Signing Certificate Request for:
  "puppet-agent" (SHA256) 7C:28:E8:AF:09:23:55:19:AF:C1:EE:C3:66:F2:02:73:AD:7F:53:17:28:CE:B0:26:AE:C7:6C:67:16:05:6F:2E
Notice: Signed certificate request for puppet-agent
Notice: Removing file Puppet::SSL::CertificateRequest puppet-agent at '/etc/puppetlabs/puppet/ssl/ca/requests/puppet-agent.pem'

これでパペットマスターサーバーはAgentノードと通信して制御できるようになりました。複数のノードの証明書要求に一度に署名したい場合は、以下のコマンドを実行します。

sudo /opt/puppetlabs/bin/puppet cert sign —all

PuppetマスターがPuppet Agent証明書に署名したら、Puppet Agentノードで以下のコマンドを実行してテストします。

sudo /opt/puppetlabs/bin/puppet agent —test

すべてが正しく行われていれば、以下のような出力が表示されるはずです。

Info: Using configured environment 'production'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Caching catalog for puppet-agent
Info: Applying configuration version '1509200872'
Notice: Applied catalog in 0.09 seconds

エージェントノードにApacheをインストールするためにPuppetサーバを設定する

これでPuppetマスター、エージェントノードともに設定が完了し、機能するようになりました。いよいよPuppetを検証してみましょう。

そのために、AgentノードにApacheウェブサーバをインストールするためのマニフェストファイルを作成します。マニフェストはクライアントの設定を含むデータファイルです。デフォルトでは、マニフェストファイルは /etc/puppetlabs/code/environments/production/manifests/directory.にあります。

マニフェストファイルの作成に進む前に、puppetlabs-apacheモジュールをインストールする必要があります。

Puppetマスターノードで、以下のコマンドを実行して、puppetlabs-apacheモジュールをインストールします。

sudo /opt/puppetlabs/bin/puppet module install puppetlabs-apache

以下のような出力が表示されるはずです。

Notice: Preparing to install into /etc/puppetlabs/code/environments/production/modules ...
Notice: Downloading from https://forgeapi.puppet.com ...
Notice: Installing -- do not interrupt ...
/etc/puppetlabs/code/environments/production/modules
└─┬ puppetlabs-apache (v2.3.0)
  ├── puppetlabs-concat (v4.1.0)
  └── puppetlabs-stdlib (v4.20.0)

次に、以下のコマンドでPuppetマスター上にマニフェストファイルを作成します。

sudo nano /etc/puppetlabs/code/environments/production/manifests/site.pp

以下の行を追加します。

node 'puppet-agent' {
  class { 'apache': }             # use apache module
  apache::vhost { 'localhost':  # define vhost resource
    port    => '80',
    docroot => '/var/www/html'
  }
}

上記の設定では、Apacheをインストールし、localhostというバーチャルホストを設定し、80番ポートでリスニングし、Agentノード上にドキュメントルート/var/www/htmlを持つようにします。

さて、Agentノード上で以下のコマンドを実行して、マニフェストファイルからすべての設定を取得します。

sudo /opt/puppetlabs/bin/puppet agent —test

すべてが成功した場合、以下のような出力が表示されるはずです。


Notice: /Stage[main]/Apache/Apache::Vhost[default]/File[15-default.conf symlink]/ensure: created
Info: /Stage[main]/Apache/Apache::Vhost[default]/File[15-default.conf symlink]: Scheduling refresh of Class[Apache::Service]
Notice: /Stage[main]/Main/Node[puppet-agent]/Apache::Vhost[localhost]/Concat[25-localhost.conf]/File[/etc/apache2/sites-available/25-localhost.conf]/ensure: defined content as '{md5}05a8b8c6772009021086814bdf8c985e'
Info: Concat[25-localhost.conf]: Scheduling refresh of Class[Apache::Service]
Notice: /Stage[main]/Main/Node[puppet-agent]/Apache::Vhost[localhost]/File[25-localhost.conf symlink]/ensure: created
Info: /Stage[main]/Main/Node[puppet-agent]/Apache::Vhost[localhost]/File[25-localhost.conf symlink]: Scheduling refresh of Class[Apache::Service]
Info: Class[Apache::Service]: Scheduling refresh of Service[httpd]
Notice: /Stage[main]/Apache::Service/Service[httpd]: Triggered 'refresh' from 1 events
Notice: Applied catalog in 53.11 seconds
`

おめでとうございます。これで Apache が Agent ノードにインストールされ、実行されています。

結論

このチュートリアルでは、本番環境にPuppetサーバーを簡単にインストールし、ITインフラ全体を簡単に管理することができます。Puppetの詳細については、Puppetの公式ドキュメントページを参照してください。その他のチュートリアルは、Alibaba Cloud Getting Startedチャンネルにも掲載されています。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

[Rails] エラーメッセージ を表示する - render、redirect_toの違い、flashについて -

エラーメッセージを表示する方法について書きます。
Railsでメッセージ出力する際によく使用されるrender、redirect_to、flashについても簡単に書きます。

実装したいこと

フォームの入力値に誤りがあるときに、以下のようなエラーメッセージを表示させる
image.png

実装したコード

login_controller.rb
    @error_txt = '※入力に誤りがあるか、登録されていません。'
    render :new

インスタンス変数@error_txtにエラーメッセージをセットし、renderでテンプレートnewを表示させるように指定します。
※エラー判定の処理は省略しています

new.html.slim
    - if @error_txt
        p.error
            = @error_txt

テンプレート側ではインスタンス変数の@error_txtの値の存在チェックを行い、あれば@error_txtを表示させます。

renderとは

renderとはテンプレートを指定して表示させる(レンダリングする)ためのメソッドです。
画面遷移させることなく、表示させることができます。

redirect_toとの違い

renderと同様にページを指定して表示させるメソッドにredirect_toがあります。
renderが画面遷移することなく指定したテンプレートを表示するのに対して、
redirect_toメソッドはリダイレクトさせるためのメソッドで、URLを指定してリクエストを再送信するようにブラウザに指令を出します。
ブラウザはこの指令に応じ、指定されたURLに対して改めてリクエストをサーバーに送信します。
ブラウザとサーバー間のやりとりや処理が増えるため、改めてリクエストを送信させる必要がない場合は、renderを使うのが良いです。

今回はフォームの入力内容に対してエラーメッセージを表示させるだけで、リダイレクトさせたくないためrenderを使用します。

flashとは

railsにおけるメッセージ表示について調べるとflashに関する記事が多くでてきます。
flashはsessionを利用した機能の一つでメッセージを画面に表示するためのメソッドです。
flashで設定したメッセージはセッションに保存され、リダイレクトされても保持されるためredirect_toメソッドを一緒に使うことが多いです。

今回はrenderを使用しており、リダイレクトもなく、セッションに値を保持する必要がないのでflashを使わずにインスタンス変数を使いました。

まとめ

  • リダイレクト不要なエラーメッセージはrenderとインスタンス変数で実装する
  • リダイレクトが必要な場合は、redirect_toとflashで実装する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでインスタやTwitterのようなハッシュタグ検索を実装(gemなし)

概要

私は現在DMMWEBCAMPというプログラミングスクールに通っておりまして
3ヶ月目の課題であるポートフォリオにインスタグラムやTwitterで使用されているような
ハッシュタグもどきを実装しました。
今後実装される方の参考になればと思います。

参考サイト

Railsで作ったインスタもどきのキャプションにハッシュタグを実装
https://qiita.com/goppy001/items/791c946abdb41c9495bb

大まかな流れは上記サイトと同じですが、うまく出来なかったところを変更しています。

完成図

投稿画面

スクリーンショット 2020-06-24 11.28.23.png

投稿詳細画面

スクリーンショット 2020-06-24 11.28.55.png

ハッシュタグ一覧及びハッシュタグ投稿一覧画面

スクリーンショット 2020-06-24 11.29.11.png

事前準備DB

投稿テーブル
スクリーンショット 2020-06-24 11.36.41.png

最初はbodyとuser_idのみのテーブル構成でしたが、ハッシュタグを入力するためのhashbodyカラムを追加しています。
ちなみに画像はポートフォリオの仕様上他テーブルへ保存しておりますが、imageのカラムがこのテーブル内にあっても問題はありません。

モデル(DB)の作成

ハッシュタグモデル

$rails g model Hashtag hashname:string

ハッシュタグ保存用のモデルを作成します。
hashnameカラムにハッシュタグが保存されます。

マイグレーションファイルの編集

create_hashtags.rb
class CreateHashtags < ActiveRecord::Migration[5.2]
  def change
    create_table :hashtags do |t|
      t.string :hashname

      t.timestamps
    end
    add_index :hashtags, :hashname, unique: true
  end
end

中間テーブルの作成

$ rails g model HashtagPostImage post_image:references hashtag:references

HashtagテーブルとPostImage(投稿)テーブルの中間テーブルです。
参考にさせて頂いた記事ではここのコマンドが若干違います。
中間テーブルなので外部キーとしてhashtagとpostimageのidを持ってきます。
references型なので作成時にhashtag_idと打ってしまうと
出来上がったカラム名がhashtag_id_idとなってしまうので注意が必要です。

マイグレーションファイル

create_hashtag_post_images.rb
class CreateHashtagPostImages < ActiveRecord::Migration[5.2]
  def change
    create_table :hashtag_post_images, id: false do |t|
      t.references :post_image, foreign_key: true
      t.references :hashtag, foreign_key: true
    end
  end
end

マイグレート

$ rails db:migrate

作成されたDB

スクリーンショット 2020-06-24 11.59.49.png

モデルのアソシエーションとバリデーションの設定

ハッシュタグモデル

hashtag.rb
class Hashtag < ApplicationRecord
  validates :hashname, presence: true, length: { maximum: 50 }
  has_many :hashtag_post_images
  has_many :post_images, through: :hashtag_post_images
end

とりあえず50文字を上限にしました。

中間テーブル

hashtag_post_image.rb
class HashtagPostImage < ApplicationRecord
  belongs_to :post_image
  belongs_to :hashtag
  validates :post_image_id, presence: true
  validates :hashtag_id, presence: true
end

PostImageモデル

post_image.rb
class PostImage < ApplicationRecord
  has_many :hashtag_post_images
  has_many :hashtags, through: :hashtag_post_images
end

PostImageモデルに下記を追加

postimage.rb
after_create do
    post_image = PostImage.find_by(id: id)
    # hashbodyに打ち込まれたハッシュタグを検出
    hashtags = hashbody.scan(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/)
    hashtags.uniq.map do |hashtag|
      # ハッシュタグは先頭の#を外した上で保存
      tag = Hashtag.find_or_create_by(hashname: hashtag.downcase.delete('#'))
      post_image.hashtags << tag
    end
  end
  #更新アクション
  before_update do
    post_image = PostImage.find_by(id: id)
    post_image.hashtags.clear
    hashtags = hashbody.scan(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/)
    hashtags.uniq.map do |hashtag|
      tag = Hashtag.find_or_create_by(hashname: hashtag.downcase.delete('#'))
      post_image.hashtags << tag
    end
  end

作成と更新時にこのアクションが実行されるように記入してあります。

・post_image = PostImage.find_by(id: id)
作成した投稿を探させます。

・ hashtags = hashbody.scan(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/)
ここでは入力されたハッシュタグ、先頭に[##]がつく入力値を探します。
hashbodyは私のDBのカラム名ですので、ここはアプリケーションにより異なります。
投稿テーブルのテキスト入力用のカラムであれば何でも良いです。

・ hashtags.uniq.map do |hashtag|
# ハッシュタグは先頭の#を外した上で保存
tag = Hashtag.find_or_create_by(hashname: hashtag.downcase.delete('#'))
post_image.hashtags << tag
end

mapで繰り返すことにより、複数のハッシュタグがpostimageに保存されます。

・post_image.hashtags.clear
更新時、一回ハッシュタグを消しているようです。

routeの記載

routes.rb
get '/post_image/hashtag/:name' => 'post_images#hashtag'
get '/post_image/hashtag' => 'post_images#hashtag'

私の場合はハッシュタグ一覧ページを作りたかったので二つのルートを用意しました。

PostImage ヘルパーの編集

post_images_helper.rb
module PostImagesHelper
  def render_with_hashtags(hashbody)
    hashbody.gsub(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/) { |word| link_to word, "/post_image/hashtag/#{word.delete("#")}",data: {"turbolinks" => false} }.html_safe
  end
end

link_to word, "/post_image/hashtag/#{word.delete("#")}"
ここのurlはアプリケーションの内容によって異なります。
ハッシュタグをクリックするとここのurlに飛びますという意味です。
先ほどrouteに書いたurlを打ち込みましょう。

View

投稿本文内で実際にハッシュタグを表示するところ

スクリーンショット 2020-06-24 11.28.55.png

Viewにて以下を表記します。

post_images/show.html.erb
<%= render_with_hashtags(@postimage.hashbody) %>

上記は先ほどhelperで作成したメソッドを呼び出しています。
ちなみにpost_images/showのコントローラーはこちらです。

controllers/post_images_controller.rb
def show
    @postimage = PostImage.find(params[:id])
end

単純に@postimageのhashtag入力欄の情報をヘルバーに渡しているんではないかと考えています。

ハッシュタグ一覧ページ

スクリーンショット 2020-06-24 11.29.11.png

クリックするとこんな感じです。
スクリーンショット 2020-06-24 13.12.07.png

PostImageコントローラーにhashtagアクションの追加

post_images_controller.rb
def hashtag
    @user = current_user
    if params[:name].nil?
      @hashtags = Hashtag.all.to_a.group_by{ |hashtag| hashtag.post_images.count}
    else
      @hashtag = Hashtag.find_by(hashname: params[:name])
      @postimage = @hashtag.post_images.page(params[:page]).per(20).reverse_order
      @hashtags = Hashtag.all.to_a.group_by{ |hashtag| hashtag.post_images.count}
    end
  end

ここの表記は作成するサイトにより変わります。
私はハッシュタグ一覧がみれるページを作りたかったので、params[:name].nil?の場合は
post_imageを表示しないという条件分岐をしています。
またgroup_byですが、hashtagに紐づく投稿が多い順番でハッシュタグを表示できるように
このような表記をしています。

ハッシュタグのView

post_images/hashtag.html.erb
<div class="row">
        <% if params[:name] == nil %>

        <% else %>
        <div class= "col-xs-12 col-lg-12 col-md-12 col-sm-12">
            <div class="hashtag-post-box">
                <h3 class="search-title">#<%= @hashtag.hashname %>:  <%= @postimage.count %> 件 </h3>
                <div class="flex-box">
                <% @postimage.each do |postimage| %>
                    <div class= "post-image-index-post-box">
                        <p class="index-post-box-top">
                            <%= postimage.created_at.strftime("%Y/%m/%d") %>
                        </p>
                        <span class='far fa-comments index-comment-count' id='comment-count_<%= postimage.id %>' style="color: #777777;">
                            <%= render 'post_image_comments/comment-count', postimage:postimage %>
                        </span>

                        <span id = "favorite-button_<%= postimage.id %>"class="post-box-top-favorite">
                            <%= render 'post_image_favorites/favorite',postimage: postimage %>
                        </span>
                        <%= link_to post_image_path(postimage),data: {"turbolinks" => false}  do %>
                            <ul class="slider">
                                    <% postimage.post_image_images.each do |post| %>
                                        <li>
                                        <%= attachment_image_tag post, :image ,size:'430x360', format:'jpg',class:"image" %>
                                    </li>
                                <% end %>
                            </ul>
                        <% end %>
                        <p class="hashtag-post-box-name">
                            <%= link_to user_path(postimage.user) do %>
                                <%= attachment_image_tag postimage.user, :profile_image,size:'30x30', format:'jpg',fallback:'no_image.jpg',class:'min-image' %>
                                <span class="index-post-box-user"><%= postimage.user.name %>
                                </span>
                            <% end %>
                        </p>
                        <div class="image-show-body-hash" style="padding:2%">
                            <%= simple_format(postimage.body.truncate(50))%>
                            <% if postimage.body.length > 50 %>
                                <span class="text-prev"><%= link_to '続きを読む', post_image_path(postimage), data: {"turbolinks" => false} %>
                                </span>
                            <% end %>
                        </div>
                    </div>
                <% end %>
                </div>
            </div>
            <div class="image-index-pagination" data-turbolinks="false">
                <%= paginate @postimage,class:"paginate" %>
            </div>
        </div>
        <% end %>
    </div>
    <div class="row">
        <div class= "col-xs-12 col-lg-12 col-md-12 col-sm-12">
            <div class= "hashtag-name">
                <% @hashtags.sort.reverse.each do |count| %>
                        <% count[1].each do |hashtag| %>
                        <p><%= link_to  "##{hashtag.hashname} (#{hashtag.post_images.count}) 件","/post_image/hashtag/#{hashtag.hashname}",data: {"turbolinks" => false} %>
                        </p>
                        <% end %>
                <% end %>
            </div>
        </div>
    </div>
</div>

クラス名とかを使いまわしていて訳わかんなくなっていてすいません。
大事なところは以下です。

post_images/hashtag.html.erb
<% if params[:name] == nil %>

<% else %>

<% end %>

この表記で先ほどrouteに書いたpost_image/hashtagとpost_image/hashtag/:nameでの条件分岐をしています。
paramsがnilのときの処理をコントローラーとViewそれぞれに書くことでエラーを起こさせないようにしています。

post_image/hashtag.html.erb
<div class= "hashtag-name">
    <% @hashtags.sort.reverse.each do |count| %>
        <% count[1].each do |hashtag| %>
            <p><%= link_to  "##{hashtag.hashname} (#{hashtag.post_images.count}) 件","/post_image/hashtag/#{hashtag.hashname}",data: {"turbolinks" => false} %>
            </p>
        <% end %>
    <% end %>
</div>

ここにハッシュタグ一覧を表示しています。
表示はハッシュタグに紐づく投稿が多い順に表示しています。

まとめ

ハッシュタグ入力欄を別にしないと、投稿の説明文のところにそのまま文章としてハッシュタグが残ってしまうため別カラムへの保存という形で実装しました。
投稿と一緒にハッシュタグを表示させている箇所では、意図的にhashbodyを非表示にしています。
view上にやたらと存在するturbolinks falseですがjsがうまく動かなくて書いてあるものなので、無視して頂いて大丈夫です。

初めての投稿で分かりづらい箇所があれば申し訳ないです。
これからポートフォリオを作成する方の参考になれば幸いです。

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

Rubyで最もシンプルなブロックチェーンを構築する方法

この記事では、Rubyを使ってシンプルなワーキングブロックチェーンデモを構築する方法を探っていきます。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

ステージ1:送金

この段階で残高確認と振込を実施します。送金は口座残高に基づいて実行される加算または減算です。

この機能を実装するには、HTTPプロトコルのGETとPOSTが最適です。GETはサーバからデータを取得し、POSTはサーバ上のデータを変更します。

ここでは、UIの表示はHTMLプロトコルを必要としません。RubyのWebフレームワークSinatraを使ってURLや関連するメソッドを整理し、UEを使ってコマンドラインで転送情報を見ることができます。

クライアント側のメソッドとサーバ側のURLは非常にシンプルです。

クライアント: client.rb

def create_user(name) … end

  def get_balance(user)  … end

  def transfer(from, to, amount) ... end

サーバー:haseebcoin.rb

get "/balance" ... end

  post "/users" ... end

  post "/transfers" ... end

この層に必要な知識:ruby、HTTP GET、POST、Sinatra

ステージ2:ゴシップネットワークの構築

ブロックチェーンには "ゴシッププロトコル "と呼ばれる分散型の構造があります。ここでいう "ゴシップ "とは噂ではなく、分散型ネットワークで拡散される情報のことを指します。

映画の名前が交換されるゴシップネットワークを構築してみましょう。

client.rbは指定されたポートにメッセージを送信します。

def self.gossip(port, state)
  ...  

  Faraday.post("#{URL}:#{port}/gossip", state: state).body

  ...

end

gossip.rbは、送信元ポートと送信先ポートの2つのパラメータを受け取ります。ポート1111や2222など、ソース側の特定のポートを介して情報を交換します。

実際の分散型ネットワークでは、2つのポートは本質的に2つのネットワークノードです。異なるローカルポート間で情報を交換することは、シミュレーションされたネットワークの異なるノード間の通信を表しています。

各ノードで
3 秒ごとに好きな映画の名前を話してください。

every(3.seconds) do

  …

  gossip_response = Client.gossip(port, JSON.dump(STATE))

  update_state(JSON.load(gossip_response))

  ...

end

8秒ごとにお気に入りの映画名を変更します。

every(8.seconds) do

  …

  update_state(PORT => [@favorite_movie, @version_number])

  ...

end

サーバはデータを受信して処理します。

post '/gossip' do

  …

  update_state(JSON.load(their_state))

  …

end

4人のネットワークで

1、最初のノードで gossip.rb 1111 を実行します。最初のノードはポート1111で好きな映画の名前を話します。
2、gossip.rb 2222 1111を実行します。2番目のノードはポート2222で好きなムービー名を最初のノード(ポート1111)に発言します。
3、gossip.rb 3333 2222を実行します。3番目のノードは、ポート3333から2番目のノード(ポート2222)にお気に入りのムービー名を話します。
4、gossip.rb 4444 3333を実行します。4番目のノードは、ポート4444から3番目のノード(ポート3333)にお気に入りのムービー名を話します。

しばらく実行してようやく4つのノードがピアエンドの情報を取得し、データは変化し続けます。これが単純なGossipネットワークです。

ステージ3:データの暗号化と復号化

トップレベルの暗号化アルゴリズムは、ブロックチェーンの基礎となるものです。この層では、ブロックチェーンアカウントを実装するために非対称暗号化技術が使用されています。RSAアルゴリズムは公開鍵、秘密鍵を生成し、非対称暗号化を強制することができます。

def generate_key_pair … end
def sign(plaintext, raw_private_key) ... end

Ruby言語のOpenSSLモジュールのおかげで、非対称暗号化や署名検証を素早く実装することができます。ブロックチェーンでは、公開鍵はアカウント、秘密鍵はパスワードです。鍵のペアはそれぞれ1つのブロックチェーンアカウントになります。

暗号文を復号化する。

def plaintext(ciphertext, raw_public_key) … end

暗号文がメッセージであるかどうかを確認します。

def valid_signature?(message, ciphertext, public_key) … end

この層に必要な知識:非対称暗号化アルゴリズム

ステージ4:データマイニング

この段階では、プルーフ・オブ・ワークが実装され、ブロックチェーン用のブロックが生成されます。これは時間と手間のかかるプロセスです。ハッシュ関数は不可逆的であり、競合はありません。計算プロセスは簡単です。入力に対してハッシュ演算を行うだけで結果が得られる。

入力とは、送金額、送金人の名前、受取人の名前など、送金に関する情報である。ハッシュ演算には様々なアルゴリズムがある。

ここでは、SHA256アルゴリズムを使用します。

def hash(message) … end

同じ情報をハッシュ化すると、毎回違う結果が出てきます。得られた結果が、例えば「0の数桁から始まる」などの特徴を満たすまで演算を続けます。

結果が数桁の0から始まるかどうかを確認します。

def is_valid_nonce?(nonce, message)

  hash(message + nonce).start_with?("0" * NUM_ZEROES)

end

上記の条件を満たすための作業を行うことは容易ではない。多くの時間を消費する。このような作業を全てマイニングといいます。

def find_nonce(message)

  …  

  until is_valid_nonce?(nonce, message)

  ...

end

入力には前回のハッシュ操作の結果が含まれます。したがって、各ハッシュ操作は前のハッシュ操作の影響を受けます。言い換えれば、これはチェーン構造である。これがブロックチェーンと呼ばれる所以です。

ステージ5:最長チェーンルール

この段階では、最初のブロックが初期化され、それに応じてブロックチェーン構造が生成され、ブロックチェーンが形成されます。ブロックチェーンはArray構造に格納されます。保存中に、ブロックは検証を受けなければなりません。

ブロックを初期化します。

def initialize(prev_block, msg)

  @msg = msg

  @prev_block_hash = prev_block.own_hash if prev_block

  mine_block!

end

採掘中に一番やりがいを感じる作業は、nonceを見つけることです。

def mine_block!

  @nonce = calc_nonce

  @own_hash = hash(full_block(@nonce))

end

完全なブロックはこのように圧縮されます。

def full_block(nonce)

  [@msg, @prev_block_hash, nonce].compact.join

end

ブロックチェーンを初期化する: class BlockChain

Arrayを使って保存するだけ!

def initialize(msg)

  @blocks = []

  @blocks << Block.new(nil, msg)

end

チェーンにブロックを追加する。ブロックチェーン全体が継続的に成長している。

def add_to_chain(msg)

  @blocks << Block.new(@blocks.last, msg)

  puts @blocks.last

end

ブロックが健全かどうかを厳密に検証する必要があります。

def valid?

  @blocks.all? { |block| block.is_a?(Block) } &&

    @blocks.all?(&:valid?) &&

    @blocks.each_cons(2).all? { |a, b| a.own_hash == b.prev_block_hash }

end

ステージ6. ピースの組み合わせ

最後に、Blockchainは、ネットワーク内のすべてのコンポーネントとの調和のとれたコラボレーションを通じて、その魔法を働かせます。第一段階では、転送はトランザクションクラスであり、情報に署名するために秘密鍵を使用する必要があります。

@signature = PKI.sign(message, priv_key)

最初のブロックを手に入れた鉱夫の報酬は50万枚の銀貨です。

def self.create_genesis_block(pub_key, priv_key)

  genesis_txn = Transaction.new(nil, pub_key, 500_000, priv_key)

  Block.new(nil, genesis_txn)

end

アカウントに請求された支出が有効かどうかを確認してください。

def all_spends_valid?

  compute_balances do |balances, from, to|

    return false if balances.values_at(from, to).any? { |bal| bal < 0 }

  end

  true

end

ネットワークの成長を維持するために未知のノード$PEERSを追加します。

if PEER_PORT.nil?

  # You are the progenitor!

  $BLOCKCHAIN = BlockChain.new(PUB_KEY, PRIV_KEY)

  else

  # You're just joining the network.

  $PEERS << PEER_PORT

end

ノード間のデータ処理は、ブロックチェーンとPEERを読み込んで更新します。

# @param blockchain

# @param peers

post '/gossip' do

  their_blockchain = YAML.load(params['blockchain'])

  their_peers = YAML.load(params['peers'])

  update_blockchain(their_blockchain)

  update_peers(their_peers)  

  YAML.dump('peers' => $PEERS, 'blockchain' => $BLOCKCHAIN)

end

受信したブロックの処理は、チェーンが長くなっているかどうかに注目しています。

def update_blockchain(their_blockchain)  

  return if their_blockchain.nil?

  return if $BLOCKCHAIN && their_blockchain.length <= $BLOCKCHAIN.length

  return unless their_blockchain.valid?  $BLOCKCHAIN = their_blockchain

  end

新しいものになるまでPEERを更新する。

def update_peers(their_peers)

  $PEERS = ($PEERS + their_peers).uniq

end

送金の際には、受取人のpub_keyを取得し、送金人のpub_keyを経由して送金します。

# @param to (port_number)

# @param amount

post '/send_money' do

  to = Client.get_pub_key(params['to'])

  amount = params['amount'].to_i

  $BLOCKCHAIN.add_to_chain(Transaction.new(PUB_KEY, to, amount, PRIV_KEY))

  'OK. Block mined!'


end

ブロックチェーンをゴシップネットワークに入れて、すべての機能コンポーネントを組み立てる。これで完成です。ブロックチェーンの作成に成功しました。

このデモについての詳細はGithubで見ることができます: https://github.com/Haseeb-Qureshi/lets-build-a-blockchain

ブロックチェーンやその他の革新的な技術の詳細については、www.alibabacloud.com をご覧ください。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

RubyでBlackJack-cliを作ってみた感想とか

Qiitaでこんな記事を見つけたので、BlackJackをRubyで作ってみました。
プログラミング入門者からの卒業試験は『ブラックジャック』を開発すべし | Qiita

blackjack_ruby_gif.gif

感想としては簡単なゲームを作るだけですが、とても楽しかったです。BlackJackを作ってみてゲーム以外にも色々作れそうな気がしたのでもう少しBlackJackのコードを変えながら遊んでみてから次何を作るか考えようかなと思ってます。

全てのコードはGithub上に上げているのでこちらから確認できます
BlackJack Ruby | Ryutaro - Github

ブラックジャックのルール

ブラックジャックのルールについては、こちらの記事に書かれているルールを基準に作成しました。

  • 初期カードは52枚。引く際にカードの重複は無いようにする
  • プレイヤーとディーラーの2人対戦。プレイヤーは実行者、ディーラーは自動的に実行
  • 実行開始時、プレイヤーとディーラーはそれぞれ、カードを2枚引く。引いたカードは画面に表示する。ただし、ディーラーの2枚目のカードは分からないようにする
  • その後、先にプレイヤーがカードを引く。プレイヤーが21を超えていたらバースト、その時点でゲーム終了
  • プレイヤーは、カードを引くたびに、次のカードを引くか選択できる
  • プレイヤーが引き終えたら、その後ディーラーは、自分の手札が17以上になるまで引き続ける
  • プレイヤーとディーラーが引き終えたら勝負。より21に近い方の勝ち
  • JとQとKは10として扱う
  • Aはとりあえず「1」としてだけ扱う。「11」にはしない
  • ダブルダウンなし、スプリットなし、サレンダーなし、その他特殊そうなルールなし


悩んだところ

  • 用意するクラスについて
  • インスタンスメソッドまたはクラスメソッドどっちにするか
  • データをどこで保持させるべきか(山札や手札など)


用意したクラス

次の6つのクラスを作成しました。
それぞれのクラスの役割は次になります。

# カードNumberとダイヤやハートなどのマークをそれぞれ配列で用意するクラス
class Card

# 用意したカードから山札を作成してシャッフルするクラス、Cardクラスを継承している
class Deck < Card

# プレイヤーとディーラーのスーパークラスでそれぞれの共通処理をするクラス
class PlayerBase

# プレイヤー専用の処理を作成している、PlayerBaseを継承している
class Player < PlayerBase

# ディーラー専用の処理を作成している、PlayerBaseを継承している
class Dealer < PlayerBase

# ゲームの進行に必要な処理や手札や山札などのデータを保持するクラス
class BlackJack

プレイヤーとディーラーで共通する処理が多かったためPlayerBaseというスーパークラスを作成することでメソッドの使い回しができるようになり楽に進められた気がします。


インススタンスメソッドまたはクラスメソッドにすべきか

これについてもめちゃくちゃ悩んで「ruby インスタンスメソッド クラスメソッド 違い」みたいなことを何回もググりました。

結果的に「基本的にはインスタンスメソッドで作成して、ちょっとインスタンスメソッドだと使いづらいかな?と感じたところはクラスメソッドにしよう」という少し抽象的な判断で決めています。


クラスメソッド

class Card
  class << self
    def numbers
      no = [1, 2, 3, 4, 5, 6, 7 ,8 ,9 ,10, 11, 12, 13]
    end

    def suit
      suit = ["ダイヤ", "ハート", "クラブ", "スペード"]
    end
  end
end

インスタンスメソッド

class PlayerBase

  def create_hand(deck)
    deck.pop(2)
  end
# .
# .
# .

手札や山札などのデータをどのクラスで保持するべきか

データの保管場所が一番悩んだところです。当初は「プレイヤーの手札だからPlayerクラスで、ディーラーの手札だからDealerクラスで管理しよう。山札に関しては共通のものだからPlayerBaseクラスだよね」と思ったのですが、進めていく途中でインスタンスを生成した際に2つの山札が作られてしまうなど色々面倒なことが多かったので、BlackJackクラスで全てのデータを保持する方針で決まりました。


詳細なコード説明

詳細なコードの説明はまた別で記事にしようと思います。今回は感想や苦労したところを書いてみました。


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

dockerとmysqlでrails環境を構築した

dockerにrails環境を作った
https://qiita.com/NA_simple/items/5e7f95ae58eec5d20e1f

途中なぜか上手くいかないと思ったら、mysql-clientsがインストールできなくなっているらしい。書き換え方は下のURLを参考に。
https://qiita.com/yagi_eng/items/1368fb2a234629a0c8e7

調子に乗ってすすめていると、またハマる。

terminal
$ docker-compose run web rails db:create
Starting postgress_db ... done
Could not find activesupport-5.2.4.3 in any of the sources
Run `bundle install` to install missing gems.

なぜだ、、、と思ったらrubyのバージョンが違う??
rbenvでバージョンを探しても、2.7.1が見つからず。
rbenv古い事に気づき、アップデート

rbenvをアップデート
https://qiita.com/pugiemonn/items/f277440ec260b8d4fa6a

からのgemも古い事に気づき、

terminal
$ gem update

まだいかない、、、
bundler updateを行う。

ここらへんで絶望したので時間を置く。

一旦整理して、違うサイトの最初から手順をパクる。

https://toranoana-lab.hatenablog.com/entry/2020/06/05/173658

なんと止まること無くdockerの起動、localhostにアクセス可能に!!!
よっしゃ!!!!

localhost_3000
Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

なんで :D
ググるとファイルが無いらしいからsudo touchで無理やり作ってる記事がちらほら。
でもファイルあるしなあ、と思いつつ削除&作成

同じエラー。

???と思い、ここでmysql立ち上げていないことに気づく
これだ!!!と思って

terminal
mysql.server start

を実行するも、起動しない。
ググるとどうやら

terminal
sudo rm mysql.sock
brew uninstall mysql
brew install mysql

と、sockファイルを消してからmysqlをアンインストール→インストールで治るらしい。
mysql.sockのパスは前のエラーで判明していたので、それを消してから

terminal
mysql.server start

…通った!!!!

これは行ったか…?

image.png

ヤッターーーーーーー!!!!!!!!!

docker理解してから構築したほうが早かったと思います。
勉強し直しましょう。自分。

なにはともあれ動いてよかった

最終的な各ファイルの中身↓

gemfile
source 'https://rubygems.org'
gem 'rails', '~>6'
docker-compose.yml
version: '3'
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD:
    ports:
      - '3306:3306'
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
    stdin_open: true
    tty: true
    command: bundle exec rails server -b 0.0.0.0
volumes:
  mysql-data:
    driver: local
Dockerfile
FROM ruby:2.7
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
    && apt-get update -qq \
    && apt-get install -y nodejs yarn \
    && mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dockerとmysqlでrails環境を構築したけどドハマリした

dockerにrails環境を作った
https://qiita.com/NA_simple/items/5e7f95ae58eec5d20e1f

途中なぜか上手くいかないと思ったら、mysql-clientsがインストールできなくなっているらしい。書き換え方は下のURLを参考に。
https://qiita.com/yagi_eng/items/1368fb2a234629a0c8e7

調子に乗ってすすめていると、またハマる。

terminal
$ docker-compose run web rails db:create
Starting postgress_db ... done
Could not find activesupport-5.2.4.3 in any of the sources
Run `bundle install` to install missing gems.

なぜだ、、、と思ったらrubyのバージョンが違う??
rbenvでバージョンを探しても、2.7.1が見つからず。
rbenv古い事に気づき、アップデート

rbenvをアップデート
https://qiita.com/pugiemonn/items/f277440ec260b8d4fa6a

からのgemも古い事に気づき、

terminal
$ gem update

まだいかない、、、
bundler updateを行う。

ここらへんで絶望したので時間を置く。

一旦整理して、違うサイトの最初から手順をパクる。

https://toranoana-lab.hatenablog.com/entry/2020/06/05/173658

なんと止まること無くdockerの起動、localhostにアクセス可能に!!!
よっしゃ!!!!

localhost_3000
Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

なんで :D
ググるとファイルが無いらしいからsudo touchで無理やり作ってる記事がちらほら。
でもファイルあるしなあ、と思いつつ削除&作成

同じエラー。

???と思い、ここでmysql立ち上げていないことに気づく
これだ!!!と思って

terminal
mysql.server start

を実行するも、起動しない。
ググるとどうやら

terminal
sudo rm mysql.sock
brew uninstall mysql
brew install mysql

と、sockファイルを消してからmysqlをアンインストール→インストールで治るらしい。
mysql.sockのパスは前のエラーで判明していたので、それを消してから

terminal
mysql.server start

…通った!!!!

これは行ったか…?

image.png

ヤッターーーーーーー!!!!!!!!!

docker理解してから構築したほうが早かったと思います。
勉強し直しましょう。自分。

なにはともあれ動いてよかった

最終的な各ファイルの中身↓

gemfile
source 'https://rubygems.org'
gem 'rails', '~>6'
docker-compose.yml
version: '3'
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD:
    ports:
      - '3306:3306'
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
    stdin_open: true
    tty: true
    command: bundle exec rails server -b 0.0.0.0
volumes:
  mysql-data:
    driver: local
Dockerfile
FROM ruby:2.7
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
    && apt-get update -qq \
    && apt-get install -y nodejs yarn \
    && mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む