- 投稿日:2019-02-27T23:53:20+09:00
payjp.jsの導入方法<Rails>
Payjp.jsを導入するにあたって、理解するのに時間がかかってしまったので(なにせ記事が少ない!)今後導入される方の時間短縮のために、ここに残しておきます。
Pay.jpとは
Pay.jpのサービスを利用することでクレジットカード決済機能を簡単に導入することができます。
gem'payjp'をインストールすることで使用可能に。
利用方法については大きく2通りあります。「チェックアウト」
公式で用意されたスクリプトタグをHTMLに記述すれば、デザインされた決済フォーム、カード情報のバリデーション、カード情報のトークン化を行うフォームを生成することができます。こちらを使用すれば、ささっとすぐに実装可能です。
ビューについてこだわりがないという方であればこちらで十分だと思います。「カスタムフォーム(payjp.js)」
payjp.jsを使うことで、好きなデザインや挙動でトークン化を行うフォームを組み込めます。
ここで注意をしたいのが、payjp.jsはトークン化に特化したライブラリであり、これを使用したからといって決済ができる訳ではありません。
クレジットカード情報入力ページは自分の好みのビューにしたい!という方はこちらです。
今回はこのpayjp.jsで実装していくための説明となります。トークンとは
クレジットカード情報を自身のデータベース上で管理するのはセキュリティの観点で好ましくないので、顧客が入力したカード情報をトークン化しなくてはいけません。
顧客情報を持った情報の塊のことをトークンを呼びます。IDを使用することで顧客情報を呼び出すことができます。トークン化については公式を参考にしてください。
https://pay.jp/docs/cardtokenまたトークンは1度のみの使用しか出来ないので、2度以上使いたい場合は顧客カードを作成してカード情報をそこに紐付けることが必要です。顧客カードへの紐付けというのは後ほど説明します。
いざ導入!
ここからは実際のコードを交えて説明します。
まずは、顧客がカード情報を入力するためのフォームを作成します。qiita.html.haml%h2 クレジットカード情報入力 = form_tag("/users/purchase", method: "PATCH", id: "charge-form") do %label カード番号 %input{maxlength: "16", type: "text", class: "number", id: "payment_card_no" name: "number", placeholder: "カード番号"} %label CVC %input{type: "text", class: "cvc", id: "payment_card_security_code", name: "cvc", maxlength: "3", placeholder: "CVC"} %label 有効期限 %input{type: "text", class: "exp_month", id: "payment_card_expire_mm", name: "exp_month", maxlength: "2", placeholder: "月"} %input{type: "text", class: "exp_year", name: "exp_year", maxlength: "4", placeholder: "年"}Pay.jpにトークンを作成してもらうには、公式で用意されているフォームを活用しましょう。
(本記事では所々書き換えています。)qiita.js$(document).on('turbolinks:load', function() { var form = $("#charge-form"); Payjp.setPublicKey('pk_test_0383a1b8f91e8a6e3ea0e2a9'); $("#charge-form").on("click", "#submit-button", function(e) { e.preventDefault(); form.find("input[type=submit]").prop("disabled", true); var card = { number: parseInt($("#payment_card_no").val()), cvc: parseInt($("#payment_card_security_code").val()), exp_month: parseInt($("#payment_card_expire_mm").val()), exp_year: parseInt($("#payment_card_expire_yy").val()) }; Payjp.createToken(card, function(s, response) { if (response.error) { alert("error") form.find('button').prop('disabled', false); } else { $(".number").removeAttr("name"); $(".cvc").removeAttr("name"); $(".exp_month").removeAttr("name"); $(".exp_year").removeAttr("name"); var token = response.id; $("#charge-form").append($('<input type="hidden" name="payjp_token" class="payjp-token" />').val(token)); $("#charge-form").get(0).submit(); } }); }); });顧客がsubmitボタンを押した瞬間にこのjsファイルが反応するようにしてあげます。
このフォームの中のPayjp.createTokenで作成されたトークンIDがresponse.idに返ってきます。
後はif文で分岐をさせてあげて、最後にe.preventDefault();によって停止させていたsubmitを実行させます。setPublicKeyにはpay.jpにログインした際の管理画面で確認できるAPIキーを入れてください。
APIキーはconfig/initializers/setting.rbを作成してその中に変数を定義して入れてあげるとrbファイル間で共有できるので、利便性・セキュア面で良いですね!
(記載しているAPIキーは公式のものなのでご心配なく)qiita.html.haml%input.payjp-token{ type: "hidden", name: "payjpToken", value: "" }そしてHTML上で上記の記述をして、jsファイルで作成したトークンIDが空のvalueに入ることで、paramsとしてコントローラーへ投げることができます。
inputタグをhiddenにすることでビュー上には反映されていません。
以上でトークン作成と、トークンIDをparamsとしてコントローラーへ渡せるようになりました。顧客カードとの紐付け
カード情報非通過対応により2018年6月頃からサーバーサイドからのカード情報のPOSTリクエストができなくなりました。
そのためトークン化はビュー上で行いましたが、顧客作成はコントローラーで行います。users.controller.rbPayjp.api_key = PAYJP_SECRET_KEY customer = Payjp::Customer.create(card: params[:payjp_token])Payjp::Customer.createで顧客カードが作成されたので、その中のcardキーのバリューとしてトークンIDを指定してあげます。
カード情報を取り出す際にもこのIDは使用するので、私はUserテーブルのカラムに入れてあげました。他にも支払い等の詳しいやり方は公式のリファレンスを参照ください。
https://pay.jp/docs/api/#introduction終わりに
私自身まだエンジニアとしてプログラミングを学んで3ヶ月満たないので、ツッコミどころがあるかもしれません。
こういったやり方もあるんだな〜という、一つの実装方法として参考程度に見てください。
他に効率の良いやり方があるよ!この記述無駄じゃない?と気づいた方がいらしたらご指摘ください(_ _)
- 投稿日:2019-02-27T23:44:25+09:00
RubyでArrayから重複した値を取り出す方法
こんにちは、とくめいチャットサービス「ネコチャ」運営者のアカネヤ(@ToshioAkaneya)です。
今回はRubyでArrayから重複した値を取り出す方法を紹介します。RubyでArrayから重複した値を取り出す方法
a = [1, 1, 2, 3, 4, 4, 5] a.select{|v| a.count(v) > 1}.uniq # [1, 4]selectとuniqを組み合わせるのがポイントです。
以上になります。はてなブックマーク・Pocketはこちらから
- 投稿日:2019-02-27T23:27:57+09:00
Ruby on RailsのIDEをどれにしようかなぁと軽く調べた
Cloud9で開発するの、地味に疲れる
今までAWSのクラウド開発環境であるCloud9を利用してきたわけですが、それをやめようかなぁと思い始めています。
理由は単純で、Command+W 暴発問題があるから。ファイルを閉じたくてうっかりCommand+Wを叩くと、Safariのタブが閉じてしまい、開発環境そのものが閉じてしまいます。
そんなの気をつけれてば済む話じゃんって言われるとそこまでなのですが、ロジックを書くことに集中しているといつの間にか頭から飛んでしまうのだ…つらぁ…。Macに入れるIDEを考え始める
元々Java屋でEclipseを使っていたので、とりあえずイマドキなIDEを入れればいいかって考えに至り、探してみることに。
パッと検索した限り、以下の3つが良いっぽい。
- Eclipse (+RadRails)
- RubyMine
- VS Code
Eclipse (+RadRails)
Javaをやってる方で(使用有無はさておき、)Eclipseを知らない方はいないでしょう。
Pleiadesでフル日本語化されたAll in Oneを落としてくればささっと開発に取りかかれる簡単さがあります。
元々使っていたところにプラグインを入れれば使えるというのは学習コストをかなり抑えられそうです。良さそうなところ:
- 学習コスト低め
ちょっとなーってところ:
- Javaやってた時と代わり映えしないので飽きが…
RubyMine
使いやすいIDEを出してくることに定評のある(と伝え聞いている)JetBrains社製IDE。
ちょっと前までデファクトスタンダードだったとどこかで見ました。良さそうなところ:
- 情報量が豊富、何かあっても検索してどうにかなりそう
ちょっとなーってところ:
- 有料か…
VS Code
プロプライエタリ製品の名称だったVisual Studioの名を冠して登場した無料版だと思ってたら、あっという間にWeb業界へ浸透している感のあるIDE。
色んなところから「VS Codeはいいぞ」と聞こえてきます。良さそうなところ:
- 今超絶ホット。使ってるだけで最前線感バリバリ。
ちょっとなーってところ:
- まだ情報が蓄積されていなさそう(& バージョン依存のトラブルシュートが引っかかる)
とりあえずVS Codeからかじってみようかなー
ローカルでRailsの開発をするのはこれからなので、切り替えの手間を考えなくていい分、新しいやつをやってみようという安直な理由でVS Code。
ダメだったらEclipseに転ぶことにしよう。IDE?テキストエディタ使えよ
わかる、わかるよ、その方が軽いし環境選ばないしハッカーっぽいしで良いってのはわかるよ。
でもこちとらRailsに関してはホビープログラマ、楽なやり方で開発したいのだ…本当にすまない…。おすすめあったら教えてくださいなんでもしますから!
「(なんでもするとは言ってない)」の文脈。
「RailsでこのIDEいいよ!」って意見、お待ちしております。
- 投稿日:2019-02-27T22:57:35+09:00
Ruby|RSpecでメソッド呼び出しのテスト(ブロック付き)
いつものように「これ、誰がつかうん?」という小ネタです。
たまーにRSpecでメソッドってちゃんと呼び出されてるよね?というテストを書くと思います。
with
を使うと引数に狙った値が渡っているかを検証できますが、ブロック付きはどうするか調べたのでそのメモです。先に言ってしまうと検証は「&手続きオブジェクト」の場合のみ出来ます。
前段階
検証に使うプログラムは
Deck
クラスを利用します。これは何枚かのカードを持っていてシャッフルしたり偶数と奇数を分けてくれます。今回の検証はDeck#split_even_numbers
でEnumerable#partition
にブロックを渡していますが、それをRSpecでテストする方法です。本来であればメソッドを呼び出した結果を検証すべきですので、メソッド呼び出しのみのテストはいいと言ってはいません。用法用量を守るのはお兄さんとのお約束だぞ
class Deck def initialize(cards) @cards = cards end def shuffle @cards.shuffle end def split_even_numbers @cards.partition { |card| card.even? } end def split_odd_numbers @cards.partition(&:odd?) end endRSpec
ブロックの検証
with
にブロックを指定すると{ |card| card.even? }
を受け取ることができるようです。&block
としているのでProc
クラスのオブジェクトになっています。
Proc
クラスのオブジェクトをつくって比較すればいいか!と思っていたのですが、メソッドに渡してるブロックとは異なるようでテストはうまくいきませんでしたdescribe Deck do describe '#split_even_numbers' do let(:deck) { Deck.new(cards) } let(:cards) { [5, 4, 3, 2, 1] } it do expect(cards).to receive(:partition).with(no_args) do |&block| # この検証はうまくいかない expect(block).to eq(Proc.new{ |card| card.even? }) end deck.split_even_numbers end end end&手続きオブジェクト
「&手続きオブジェクト」といっていますが検証したい
Proc
クラスのオブジェクトとテストでの期待値が同じオブジェクトであれば今回のやりたいことは実現することが出来ます。
:odd?.to_proc
は常に同じオブジェクトを返すので検証で利用して期待した結果を得ることが出来ます。describe Deck do describe '#split_odd_numbers' do let(:deck) { Deck.new(cards) } let(:cards) { [5, 4, 3, 2, 1] } it do expect(cards).to receive(:partition).with(no_args) do |&block| expect(block).to eq(:odd?.to_proc) end deck.split_odd_numbers end end end
- 投稿日:2019-02-27T21:15:01+09:00
RSpec
- 投稿日:2019-02-27T21:14:42+09:00
Ruby コメント、変数、定数、リテラル
動作環境はMacとなります。
主に自分の勉強用メモとして残しています。コメント
特定の処理を向こうにしたいときや、メモを残したい時にしようする。
comment.rb# puts "Hello, World!" # 処理を向こうにしたいときは、プログラムの先頭に#をつける。改行までがコメント扱いとなる。
$ruby comment.rb $ #コメントアウトしているため出力されない。複数行コメントアウトしたい場合
comment.rb=begin puts "Hello, World!" puts "Hello, World!" puts "Hello, World!" =end # =beginから=endの間の処理がコメントアウトされる$ruby comment.rb $ #コメントアウトしているため出力されない。実務上複数行に渡る場合でも、行頭に一行ごとに#をつけて実行されることが多い。
コメントを解除したい場合
commandキー+/キーで解除可能 ※複数行の場合、解除したい行をまとめて選択
メモとして残したい場合でも使用可能。
comment.rb# This is a comment.
$ruby comment.rb $ #コメントアウトしているため出力されない。変数
扱うデータに名前をつけて、一時的に利用できるようにしたもの
変数名 = 式
例
name = "suzuki"
fruit = "apple"
x = 10#変数sに Helloという文字列を代入 irb(main):001:0> s = "Hello!" => "Hello!" irb(main):002:0> puts s Hello! => nil #出力 #型の宣言は不要 irb(main):001:0> char s = "Hello!" irb(main):001:0> var s = "Hello!"数値の代入
irb(main):003:0> n = 2 * 10 #値20を変数nに代入 => 20 irb(main):004:0> puts n #変数nの出力 20 => nil変数には何かしら代入しないといけない
#変数に何も値が入っていないとエラーになる。 irb(main):005:0> i NameError: undefined local variable or method `i' for main:Object from (irb):5 from /Users/toripurug884/.rbenv/versions/2.4.1/bin/irb:11:in `<main>' #中身を入れて再出力 irb(main):006:0> i = nil => nil irb(main):007:0> i => nil #実行された!変数名の命名規則
アルファベットの小文字かアンダースコアで始まる。
それに続けてアルファベット、数字、アンダースコアで記述する。irb(main):014:0> price = 100 => 100 #以下原則しないほうが良い命名規則 irb(main):015:0> price1 = 100 #数字をつける => 100 irb(main):016:0> _price = 100 #アンダースコアから始まる変数 => 100 #数字から始まる変数名はエラーになる。 irb(main):017:0> 1price = 100 SyntaxError: (irb):17: syntax error, unexpected tIDENTIFIER, expecting end-of-input 1price = 100#2語以上になる場合は原則_で繋げる。 #キャメルケース(単語と単語の間の文字を大文字にする)は原則使わない。 irb(main):018:0> price_cost = 100 => 100 irb(main):019:0> priceCost = 100 => 100 irb(main):020:0>よくない変数の使い方
エラーではないが、メンテナンス性が悪くなるので推奨されない変数の使い方
同じ変数に違う型を代入
irb(main):022:0> x = "abc" #文字列を代入 => "abc" irb(main):023:0> x = 123 #数字を代入 => 123 irb(main):024:0> x = "def" #文字列を代入 => "def" irb(main):025:0> x = [1,2,3] #配列を代入 => [1, 2, 3] #同じ値を入れてしまうとわかりにくくなる複数の変数に代入
irb(main):026:0> x,y = 1,2 => [1, 2] irb(main):027:0> x => 1 irb(main):028:0> y => 2 #あまりメリットがない。irb(main):029:0> a = b = 10 => 10 irb(main):030:0> a => 10 irb(main):031:0> b => 10 #あえて使う必要はない定数
あまり変化しない値を扱う時に使う
例)消費税、円周率、手数料など定数は原則全て大文字
irb(main):032:0> TAX = 1.08 #定数TAXに値を代入 => 1.08 irb(main):036:0> puts TAX 1.08 => nil irb(main):037:0> TAX_RATE = 1.08 #2語以上の定数の場合単語は_で繋ぐ => 1.08 irb(main):038:0> puts TAX_RATE 1.08 => nilコードのメンテナンスを保つため、定数には再代入は原則しない。
irb(main):039:0> TAX_RATE = 1.1 #一度定義した定数に再代入 #警告が出るが、再代入されてしまうので注意 (irb):39: warning: already initialized constant TAX_RATE (irb):37: warning: previous definition of TAX_RATE was here => 1.1 #再代入後の値が出力される irb(main):040:0> puts TAX_RATE 1.1 => nilリテラルとは
数値の123や文字列の"Hello! World"など
Rubyのプログラムの中に直接記述できる値ソースコードに書いたもの
- 投稿日:2019-02-27T19:55:58+09:00
Rubyでインターフェイスの実装を強制させたい
Rubyでインターフェイスの実装を強制させる方法を考えたいと思います。
Rubyは動的型付け言語なので、少なくともポリモーフィズムを実現するのに、Javaのようなインターフェイスを定義する必要はありません。しかし、「ソフトウェアのモジュールを疎結合に保つためには、モジュールは他のモジュールの実装ではなくインターフェイスに依存すべきである」という原則があります。そのような観点からは、インターフェイスを明示でき、それを利用するクラスがインターフェイスの規約をみたしているかチェックできると便利そうです(本当か?)。
以下に紹介する方法では一部メタプログラミングを用いていますが、一般的にメタプログラミングはプログラムの保守性や可読性を下げるため、多用すべきではありません。また、本稿の内容によって生じた問題に関して、筆者は責任を負いかねます。
とりあえず、メソッド呼び出し時に例外にしてみる
もっとも簡単な方法は、実装されていないメソッドが呼び出されたときに、例外とする方法でしょう。
sample.rbmodule SomeInterface def some_method raise NotImplementedError, "Method \"#{__method__}\" is not implemented in class \"#{self.class.name}\"." end end class SomeClass include SomeInterface end obj = SomeClass.new obj.some_method # => Method "some_method" is not implemented in class "SomeClass". (NotImplementedError)この例では、SomeClassでsome_methodがオーバーライドされていなければ、親モジュールのsome_methodが呼び出されて、NotImplementedErrorが投げられます。
ただ、これだと実際にsome_methodが呼び出されないと、メソッドの未実装が検知できないので微妙です(そもそもこんなことしなくても、定義されていないメソッドを呼び出せばNoMethodErrorが投げられますし)。やはり、クラス定義時にメソッドの未実装を検知できるようにしたいものです。
メタプログラミングでなんとかしてみる
というわけで目標としては、attr_accessorやincludeのように、モジュール定義内で以下のように書くことで、メソッド未定義検知に必要な処理をするようにしたいです。
module SomeInterface interface :some_method end class SomeClass implements SomeInterface end # => Method "some_method" is not implemented in class "SomeClass". (NotImplementedError)以下、これを実装するために必要な事項をかんたんに復習します。
モンキーパッチ
Rubyでは、実行時にクラスやモジュールの定義を、変更したり拡張したりできます。
たとえば、以下の例では組み込みのクラスHashに、Structへ変換する処理を追加しています。hash_to_struct.rbclass Hash # 注意: キーと値が同一でも==による比較はfalseになる def to_struct new_keys = self.keys.map(&:to_sym) new_values = self.values Struct.new(*new_keys).new(*new_values) end end h = {'name': 'Taro', 'age': 22} s = h.to_struct # => #<struct name="Taro", age=22>なお、クラスやモジュールの上書きは、グローバルに影響するので注意が必要です。今回は簡単のためにこの方法で説明しますが、特別な理由がなければRefinementsを用いるのが好ましいです。Refinementsを使った場合、上書きの影響範囲はusingを用いたスコープ内に限定されます。
クラスもモジュールもオブジェクト
Rubyistには今さらな話ですが、Rubyではクラスやモジュールもオブジェクトです。たとえば、クラスは「Classクラス」のインスタンスであり、クラスFooの宣言は「定数FooにClassのインスタンスを代入している」に過ぎません。なので、以下の2つのコードは同じことをやっています。
class Foo end f = Foo.newFoo = Class.new f = Foo.newattr_accessorやincludeはメソッド
これもRubyistには今さらな話ですが、attr_accessorやincludeなどはいかにも言語の予約語みたいな見た目ですが、単なるメソッドです。たとえば、attr_accessorはModuleクラスのインスタンスメソッドであり、引数で受け取ったシンボルと同名のインスタンス変数を返すメソッドを動的に定義します。
いざ、実装
上のコードを動作させるには、attr_accessorやincludeと同じくModuleクラスのインスタンスメソッドとして、「interface(*syms)」と「implements(*consts)」を実装すればいいようです。
あとは、実装させるべきメソッド名と、実際に実装されているメソッド名が取得できれば、目的のものが作れそうです。実装させるべきメソッド名の一覧(上述の例におけるsome_method)は、具体的なモジュール(上述の例におけるSomeInterface)が有します。
具体的なモジュールは、Moduleクラスから見ると自身のインスタンスなので、実装されるべきメソッド名は、インスタンス変数または特異メソッドから取得できそうです。
インスタンス変数は外部からは直接参照できないので、今回はinterfaceメソッド呼び出し時に、実装されるべきメソッド一覧を返す特異メソッドinterface_methodsを動的に定義するようにします。implementsの方は、実装すべきメソッド名がすべて、実際に実装されているメソッド名一覧に含まれているかチェックします。その後、includeを呼び出して、クラスの継承関係にインターフェイスモジュールを追加します。
というわけで、できたコードが以下です。
interface.rbclass Module def interface(*syms) define_singleton_method(:interface_methods) { syms } end def implements(*consts) methods_should_be_implemented = consts.map(&:interface_methods).reduce(:+).uniq methods_actually_implemented = self.instance_methods methods_should_be_implemented.each do |method| unless methods_actually_implemented.include?(method) error_str = "Method \"#{method}\" is not implemented in class \"#{self.name}\"." raise NotImplementedError, error_str end end include *consts end end実際に実行してみると、どうやら意図どおりに動くようです。
sample3.rbrequire './interface.rb' module Interface1 interface :method_a, :method_b end module Interface2 interface :method_c end class SomeClass def method_a end def method_c end implements Interface1, Interface2 # 仕様上どうしても、メソッド定義のあとに書かないといけない。 end # => NoImplementedError: Method "method_b" is not implemented in class "SomeClass".以上です。
今回は、最低限の機能だけ果たしそうなものを書いてみましたが、いろいろ懸念点や改善点がありそうです。たとえば、
- 他のライブラリと名前が衝突しないか
- パフォーマンスが悪化しないか
- 引数の個数まで含めてチェックしたい場合はどうするか
- implementsはメソッド定義の前に書きたい
等々。
ご意見・ご感想等ございましたら、コメントいただけると幸いです。
参考文献
有名すぎて今さらですが、非常に良い本です。「メタプログラミング」という語から、何やらマニアックなテクニックばかり載っているように思われるかも知れませんが、Rubyのオブジェクトモデルやブロックの説明などは、多くのRubyプログラマーの役に立つと思います。
- 投稿日:2019-02-27T17:03:56+09:00
Rails APIモードで始めるGraphQL
はじめに
本記事は、社内でGraphQL入門勉強会を実施した際の内容です。
本記事での対象は、
- graphql-ruby/graphiql-railsの設定
- Query(データ取得)
- Mutation(データ更新)
になります。
内部的な仕組みなど、細かい内容は出てきません。
とりあえず動かして雰囲気を掴むことを目的としています。環境
バージョン Ruby 2.6.0 Rails 5.2.2 graphql-ruby 1.6.0 graphiql-rails 1.9.3 GraphQLとは
GraphQLは、2012年にFacebookによって開発されたデータクエリ言語および仕様です。
RESTベースのアーキテクチャに代わるものを提供します。
GraphQLはGitHubのAPI(GraphQL API v4
)でも採用されています。GraphQL API v4 (https://developer.github.com/v4/)
GraphQLでは、クエリ言語を使用してデータの取得や更新を行います。
- query (データ取得系)
- mutation (データ更新系)
- subscription (イベント通知) ※本記事では対象外
Preparation
graphql-ruby
導入前のサンプルプロジェクトを用意しています。
本記事では、このサンプルプロジェクトにgraphql-ruby
を導入していきます。git clone git@github.com:dkawabata/graphql-ruby-sample.git cd graphql-ruby-sample bundle install --path vendor/bundle bundle exec rails db:migrate bundle exec rails db:seedサンプルプロジェクトは、
users
テーブル/posts
テーブル/comments
テーブルの3テーブルで構成しています。Setup
GraphQL Rubyのインストール
Gemfileを編集します。
Gemfile+ gem 'graphql' group :development do + gem 'graphiql-rails' end
graphiql
というブラウザでクエリを実行して、GraphQLの動作確認ができるツールのRails版がgraphiql-rails
です。
GraphQLでの開発に必須ではありませんが、今回はgraphiql
で動作確認するため、追加しています。
Gemfileを編集したら、インストールします。bundle install bundle exec rails g graphql:installAPIモードでは無い場合、
graphiql-rails
の設定(Gemfileへの追加や、以降で行うroutes.rb
の編集)は不要です。
rails g graphql:install
のタイミングで自動的に設定されますが、APIモードの場合はSkipされるようです。Graphiqlの設定
routes.rbの編集
config/routes.rbif Rails.env.development? mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: '/graphql' endapplication.rbの編集
Rails APIモードでgraphiqlを利用するために、
application.rb
のrequire "sprockets/railtie"
のコメントアウトを外します。
graphiql-rails
のREADMEにも記載されています。https://github.com/rmosolgo/graphiql-rails#note-on-api-mode
If you're using Rails 5 in "API mode", you'll also need to add require "sprockets/railtie" to your application.rb.
config/application.rb- # require "sprockets/railtie" + require "sprockets/railtie"サンプルクエリの実行
ここまでで一通りの準備は完了です。
rails s
でサーバを起動して、http://localhost:3000/graphiql
にアクセスしてみましょう。
graphiql
の画面が表示されます。
左側にクエリを入力して実行すると、右側に結果が表示されます。
下図のように、testField
というクエリを実行して、"Hello World!"
が表示されれば成功ですQuery
Queryはデータを取得するために定義します。
Queryを追加する前に、各モデルに対応したTypeを定義しましょう。型の定義
user
/post
/comment
の各モデルに対応したTypeを定義します。app/graphql/types/user_type.rbmodule Types class UserType < Types::BaseObject field :id, Int, null: false field :name, String, null: false field :created_at, GraphQL::Types::ISO8601DateTime, null: false field :updated_at, GraphQL::Types::ISO8601DateTime, null: false field :posts, [Types::PostType], null: false field :comments, [Types::CommentType], null: false end endapp/graphql/types/post_type.rbmodule Types class PostType < Types::BaseObject field :id, Int, null: false field :user, Types::UserType, null: false field :subject, String, null: false field :body, String, null: false field :created_at, GraphQL::Types::ISO8601DateTime, null: false field :updated_at, GraphQL::Types::ISO8601DateTime, null: false field :comments, [Types::CommentType], null: false end endapp/graphql/types/comment_type.rbmodule Types class CommentType < Types::BaseObject field :id, Int, null: false field :post, Types::PostType, null: false field :user, Types::UserType, null: false field :body, String, null: false field :created_at, GraphQL::Types::ISO8601DateTime, null: false field :updated_at, GraphQL::Types::ISO8601DateTime, null: false end end
graphql-ruby
の場合、各Typeに定義しているfield
と同名のメソッド/attributeがモデルに定義されている場合、自動的にデータを取得できます。
field
で型を指定しますが、標準で用意されている型には以下のようなものがあります。
- String
- Int
- Float
- Boolean
- ID
- ISO8601DateTime
※
[]
で囲うと配列になります。Queryの追加
それでは、実際にデータを取得するためのQueryを追加しましょう。
特定のid
を持つUser
を1件返すQueryと、全てのUser
を返すQueryを定義してみましょう。
QueryはTypes::QueryType
に定義を追加します。app/graphql/types/query_type.rbmodule Types class QueryType < Types::BaseObject - # Add root-level fields here. - # They will be entry points for queries on your schema. - # TODO: remove me - field :test_field, String, null: false, - description: "An example field added by the generator" - def test_field - "Hello World!" - end + field :user, Types::UserType, null: false do + description 'ユーザ情報を1件取得する' + argument :id, Int, required: true, description: 'ユーザID' + end + def user(id:) + User.find(id) + end + field :users, [Types::UserType], null: false, description: 'ユーザ情報を全件取得する' + def users + User.all + end end endQueryの実行
実際に
graphiql
からQueryを実行してみましょう。
まずは、idを指定して1件取得してみます。query { user(id: 1) { id name } }次に、登録されているuser全てを取得します。
query { users { id name posts { id subject body } } }上記のQueryでは、各ユーザの
id
とname
、そのユーザが投稿したPost
を全て取得しています。
Post
が不要な場合は、id
とname
だけ取得するということも可能です。
Queryを実行する側が、必要なデータを選択して取得することができる点は、GraphQLのいい点ですね!Mutation
次はデータを登録するためのMutationを定義しましょう。
特定のPostにコメントを追加するMutationを定義します。Mutationクラスの作成
app/graphql/mutations/base_mutation.rbmodule Mutations class BaseMutation < GraphQL::Schema::RelayClassicMutation end endapp/graphql/mutations/create_comment.rbmodule Mutations class CreateComment < Mutations::BaseMutation argument :user_id, Int, required: true argument :post_id, Int, required: true argument :body, String, required: true field :comment, Types::CommentType, null: true field :errors, [String], null: false def resolve(user_id:, post_id:, body:) comment = Comment.new(user_id: user_id, post_id: post_id, body: body) if comment.save { comment: comment, errors: [] } else { comment: nil, errors: comment.errors.full_messages } end end end endMutation定義の追加
先ほど作成した
CreateComment
を使えるようにするため、Mutationの定義を追加します。app/graphql/types/mutation_type.rbmodule Types class MutationType < Types::BaseObject field :create_comment, mutation: Mutations::CreateComment end endMutationの実行
では、
graphiql
からMutationを実行してみましょう。mutation { createComment(input: { userId: 1 postId: 1 body: "コメント追加" }) { comment { id body } } }{ comment { id body } }の部分は、登録した
comment
の内容を返却するための記述です。まとめ
ざっくりですが、RailsのプロジェクトにgraphQLを導入して、
Query
とMutation
を定義する部分を行いました。
一部古い情報もありますが、graphql-rubyのガイド( https://graphql-ruby.org/guides )を見ると、色々載っているので参考になります。参考URL
https://github.com/rmosolgo/graphql-ruby
https://github.com/rmosolgo/graphiql-rails
https://graphql-ruby.org/guides
- 投稿日:2019-02-27T16:51:13+09:00
ActiveStorageのファイルをアップロードするタイミングについて
ActiveStorageを使うにあたって、アップロードできたかどうかを調べるために、アップロードをどの処理で実施しているか調べたので、覚書として。(アップロードできたかどうかについては、これから改めて調査)
アプリケーションコードに近い方から遠い方へ順に追っていきます。
前提条件
- activestorage のバージョンは5.2.2を使用する
- 属性の定義に
has_one_attached
を使用する- attachするファイルはHTMLからアップロードしたファイルを使用する
- ファイル保存サービスはローカル(S3やGCSではない)を使用する
ActiveStorage::Attached::Macros
https://github.com/rails/rails/blob/v5.2.2/activestorage/lib/active_storage/attached/macros.rb#L37
ActiveRecord::Baseを継承するモデルクラス内に定義した
has_one_attached
の属性に対して、値が代入された時にattach
メソッドを実行します。ActiveStorage::Attached::One
https://github.com/rails/rails/blob/v5.2.2/activestorage/lib/active_storage/attached/one.rb#L24
ActiveStorage::Attached::One.attach
メソッドがcreate_blob_from
を実行します。ActiveStorage::Attached
https://github.com/rails/rails/blob/v5.2.2/activestorage/lib/active_storage/attached.rb#L23
ActiveStorage::Attached. create_blob_from
メソッドで、attachableのクラスによって処理を分けています。
今回はアップロードされたファイルを属性に指定したので、
ActiveStorage::Blob.create_after_upload!
を実行します。ActiveStorage::Blob
https://github.com/rails/rails/blob/v5.2.2/activestorage/app/models/active_storage/blob.rb#L65
ActiveStorage::Blob.create_after_upload!
がActiveStorage::Blob.build_after_upload
を実行します。https://github.com/rails/rails/blob/v5.2.2/activestorage/app/models/active_storage/blob.rb#L57
ActiveStorage::Blob.build_after_upload
がActiveStorage::Blob
をnew
して、upload
を実行します。https://github.com/rails/rails/blob/v5.2.2/activestorage/app/models/active_storage/blob.rb#L159
ActiveStorage::Blob#upload
がservice
インスタンスのupload
を実行します。
このservice
は、公式ガイドのセットアップでservice
に指定したDisk
やS3
が ActiveStorage::Service::DiskServiceやActiveStorage::Service::S3Serviceをnewしたインスタンスが格納されています。今回はローカルディスクを保存サービスに指定しているので、
ActiveStorage::Service::DiskService
のupload
を実行します。ActiveStorage::Service::DiskService
ActiveStorage::Service::DiskService.upload
で IO.copy_stream でファイルをRails.root/storage/
にコピーして、アップロード処理が終わります。
instrument
のブロックで囲まれていますが、これはActiveSupport::Notifications.instrument
をラップするためのメソッドです。
ログを見ると、logs/development.logDisk Storage (31.0ms) Uploaded file to key: fH4PhWtse6qwM6hBqsoqqmtz (checksum: ci+Mp/YmvV385qYcTJnlgg==)こんな感じのログが、アップロードファイルを属性に代入、またはattachの処理あたりで流れてくると思います。
感想
調べ終わってから記事を書いたので思考とは逆順になっています。
調べ始める前は、モデルクラスを
save
するタイミングでupload
的なメソッドをアプリケーションコードに近いところで実行していると予想していたので、attach
がそれに相当すると気づくまで時間がかかりました。
save
に合わせてファイルを保存するようにしたい場合は、直接属性に代入するのではなく、after_save
かそれに近いタイミングでattach
してあげると良さそうです。
- 投稿日:2019-02-27T14:16:44+09:00
Serverless FrameworkでGemをLayersに切り出す
環境
- Mac OS X 10.14.3
- Serverless Framework 1.36.1
- Ruby 2.5
はじめに
Serverless FrameworkでRubyを使用する場合にパッケージ容量をもっとも圧迫する
vendor
(gemの内包ディレクトリ)を切り出す方法について書きます。こちらの記事を読んで出来るようになること
- Serverless Framework x RubyでGemをLayerに切り出す
- Layersのバージョンアップにも対応可能
Lambda Layersとは
ざっくりと、各Lambda関数で共通化した処理をLambda関数の中に内包するのではなく、外に切り出してしまう仕組みのこと。
1. サンプルアプリケーションの作成
この記事では
serverless framework
の使い方などは書きません以下のコマンドでサンプルアプリケーションを作成します。
$ sls create -t aws-ruby -p sample上記を実行すると、以下のようなディレクトリが作成されます。
sample/ - serverless.yml - handler.rb2. bundle init & gem install
$ cd sample上記で、sampleディレクトリに移動しておきます。
sample
ディレクトリ内で、以下を実行していきます。$ bundle init上記で、
Gemfile
が作成されるので、エディタでGemfile
を編集していきます。# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } # 以下を追加 gem "business_time"適当なGemを
Gemfile
に追加して、以下を実行します。$ bundle install --path vendor/bundle3. serverless.ymlを編集
service: sample provider: name: aws runtime: ruby2.5 region: ap-northeast-1 role: arn:aws:iam::ロールのarn package: exclude: - vendor/** - Gemfile - Gemfile.lock functions: hello: handler: handler.hello layers: gems: path: vendorこの時点で一度デプロイします。
$ sls deployデプロイを実行すると、layersが作成されるので、再度
serverless.yml
を編集します。同一のserverless.yml内であれば、REFで参照をすることが出来ます。
公式ドキュメントservice: sample provider: name: aws runtime: ruby2.5 region: ap-northeast-1 role: arn:aws:iam::ロールのarn package: exclude: - vendor/** - Gemfile - Gemfile.lock functions: hello: handler: handler.hello layers: - {Ref: GemsLambdaLayer} layers: gems: path: vendor
function
にlayers
を追加して、Refで参照させるのですが、この時に参照方法のルールとして、Layer名をTitleCaseで記載し、LambdaLayer
を末尾に結合させる必要があります。
今回のLayer名はgems
なので、GemsLambdaLayer
となっています。Layer名が
hoge
だったら、HogeLambdaLayer
となるかと思います。これで再度デプロイします。
$ sls deployこれでAWSのLambda関数ページを見ても、まだLayerが反映されません。
どこが問題かと言うと、serverless.ymlには記載する順番を気をつける必要があるようで、以下のように、
functions
より上にlayers
を記載する必要があります。上記を修正した
serverless.yml
が以下service: sample provider: name: aws runtime: ruby2.5 region: ap-northeast-1 role: arn:aws:iam::ロールのARN package: exclude: - vendor/** - Gemfile - Gemfile.lock layers: gems: path: vendor functions: hello: handler: handler.hello layers: - {Ref: GemsLambdaLayer}これで再度デプロイすることでLambda関数にLayerが適用されます。
4. Lambda関数でgemを使用する
handler.rb
を以下のようにしますrequire 'json' require 'business_time' def hello(event:, context:) puts Date.today.workday? { statusCode: 200, body: JSON.generate('Go Serverless v1.0! Your function executed successfully!') } endこれで、適当にテストを作成し、「テスト」ボタンを押下すると、以下のようなエラーが発生します。
{ "errorMessage": "cannot load such file -- business_time", "errorType": "Init<LoadError>", "stackTrace": [ "/var/lang/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'", "/var/lang/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'", "/var/task/handler.rb:2:in `<top (required)>'", "/var/lang/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'", "/var/lang/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'" ] }
bussiness_time
を参照出来なくなっています。
Lambda Layersを使用する際、格納したファイルやディレクトリは、/opt
ディレクトリに格納される仕様になっています。なので、こちらの対応が必要になります。対応方法は以下の二つです。
* gemの参照先を絶対パスで記述する
* LOAD_PATHを変更する後者で対応することにしました。
以下のように、handler.rb
を変更します。load_path = Dir["/opt/bundle/ruby/2.5.0/gems/**/lib"] $LOAD_PATH.unshift(*load_path) require 'json' require 'business_time' def hello(event:, context:) puts Date.today.workday? { statusCode: 200, body: JSON.generate('Go Serverless v1.0! Your function executed successfully!') } endちなみに、
/opt/bundle/ruby/2.5.0/gems/**/lib
のパスはlayersの作成方法によって変わってしまうため、適宜変更してください。これでテストを実行すると、200が返却され、ログにtrueが返ってくるかと思います。
5. sls invokeを使用出来るようにする
上記までだと、
LOAD_PATH
を変更してしまうため、ローカルでsls invoke
が使用できなくなってしまうため、対応したいと思います。serverless frameworkの
sls invoke
コマンドを実行すると、IS_LOCAL
という環境変数が作成され、true
がセットされます。※ こちらを参照くださいなので、これをフラグにLambda関数に条件分岐を設定します。
handler.rb
を以下のように変更します。if ENV['IS_LOCAL'].nil? load_paths = Dir["/opt/bundle/ruby/2.5.0/gems/**/lib"] $LOAD_PATH.unshift(*load_paths) end require 'json' require 'business_time' def hello(event:, context:) puts Date.today.workday? { statusCode: 200, body: JSON.generate('Go Serverless v1.0! Your function executed successfully!') } endこれで、
sls invoke
だった時は、LOAD_PATH
が参照されなくなるので、sls invoke
が使えるようになるかと思います。
当然この方法だと、関数が増えた時に全てに分岐を加えなければいけないので、対応策としては微妙ですが...もっといい方法があればコメントいただけますと幸いです。
- 投稿日:2019-02-27T13:55:24+09:00
/xl/drawings/drawing1.xml が壊れたので、なんとかした話
あらすじ
せっせと作ったExcelファイル。完成して他の人に送ったら「開けないんですけど」とのこと。
そんなわけあるかい、、と思い、手元のオリジナルを開くと、「一部に問題が見つかりました。」とのこと。可能な限り内容を修復してほしいので、「はい(Y)」を選択
図形や画像ファイルが綺麗さっぱり消えていました。
こんなに悲しいことがあるだろうか。(いや、ない)
なので、紆余曲折の後、壊れた箇所の特定を手伝うツールを作成したのであった。
調査
しばらくインターネットの海を漂うと、同じ問題に直面した人がいるということが分かった。
OpenOfficeを試す
Excel2010で壊れた図形を復活させてみたを参考にOpenOfficeで開き直すと、確かに開くことができた。
ただ、図の矢印や画像のアスペクト比がぐちゃくちゃになっていて、とても、完全復活といえる代物ではなかった。
テキストベースのexcelなら、この方法で復活させて完了で良い気もするが、図や絵がふんだんに使われていたので、今回はPass。ちなみに、OpenOfficeではxls形式で保存することしかできないため注意が必要。
drawing1.xmlから壊れている図を特定する
エクセルが開かないによると、
- xlsxは拡張子をzipに変えるとzipで開くことができる
- /xl/drawings/drawing1.xml というファイルの中にある
<xdr:twoCellAnchor>
という要素で図や絵を表現している- 壊れた
<xdr:twoCellAnchor>
を取り除くことで、壊れていない部分は復旧させることが可能とのこと。
例えば、
<xdr:twoCellAnchor>
要素を半分にして、エラーが起こるかどうかを確認する、発生したら更に半分にして、、、みたいに、二分探索していけばいつかはたどり着くんだろうけど、、、
- 拡張子をxlsxからzipに変更
- 解凍
- drawing1.xmlを書き換える <= 改行の無いxmlなので、ツールが無いと結構しんどい
- 解凍した内容を再び圧縮
- 拡張子をzipからxlsxに変更
- excelで開いてみる
- 壊れていれば、再び3から
- (゚д゚)ウマー
という手順が想定されて、そこそこ手間がかかる。
今回は、170個ぐらい図や絵が貼ってあるExcelファイルだったので、[log_2 170] + 1 = 8 で最大8回この作業が発生するようだ。
壊れたら困る(情報量の多い)ファイルほど、壊れるというのが世の常なので、
いざという時のために、ツールを準備しておくのも悪くないだろう、ということで、ひと仕事しました。それらしいツール作成
それらしいツールを作りました。せっかくなので、置いていきます。
結果的に線形探索になりました。
急いでいたので、Rubyで書きました。
Rubyの記述力に私の実力が追いついていないので、冗長な箇所モリモリですが、お気になさらずどうぞ。やっていること
<xdr:twoCellAnchor>
、もしくは<xdr:oneCellAnchor>
要素をdrawing1.xmlに追加しては、その都度rebuild_[連番].xlsxを生成する使い方
- RubyのインストールされたOSを準備する
gem install rubyzip
する- githubから例のツールを取得する
- targetフォルダにxlsxファイルをzipにして解凍したファイルの中身を配置する
$ ruby ./src/rebuild_excel_book.rb
する- resultフォルダに生成されたExcelファイルを上から叩いて、イカれたメンバーを特定するぜ
感想
<xdr:twoCellAnchor>
だけでなく、<xdr:oneCellAnchor>
も存在するようだ。更なる調査が必要- xmlは改行が入っていても問題ないので、
(</xdr:oneCellAnchor)
や(</xdr:twoCellAnchor)
を\r\n$1
で置換してしまえば読みやすくなる- xmlは
<!-- -->
でコメントアウトできるので、drawing1.xmlの内容が少なければ、手作業で怪しい箇所をコメントアウトして確認しても大した手間じゃないかも<xdr:twoCellAnchor>
の子要素に一意となるid的な属性を持っていることに後から気付いた。rebuildするファイルのファイル名をidにすると、特定した後、壊れた要素を削除するのが楽になるかも- Excelがdrawing1.xmlを読み込むモジュールが分かれば、該当箇所の特定と削除を1つのプログラムで完結できそう(となるとVBSかC#が適しているかな)
- 投稿日:2019-02-27T13:10:13+09:00
NetApp Data ONTAP device moduleを使ってNetAppの初期設定作業をPuppet化してみた
Puppet社が公開しているNetApp Data ONTAP device moduleの検証メモです。この記事ではData ONTAP SimulatorをVirtualBox上にディプロイして検証しています。
環境
- Puppet MasterのOS : CentOS7
- Puppet AgentのOS : CentOS7
- Puppet : 6.0.5
- Data ONTAP Simulator : 8.3
- NetApp Manageability SDK : 5.7
- Vagrant : 2.2.3
- VirtualBox : 6.0.4
準備
ダウンロード
NetApp社のサイトからNetApp Manageability SDKをダウンロードします。事前にアカウント登録が必要です。
同じサイトからSimulate ONTAP 8.3 for VMware Workstation, VMware Player, and VMware FusionとCMode_licenses_8.3.txt をダウンロードします。ESXi版のovaと間違えないように注意が必要です。
Puppet社のサイトからPuppet Enterpriseをダウンロードします。
Puppetのセットアップ
Master側でダウンロード済みのPuppet Enterpriseのインストールバイナリを解いてインストールスクリプトを実行します。
$ sudo ./puppet-enterprise-installer どのように続行しますか? [1]: 1 --省略-- ## Puppet Enterpriseの設定が完了しました! --省略--puppetを実行してインストールは完了です。
$ sudo /opt/puppetlabs/bin/puppet agent -tAgent側でAgentのインストールスクリプトを実行します。
$ curl -k https:<Puppet Masterのホスト名>:8140/packages/current/install.bash | sudo bashAgent側でpuppetを実行してMasterに証明書のサインを要求します。
sudo /opt/puppetlabs/bin/puppet agent -tMaster側で証明書にサインします。
$ sudo /opt/puppetlabs/bin/puppetserver ca sign --all Successfully signed certificate request for device-proxy.puppet.com再度Agent側でpuppetを実行します。エラーが出なければ成功です。
$ sudo /opt/puppetlabs/bin/puppet agent -tNetApp Data ONTAP device moduleをMaster/Agent側両方にインストールします。
$ sudo puppet module install puppetlabs-netapp --version 1.3.0 --modulepath /etc/puppetlabs/code/environments/production/modules/ Notice: /etc/puppetlabs/code/environments/production/modulesへのインストールを準備をしています ... Notice: https://forgeapi.puppet.comからダウンロードしています ... Notice: インストール中 -- 中断しないでください ... /etc/puppetlabs/code/environments/production/modules └── puppetlabs-netapp (v1.3.0)ダウンロード済みのNetApp Manageability SDKをほどいてnetapp-manageability-sdk-5.7/lib/ruby/NetApp配下にある4つのファイルをAgent側の/etc/puppetlabs/code/environments/production/modules/netapp/lib/puppet/netapp_sdkにコピーします。
$ cp ./netapp-manageability-sdk-5.7/lib/ruby/NetApp/* /etc/puppetlabs/code/environments/production/modules/netapp/lib/puppet/netapp_sdkMaster側でpuppet.confの最終行に autosign = true を追記して、autosign.confに対象のホスト名を追記します。
$ sudo vi /etc/puppetlabs/puppet/puppet.conf --省略-- [master] autosign = true$ sudo vi ~/.puppetlabs/etc/puppet/autosign.conf ontap puppetsvmData ONTAP Simulatorのセットアップ
GitHubのリポジトリをクローンします。
$ git clone git@github.com:tlichten/vagrant-vsim.gitダウンロード済みのNetApp Manageability SDKをほどかずにzipファイルのままコピーします。
$ cp netapp-manageability-sdk-5.7.zip ./vagrant-vsimダウンロード済みのSimulate ONTAP 8.3 for VMware Workstation, VMware Player, and VMware Fusionをコピーします。
$ cp vsim-netapp-DOT8.3-cm.ova ./vagrant-vsimvsim.confを編集してライセンスキーを貼り付けます。ライセンスキーはダウンロード済みのCMode_licenses_8.3.txt:21行目に書いてあります。
$ vi vsim.conf --省略-- CLUSTER_BASE_LICENSE="xxx"$ 起動します。エラーが出なければ成功です。
$ vagrant up$ Cluster ONTAPのノード管理コンソールにログインしてみます。VSIM::>というプロンプトが出れば成功です。
$ vagrant ssh vsim VSIM::>動作確認
Agent側でdevice.confを編集してData ONTAPの接続情報を記載します。
$ sudo vi /etc/puppetlabs/puppet/device.conf [ontap] type netapp url https://admin:netapp123@VSIM-01Master側でData ONTAPのノードからStorage Virtual Machineを作成するマニフェストを作成します。
$ sudo vi /etc/puppetlabs/code/environments/production/manifests/site.pp node 'ontap' { netapp_aggregate { 'puppetaggr1': ensure => present, diskcount => '8', raidtype => 'raid_dp', nodes => ['VSIM-01'] } netapp_vserver { 'puppetsvm': ensure => present, rootvol => 'puppetsvm_root', rootvolaggr => 'puppetaggr1', rootvolsecstyle => 'unix', allowedprotos => 'nfs', state => 'running' } netapp_security_login {'ssh:password:vsadmin:puppetsvm': ensure => present, password => 'netapp123', role_name => 'vsadmin', is_locked => false } netapp_lif { 'puppet_lif': ensure => present, role => 'data', homeport => 'e0c', homenode => 'VSIM-01', address => '10.0.207.5', vserver => 'puppetsvm', netmask => '255.255.255.0', dataprotocols => ['nfs'] } }Agent側でPuppetを実行してマニフェストを適用します。
$ sudo RUBYLIB=/etc/puppetlabs/code/environments/production/modules/netapp/lib/puppet/netapp_sdk puppet device --verbose --省略-- Info: Node[ontap]: Unscheduling all events on Node[ontap] Notice: Applied catalog in 0.93 secondsData ONTAPの管理コンソールにログインしてマニフェストの通りにリソースが作成されたか確認します。
VSIM::> aggr show Aggregate Size Available Used% State #Vols Nodes RAID Status --------- -------- --------- ----- ------- ------ ---------------- ------------ aggr0 855MB 35.97MB 96% online 1 VSIM-01 raid_dp, normal puppetaggr1 5.27GB 3.24GB 39% online 2 VSIM-01 raid_dp, normal 2 entries were displayed. VSIM::> vserver show Admin Operational Root Vserver Type Subtype State State Volume Aggregate ----------- ------- ---------- ---------- ----------- ---------- ---------- VSIM admin - - - - - VSIM-01 node - - - - - puppetsvm data default running running puppetsvm_ puppetaggr root 1 3 entries were displayed. VSIM::> security login show Vserver: VSIM Authentication Acct User/Group Name Application Method Role Name Locked ---------------- ----------- -------------- ---------------- ------ admin console password admin no admin http password admin no admin ontapi password admin no admin service-processor password admin no autosupport console password autosupport no vagrant ontapi password admin no vagrant ssh password admin no vagrant ssh publickey admin - Vserver: puppetsvm Authentication Acct User/Group Name Application Method Role Name Locked ---------------- ----------- -------------- ---------------- ------ vsadmin ontapi password vsadmin yes vsadmin ssh password vsadmin yes 10 entries were displayed. VSIM::> network interface show Logical Status Network Current Current Is Vserver Interface Admin/Oper Address/Mask Node Port Home ----------- ---------- ---------- ------------------ ------------- ------- ---- VSIM VSIM-01_mgmt_auto up/up 10.0.207.3/24 VSIM-01 e0c true puppetsvm puppet_lif up/up 10.0.207.5/24 VSIM-01 e0c true 2 entries were displayed.
- 投稿日:2019-02-27T07:05:29+09:00
条件文の分解(Decompose Conditional)
1つずつリファクタリング技法まとめ
個人的に簡単かつ取り入れ易いと思うものから目的
すぐ引き出せるようにする
基本作業サイクル
- システムを動かして仕様を精査
- テストメソッドを作成
- テストの失敗を確認
- テストの成功を確認
- 小さい変更、随時テスト実行(失敗確認->成功確認)
- 最後テスト実行
- 最後動作確認
条件文の分解(Decompose Conditional)とは
判定メソッド化
条件文をメソッドとして抽出し、メソッド名に意味を持たせることポイント
- 算術演算子をifの条件式から消す。
- 複数の条件式の示す意味をメソッド名にする。
- コメントに記載してある内容をメソッド名にする。
例
# 平成 if i >= 1989 && i <= 2019 end↓
def heisei?(i) i >= 1989 && i <= 2019 end if heisei?(i) puts '多分平成' end書籍情報
Jay Fields (著), Shane Harvie (著), Martin Fowler (著), Kent Beck (著),
長尾 高弘(訳), リファクタリング:Rubyエディション
https://amzn.to/2VlyWML雑感
スコープが狭くて影響が小さいものから
- 投稿日:2019-02-27T06:06:52+09:00
Rspec導入まとめ
最低限の導入手順を記載
後ほどいつもやりそうな初期設定を追記事前設定
config/database.ymlにtestの指定がある事
config/database.ymltest: <<: *default database: プロジェクト名_testテストを導入したい環境に以下のgemを指定している事
Gemfilegroup :development, :test do ・ ・ gem 'rspec-rails', '~> 3.6' gem 'factory_bot_rails' gem 'database_cleaner' ・ ・ end導入
rspecに必要なモジュールをインストールする
bundle install rspec:install rails generate rspec:install下記を追記
spec/support/factory_bot.rbRSpec.configure do |config| config.include FactoryBot::Syntax::Methods endテスト用DBの作成
rails db:migrate RAILS_ENV=test実行方法
rspec spec/対象の階層下 or ファイル
- 投稿日:2019-02-27T01:41:41+09:00
【備忘録】No route matches [GET]の原因と解決策
エラー
post/indexのブラウザ上で、postコントローラーのdestroyアクションを実施した結果、以下のエラーが起きた。
エラーの該当箇所
<%= link_to("削除", "/posts/#{@post.id}/destroy", {method: "delete"}) %>解決策
rake routesでひとまず確認
rake routesposts_create POST /posts/create(.:format) posts#create GET /posts/:id/edit(.:format) posts#edit POST /posts/:id/update(.:format) posts#update POST /posts/:id/destroy(.:format) posts#destroy解決策
routesと、メソッドを変えたら解決した。
①POSTをDELETEに変更
routesで、POST /posts/:id/destroy(.:format) を、DELETE /posts/:id/destroy(.:format)に変更
参考記事:https://railsguides.jp/routing.html
②link_toをbutton_toに変更
<%= button_to("削除", "/posts/#{@post.id}/destroy",method: :delete) %>参考記事:https://qiita.com/y-temp4/items/2d50feb3ff0d65acdf67
メモ
link_toからbutton_toに変更すると、Viewはこうなった。
- 投稿日:2019-02-27T01:41:41+09:00
No route matches [GET]の原因と解決策
エラー
post/indexのブラウザ上で、postコントローラーのdestroyアクションを実施した結果、以下のエラーが起きた。
エラーの該当箇所
<%= link_to("削除", "/posts/#{@post.id}/destroy", {method: "delete"}) %>解決策
rake routesでひとまず確認
rake routesposts_create POST /posts/create(.:format) posts#create GET /posts/:id/edit(.:format) posts#edit POST /posts/:id/update(.:format) posts#update POST /posts/:id/destroy(.:format) posts#destroy解決策
routesと、メソッドを変えたら解決した。
①POSTをDELETEに変更
routesで、POST /posts/:id/destroy(.:format) を、DELETE /posts/:id/destroy(.:format)に変更
参考記事:https://railsguides.jp/routing.html
②link_toをbutton_toに変更
<%= button_to("削除", "/posts/#{@post.id}/destroy",method: :delete) %>参考記事:https://qiita.com/y-temp4/items/2d50feb3ff0d65acdf67
メモ
link_toからbutton_toに変更すると、Viewはこうなった。
- 投稿日:2019-02-27T00:12:33+09:00
【備忘録】undefined method `name' for nil:NilClassの原因と解決策
エラー
2行目のpost.user.nameの「name」のメソッドが見当たらない
post.indexActionView::Template::Error (undefined method `name' for nil:NilClass): 1: <% @posts.each do |post| %> 2: <P><%= link_to(post.user.name, "/users/#{post.user.id}") %></p> 3: <p><%= link_to(post.content, "/posts/#{post.id}") %></p> 4: <% end %>原因:postテーブル内の何かしらのレコードがなかった
考えられる原因は2つ
(1)postはpost.controller.rbに定義されていない
(2)データベースのpostモデルに、あるべきレコードがない前者は、コントローラーで定義されていたので、後者の方に原因があると見立てた。
解決策:DBの全てのレコードを空にした
rake db:reset全てのテーブルを dropして、"db/schema.rb" を元にテーブルを再作成する。(参考記事:http://o.inchiki.jp/obbr/183)
メモ
DBの全てのレコードを空にした後、改めて、post.createしたら無事にpost.indexに反映された。
- 投稿日:2019-02-27T00:12:33+09:00
undefined method `name' for nil:NilClassの原因と解決策
エラー
2行目のpost.user.nameの「name」のメソッドが見当たらない
post.indexActionView::Template::Error (undefined method `name' for nil:NilClass): 1: <% @posts.each do |post| %> 2: <P><%= link_to(post.user.name, "/users/#{post.user.id}") %></p> 3: <p><%= link_to(post.content, "/posts/#{post.id}") %></p> 4: <% end %>原因:postテーブル内の何かしらのレコードがなかった
考えられる原因は2つ
(1)postはpost.controller.rbに定義されていない
(2)データベースのpostモデルに、あるべきレコードがない前者は、コントローラーで定義されていたので、後者の方に原因があると見立てた。
解決策:DBの全てのレコードを空にした
rake db:reset全てのテーブルを dropして、"db/schema.rb" を元にテーブルを再作成する。(参考記事:http://o.inchiki.jp/obbr/183)
メモ
DBの全てのレコードを空にした後、改めて、post.createしたら無事にpost.indexに反映された。