- 投稿日:2020-07-14T22:20:15+09:00
【before_action】
before_actionとは
before_actionはコントローラーで定義された処理を実行する前に共通の処理を行うことができるメソッドのこと
今回はコントローラー内のメソッドの実行内容が重複している場合のbefore_actionの使用例を書いていきます。
上の画像のようにeditアクションとshowアクションの実行内容が同じである場合は共通の処理としてまとめてしまった方が可読性も上がり、変更の際も便利なので処理をまとめる。
まず該当のアクションを削除。
その後先ほど削除した共通していた処理内容をprivateメソッドの部分にset_actionとして定義する。
最後にコントローラーの上部にbefore_actionを記述する。
今回の場合はset_actionを適用させたいのはeditとshowアクションのみなのでオプションとしてonlyを記述することで該当のアクションにのみset_actionを実行させれば良い。
- 投稿日:2020-07-14T22:15:22+09:00
Ruby on RailsのポートフォリオにDockerを組み込む!
現在の状態
みなさんこんにちわ今回Qiita初投稿を行います!
間違っているところがあると思いますがその時は優しく教えてください(笑)
今回、AwsとDockerを用いて既に作成済のRailsのポートフォリオをデプロイしています。
現在の状態は、railsとAws用いてポートフォリオを作成したのですが、環境構築が大変だと思い、環境周りをコードで管理しやすいDockerを用いてポートフォリオを際デプロイしたいと思います。構成図
デプロイの際の構成図は以下の通りです。
めちゃくちゃ簡単に説明するとクライアントから通信リクエストが来るとNginxに行き動的な処理が必要な際はpumaに通信を行いその際にpumaとmysqlも通信する流れとなっています。
今回はこの構成をDockerを用いて作成していきたいと思います。必要な物
今回デプロイする際はAwsのEC2とRDSをを使用します。
Dockerで環境構築を行いますが、データの永続化の観点から今回データベースはDockerではなくRDSのMysqlをしようします。
以下作業に必要な準備と作業です。・AwsでEC2、RDSを用いてデプロイする際に必要な準備を行う(ネットワーク構成など...)
・local環境とEc2にDokcerをインストール
・local環境とEc2にDocker-composeをインストール参考にした記事
・EC2上でRailsアプリケーションにDockerを導入する(Rails、Nginx、RDS)
・Rails On DockerでのAWSデプロイができたので,中身を整理します。作業
まずはじめにDockerファイルを作成します。
ここではDockerfileを用いてRuby周りの環境を構築するコードを書きます。FROM ruby:2.5.7 RUN apt-get update -qq && \ apt-get install -y build-essential \ libpq-dev \ nodejs \ vim RUN mkdir /アプリの名前 WORKDIR /アプリの名前 ADD Gemfile /アプリの名前/Gemfile ADD Gemfile.lock /アプリの名前/Gemfile.lock RUN gem install bundler RUN bundle install ADD . /アプリの名前 RUN mkdir -p tmp/sockets RUN mkdir -p tmp/pids次にDocker-composeを作成します。
ここではDockerコンテナの管理やマントする箇所の指定を行っていきます!Docker-compose.ymlversion: '3' services: app: build: . command: bundle exec puma -C config/puma.rb -e production volumes: - .:/アプリの名前:cached - public-data:/アプリの名前/public - tmp-data:/アプリの名前/tmp - log-data:/アプリの名前/log web: build: context: containers/nginx volumes: - public-data:/アプリの名前/public - tmp-data:/アプリの名前/tmp ports: - 80:80 volumes: public-data: tmp-data: log-data:次に以下のファイルを作成します。
ここではNginxコンテナの設定を行っていきます。FROM nginx:1.15.8 RUN rm -f /etc/nginx/conf.d/* ADD nginx.conf /etc/nginx/conf.d/アプリの名前.conf CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.confcontainers/nginx/nginx.confupstream FashionInformation_app { server unix:///アプリの名前/tmp/sockets/puma.sock; } server { listen 8000; server_name ドメイン名; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; root /アプリの名前/public; client_max_body_size 100m; error_page 404 /404.html; error_page 505 502 503 504 /500.html; try_files $uri/index.html $uri @アプリの名前; keepalive_timeout 5; location @アプリの名前 { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_pass http://アプリの名前; } }Railsアプリケーションをデプロイする
ここまできたらEC2にgitをクローンします!
ec2-user@ip-xxx-xx-xx-xxx ~]$ git clone GitHubのリポジトリのURLイメージのビルド
ec2-user@ip-xxx-xx-xx-xxx]$ cd myapp [ec2-user@ip-xxx-xx-xx-xxx myapp]$ docker-compose buildサーバー起動前の準備
[ec2-user@ip-xxx-xx-xx-xxx myapp]$ docker-compose run app rails assets:precompile RAILS_ENV=productionサーバー起動前の準備
[ec2-user@ip-xxx-xx-xx-xxx myapp]$ docker-compose up -dデータベースの作成、マイグレーションファイルの読み込み
[ec2-user@ip-xxx-xx-xx-xxx myapp]$ docker-compose exec app rails db:create db:migrate RAILS_ENV=productionパブリックIPにアクセスして、正しく表示されれば成功です!
- 投稿日:2020-07-14T21:59:04+09:00
Kinx ライブラリ - JIT ライブラリ(番外編)
Kinx ライブラリ - JIT ライブラリ(番外編)
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。今回は JIT ライブラリ番外編です。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
ここ で紹介した JIT ライブラリに新規追加した機能です。
JIT ライブラリ に任意のバイナリコードを実行できる機能を追加してみました。本気で細かく制御したい人向けです。こちらはがっつりアーキテクチャ依存です。そして、クラッシュさせるのも簡単です。
サンプル
まずはサンプルです。
okay.kxusing Jit; var code = System.PLATFORM == "X86_64-WIN" ? <0x48, 0x89, 0xc8, 0xc3> // mov rax, rcx | ret : System.PLATFORM == "X86_64" ? <0x48, 0x89, 0xf8, 0xc3> // mov rax, rdi | ret : null; if (code.isBinary) { Jit.dump(code); var runner = new Jit.Runner(code); System.println(runner.run(100)); }$ ./kinx okay.kx 0: 48 89 f8 mov rax, rdi 3: c3 ret 100さて、引数で与えた数値を単に返すだけの関数で、破壊するレジスタも無いのでプロローグもエピローグもなく、第一引数に来た値を復帰値(
rax
)に設定してret
するだけのものです。
System.PLATFORM
System.PLATFORM
で x64 でかつ Windows か Windows ではないかを切り分けてます。生のアセンブラだとこうやって切り分けないといけないのが面倒ですね。しかし、何でもできる という危険で甘い香りのするメリットを存分に享受できます。Windows だと Microsoft 呼び出し規約に基づくので第一引数は
rcx
レジスタに入ってきます。それに対し、ほぼほぼマイクロソフト以外が採用している System V 呼び出し規約ではrdi
レジスタに第一引数が入ってきます。
System.PLATFORM
で具体的には何が返るかというと...
Value Window? "X86_32-WIN"
O "X86_64-WIN"
O "ARM_THUMB2-WIN"
O "ARM_V7-WIN"
O "ARM_V5-WIN"
O "ARM_64-WIN"
O "X86_32"
"X86_64"
"ARM_THUMB2"
"ARM_V7"
"ARM_V5"
"ARM_64"
"PPC_64"
"PPC_32"
"MIPS_32"
"MIPS_64"
"SPARC_32"
"TILEGX"
"UNSUPPORTED"
です。
やってはいけないこと
crash.kxusing Jit; var code = <0x48, 0x31, 0xc0, // xor rax, rax 0x48, 0x8b, 0x00> // mov rax, [rax] ; Jit.dump(code); var runner = new Jit.Runner(code); System.println(runner.run());$ ./kinx crash.kx 0: 48 31 c0 xor rax, rax 3: 48 8b 00 mov rax, [rax] Segmentation fault (core dumped)ふふふ...。危ないなぁ。
おわりに
一言でいうと、"Take your own risk." といったところですね。しかし、こういうところに踏み込むスクリプト言語はなかなか無いと思うので、それなりの価値はあるのではないでしょうか。例えば、このライブラリがあれば Xbyak なんかも移植できそうですね! Kinx には演算子オーバーライド(オーバーロードとは呼ばない)もありますし。
ますます JIT が身近になりますね。
ではまた。
- 投稿日:2020-07-14T20:58:12+09:00
[Rails]payjp(API)を利用する仕組みと方法をかなり丁寧に書いてみる(クレカ登録編)
はじめに
スクールの課題でフリマアプリを作る中で、payjpを利用したクレカ登録・決済機能の実装がありました。
検索すると以前の卒業生が多数記事を書いてくれてるのでコードは見つかるのですが、何をやっているの?という所は結局公式のリファレンス読むのが一番だよねとなったので理解したことについて記載しておくものです。この記事では
payjpを利用するってそもそもどういうこと?(APIの説明)
payjpの呼び出し方
クレカ登録の実装
登録したクレカによる決済実装上記について記載していきます。
※現在payjpはv2バージョンを利用することが推奨されております。よりセキュアな実装として登録フォーム自体もpayjp側で用意してくれているのがv2なのですが、今回の課題はフォームの実装もこちら側のタスクとして含まれていたため、旧型のv1で実装を進めています。
とはいえ呼び出し方や使えるメソッドが異なるだけで、どちらも公式を読みながらやればあまり差がなく実装できるかと思います。環境
ruby 2.6.5
rails 6.0.3payjpを利用するってそもそもどういうこと?
コードを書いていくにしても、前段としてここの理解がまず大事だと思っています(あまりここに触れた記事はなかった)
payjpは、APIの1種です。
APIっていうのはググれば色々説明が出てくるんですが、「外部向けにソフトウェアの機能を一部提供してあげるよ」って感じです。用意してくれてる側の指示に従いうまくAPIを呼び出すことで、自分のアプリケーションの中でそのソフトウェアの機能を一部使えることができる様になる、という仕組みですね。
具体例を言うと、例えばGogole Mapのサービスがあります。
あれもgoogleが用意してくれたAPIを利用し、指示にしたがってコードを記載することで、自分のアプリケーション上にgoogle mapを表示することが可能になるという仕組みです。payjpもクレカに関するAPIであり、利用することでクレジットカードの登録や決済が可能になります。
APIを利用する時に考え方として個人的に大事だと思っているのが、相手に用意してもらっているという流れをちゃんと把握するということです。
上記を認識した上で実装の流れを考えると、
用意してもらったやり方で、APIを呼び出す準備をする
用意してもらったやり方で、向こうのアプリケーションとやりとりをし、処理を行うといった感じになります。このイメージがあると、以降の作業が具体的に何をしているのか分かりやすくなると思うので重要です。
それが故、用意してくれてる側の公式ドキュメントでやり方を理解する、と言うのが一番適切なアプローチになります。公式の説明書を読む様なものなので。
payjpの呼び出し方
前提として、payjpに登録しテスト公開鍵とテスト秘密鍵を取得しましょう。
APIの認証のために必要なキーです。payjp側はこれらを持って、僕らが「利用を許されたユーザー」であることを判断しています。登録が終わったらv1の公式リファレンスを読みましょう。
アクセスして早々、埋め込むべきスクリプトがちゃんと記載されています。
また、少し下に読み進めると、公開鍵による認証方法も書いています。
実際の記載については次項で見ていきます。クレカ登録の実装
上記の呼び出し方も踏まえ、先に実際のコードを載せます。
その上で流れを解説していきます。
※各ファイル、説明に不要な部分の記載は適宜削除しています。application.html.haml!!! %html %head %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ %script{src: "https://js.pay.jp/v1/", type: "text/javascript"} = csrf_meta_tags = csp_meta_tag = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' = javascript_include_tag 'application'new.html.haml.creditCreate .creditCreate__title %h1 クレジットカード情報入力 .creditForm = form_with model: @credit, method: :post, id: "cardCreateForm" do |f| .creditForm__numberfield %label(for="cardnumber-input") カード番号 %span.creditPoint 必須 %br = f.text_field :card_number, type: "text", class: 'cardnumber-input', id:'card-number', placeholder: "半角数字のみ", maxlength: 16 .creditslist = image_tag "jcb.gif", class:"creditsIcon" = image_tag "visa.gif", class:"creditsIcon" = image_tag "master.gif", class:"creditsIcon" = image_tag "amex.gif", class:"creditsIcon" .creditForm__datefield %label 有効期限 %span.creditPoint 必須 %br = f.select :exp_month, [["01",1],["02",2],["03",3],["04",4],["05",5],["06",6],["07",7],["08",8],["09",9],["10",10],["11",11],["12",12]],{} , class: 'dateSelect', name: 'exp_month' 月 = f.select :exp_year, [["20",2020],["21",2021],["22",2022],["23",2023],["24",2024],["25",2025],["26",2026],["27",2027],["28",2028],["29",2029]],{} , class: 'dateSelect', name: 'exp_year' 年 .creditForm__securityfield %label セキュリティコード %span.creditPoint 必須 %br = f.text_field :cvc, type: 'text', class: 'securityInput', id: 'cvc', placeholder: 'カード背面4桁もしくは3桁の番号', maxlength: "4" #card_token.creditForm__submitfield = f.submit '追加する', class: 'creditsSubmit', id: 'token_submit'payjp.js$(function() { $('#cardCreateForm').on('submit', function(e) { e.preventDefault() Payjp.setPublicKey(['PAYJP_PUBLIC_KEY']); var card = { number: document.getElementById("card-number").value, exp_month: document.getElementById("credit_exp_month").value, exp_year: document.getElementById("credit_exp_year").value, cvc: document.getElementById("cvc").value }; if (card.number == "" || card.cvc == "") { alert("入力もれがあります"); } else { Payjp.createToken(card, function(status, response) { if (status === 200 ) { $("#card_number").removeAttr("name"); $("#cvc").removeAttr("name"); $("#exp_month").removeAttr("name"); $("#exp_year").removeAttr("name"); $("#card_token").append( $('<input type="hidden" name="payjp-token">').val(response.id) ); $('#cardCreateForm').get(0).submit(); alert("登録に成功しました"); } else { alert("カード情報が正しくありません"); } }); } }); });credits_controller.rbrequire 'payjp' def create Payjp.api_key = ENV['PAYJP_SECRET_KEY'] if params['payjp-token'].blank? render :new else customer = Payjp::Customer.create( email: current_user.email, card: params['payjp-token'], metadata: {user_id: current_user.id} ) @credit = Credit.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card) if @credit.save redirect_to user_path(current_user.id) else render :new end end end以上がクレカ登録に関するコードです。
処理の流れは以下の通りです。この流れに沿って説明をしていきます。①payjpのAPIを利用するセッティングをする
②jsファイルにて、フォーム送信時に入力内容とpajypを紐付け登録の準備をする
③コントローラーにアクションを飛ばし、payjp上で顧客データを生成・顧客データを紐づくidをテーブルに保存する①payjpのAPIを利用するセッティングをする
application.html.hamlをみましょう
application.html.haml!!! %html %head %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ %script{src: "https://js.pay.jp/v1/", type: "text/javascript"} = csrf_meta_tags = csp_meta_tag = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' = javascript_include_tag 'application'scriptの部分がpayjpを呼び出す記載です。
呼び出し方は公式リファレンスに書いてあります。ただ転記するだけです。公式をみていればただ転記するだけなのです。②jsファイルにて、フォーム送信時に入力内容とpajypを紐付け登録の準備をする
jsファイルをみましょう。
フォーム送信のhamlファイルと対応した記載になってるので、並べてみながらだと理解がしやすいと思います。payjp.js$(function() { $('#cardCreateForm').on('submit', function(e) { e.preventDefault() Payjp.setPublicKey(['PAYJP_PUBLIC_KEY']); var card = { number: document.getElementById("card-number").value, exp_month: document.getElementById("credit_exp_month").value, exp_year: document.getElementById("credit_exp_year").value, cvc: document.getElementById("cvc").value }; if (card.number == "" || card.cvc == "") { alert("入力もれがあります"); } else { Payjp.createToken(card, function(status, response) { if (status === 200 ) { $("#card_number").removeAttr("name"); $("#cvc").removeAttr("name"); $("#exp_month").removeAttr("name"); $("#exp_year").removeAttr("name"); $("#card_token").append( $('<input type="hidden" name="payjp-token">').val(response.id) ); $('#cardCreateForm').get(0).submit(); alert("登録に成功しました"); } else { alert("カード情報が正しくありません"); } }); } }); });分割して解説していきます。
payjp.js$(function() { $('#cardCreateForm').on('submit', function(e) { e.preventDefault()jQueryで、フォームボタンをクリックした際の挙動ですと定義しています。
payjp.jsPayjp.setPublicKey(['PAYJP_PUBLIC_KEY']);認証(payjpの利用登録ができていますよ!と言うこと)を示すため、登録し取得したテスト公開鍵をセットします。
これを持ってpayjp側が利用を許可してくれます。setPublicKeyという書き方がいきなり出てきますが、これも公式リファレンスで指定された方法で記述してます。ただのコピペです。恐ることはありません。
payjp.jsvar card = { number: document.getElementById("card-number").value, exp_month: document.getElementById("credit_exp_month").value, exp_year: document.getElementById("credit_exp_year").value, cvc: document.getElementById("cvc").value };JSの記載です。get element by id と言う書き方の通り、各フォーム欄のidを指定・特定することで、それぞれの入力内容を定義し、cardと言う変数に代入し定義しています。
payjp.jsif (card.number == "" || card.cvc == "") { alert("入力もれがあります");jsの記載です。定義した変数cardの、numberもしくはcvcが空だった時に受付をせずエラーを返す処理をしています。
payjp.js} else { Payjp.createToken(card, function(status, response) { if (status === 200 ) { $("#card_number").removeAttr("name"); $("#cvc").removeAttr("name"); $("#exp_month").removeAttr("name"); $("#exp_year").removeAttr("name"); $("#card_token").append( $('<input type="hidden" name="payjp-token">').val(response.id) ); $('#cardCreateForm').get(0).submit(); alert("登録に成功しました"); } else { alert("カード情報が正しくありません"); } }); } }); });カード情報を基にトークンを生成します。
これもpayjp APIの公式リファレンスを読んでください。トークンを生成の部分を読むと、トークンはどのように生成するのかと言う文章での説明と、実際の記載方法が右側に記載されているはずです。
いきなりPayjp.create~と言う記載が出てきましたが、これはリファレンスに「この通り書いたらできるよ」って書いてくれてるだけなので、ちゃんと読めばその通りにするだけです。
ここでnumber等4つの値を渡す必要があると書かれいてがために、先ほどcard変数に4つの値を定義した訳です。順番的にはここで必要とされてるから定義している訳です。if status === 200とは、カード情報が有効であり、正しくトークンが生成された場合にpayjp側が返してくれるステータス値になります。
なのでelseでエラーを返しているのは「正常に登録できなかった場合」を示しているわけです。ここからさらに細かくみていきます。
payjp.js$("#card_number").removeAttr("name"); $("#cvc").removeAttr("name"); $("#exp_month").removeAttr("name"); $("#exp_year").removeAttr("name");JS,jQueryの記載です。
カードが有効だった場合、セキュリティの観点からそれぞれのフォームに入力した値を取り除いています(idで指定したフォームのname属性をremoveする、と言う処理です)payjp.js$("#card_token").append( $('<input type="hidden" name="payjp-token">').val(response.id) ); $('#cardCreateForm').get(0).submit(); alert("登録に成功しました");カードが有効な際にPayjpから帰ってきたデータ(response.id)を、フォームに返す処理をappendで行っています。
type = hiddenを指定することで、あくまでユーザーからは見えないものの、フォームにデータを送っている形です。その上で、submitすることで、Payjpから返してもらったデータをこの後controllerに送りcreateアクションを行っていく、と言うことを実現しています。
コントローラーにうまくデータを飛ばすための記載ということですね。
これを持って次にcontrollerの処理をみていくことができます。③コントローラーにアクションを飛ばし、payjp上で顧客データを生成・顧客データを紐づくidをテーブルに保存する
credits_controller.rbrequire 'payjp' def create Payjp.api_key = ENV['PAYJP_SECRET_KEY'] if params['payjp-token'].blank? render :new else customer = Payjp::Customer.create( email: current_user.email, card: params['payjp-token'], metadata: {user_id: current_user.id} ) @credit = Credit.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card) if @credit.save redirect_to user_path(current_user.id) else render :new end end endここまでうまく処理ができて、最後にcontrollerでデータ作成となります。
順を追ってみていきましょう。credits_controller.rbrequire 'payjp' def create Payjp.api_key = ENV['PAYJP_SECRET_KEY'] if params['payjp-token'].blank? render :newまずrequire 'payjp'でpayjpを使える様にします。
次にSECRET_KEYを指定し、こちらがpayjpの利用権を思ったユーザーであることを示します(環境変数を用いて記載しています)その上で先ほど正常なレスポンスだった場合に送られてきたデータがparams['payjp-token']ですので、そちらがからだった場合は処理を行わずrenderする処理を記載しています。
credits_controller.rbdef create else customer = Payjp::Customer.create( email: current_user.email, card: params['payjp-token'], metadata: {user_id: current_user.id} #ここは任意 )ここで、Payjpの顧客データを作成し、変数customerに代入しています。
いきなりこの書き方が出てきました。
何度も言う様ですが公式リファレンスを見れば全てが買いてあります。今回行いたいのは顧客データの作成なので、リファレンスの中の顧客データの欄をみます。
すると、Payjp::Customer.createという記載方法や、その引数が書かれているはずです。引数の内容をみていけば何をいれるべきなのかは簡単にわかるはずです。
例えばcardと言う引数はトークンIDを指定と書かれているので、先ほど送る様に設定したトークンIDを設定すればいいことがわかります。credits_controller.rbdef create @credit = Credit.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card) if @credit.save redirect_to user_path(current_user.id) else render :new end endここからはRails側のテーブル処理の話です。
先ほどのリファレンスを参照すれば、Payjpの顧客が持つレスポンスデータについても確認することができます。
先ほど作成したPayjpの顧客データは変数customerに代入していたため、customer.id等で指定することで、レスポンスデータをテーブルに保存することができるわけです。(法律上クレカデータを自分のテーブルに保存することはできないため、そのデータ自体はpayjp側に保存してもらい、そのデータを呼び出すための紐付けとしてcustomer_idやcardのdataをテーブルに保存しておくわけです。
終わりに
結構な長文になってしまったので登録のみで終わらせますが、大事なのは流れを理解し、リファレンスを読むこと、これに尽きると思います。
この登録したデータを基にカード決済を行う流れについても、リファレンスを読みながら決済にはどの様な記述が必要か?引数には何を指定するのか?を見ることでわりとすんなり実装できました。
(先ほどテーブルに紐付けたことからわかる通り、決済に必要なカードデータを呼び出して引数に渡してやれば良い、と言うのはそれほど難しくなく想像できるのではないでしょうか)とはいえAPIに慣れていない状態だったのでQiitaの記事にも大分助けられつつの実装にはなりました。が、結論公式が最強、この言葉を実感を持って理解できる経験だったので記録しておきます。
*初学者ゆえ何かあればご指摘いただけると嬉しいです。
- 投稿日:2020-07-14T20:18:50+09:00
文字列の間を正規表現で抜き出したい
文字列の間の抜き出しに手間取ったので、メモ。
経緯
文字列の前後が固定で、間にある文字を抜き出す
例: 下記を連結したファイル名や定数からサービス名部分を抜き出したい COMPANY_SERVICE_OPTION ※命名規則がサービス会社_サービス名_OPTIONパターン1
こちらを参考にして取得
"COMPANY_SERVICE_OPTION".slice(/COMPANY_(.+)_OPTION/) puts $+ SERVICE「$+」はRubyの組み込み変数で他にもいろいろとある模様。
しかしながら、非推奨との記載もちらほら見かけたため下記パターン2を使用した。パターン2
こちらのgsub記載を参考にして取得
puts "COMPANY_SERVICE_OPTION".gsub(/COMPANY_(.+)_OPTION/,'\+') SERVICE以上です。
いいねやQiitaやTwitterのフォローいただけると励みになります!
他にも方法がありましたら、コメントお待ちしております。
宜しくお願いします〜参考
正規表現で間の文字列を抜き出したい
[Ruby] Kernelの特殊変数をできるだけ$記号なしで書いてみる
String - Rubyリファレンスマニュアル
- 投稿日:2020-07-14T19:59:11+09:00
【HTTPメソッドのPATCH】
- 投稿日:2020-07-14T19:32:06+09:00
Rails5でECサイトを作る⑩ ~注文機能を作る~
はじめに
架空のベーカリーで買い物できるECサイトを作るシリーズ、Rails5でECサイトを作る⑨の続きです。このサイトの主要機能であり最も難しい箇所であるOrderモデル(注文機能)周辺を実装します。
ユーザの動きは以下のようになります。
カートに商品を入れて「注文画面に進む」ボタン押下 ↓ 注文情報入力(orders/new)画面 ・支払方法 ・お届け先 を入力、「確認画面へ進む」ボタン押下 ↓ 注文情報確認(orders/confirm)画面 内容を確認して「注文を確定する」ボタン押下 ↓ orders/thanks画面を表示(静的ページ) 注文履歴を一覧、詳細画面で確認できるポイントは、orders/new画面で入力するお届け先が、「自分の住所」「登録した住所」「新しい住所」から選べることです。新しい住所を選択してnew画面のフォームに内容を入力すると、Orderのデータとして保存されるとともに、Addressモデルにも新規データとして保存されます。
フォームを入れ子にすることと、そのデータを保存する前に確認画面を経由することが、この機能の実装を難しくしている要因です。ソースコード
https://github.com/Sn16799/bakeryFUMIZUKI
Modelのアソシエーション
Controller
app/controllers/orders_controller.rbclass OrdersController < ApplicationController before_action :authenticate_customer! before_action :set_customer def index @orders = @customer.orders end def create if current_customer.cart_items.exists? @order = Order.new(order_params) @order.customer_id = current_customer.id # 住所のラジオボタン選択に応じて引数を調整 @add = params[:order][:add].to_i case @add when 1 @order.post_code = @customer.post_code @order.send_to_address = @customer.address @order.addressee = full_name(@customer) when 2 @order.post_code = params[:order][:post_code] @order.send_to_address = params[:order][:send_to_address] @order.addressee = params[:order][:addressee] when 3 @order.post_code = params[:order][:post_code] @order.send_to_address = params[:order][:send_to_address] @order.addressee = params[:order][:addressee] end @order.save # send_to_addressで住所モデル検索、該当データなければ新規作成 if Address.find_by(address: @order.send_to_address).nil? @address = Address.new @address.post_code = @order.post_code @address.address = @order.send_to_address @address.addressee = @order.addressee @address.customer_id = current_customer.id @address.save end # cart_itemsの内容をorder_itemsに新規登録 current_customer.cart_items.each do |cart_item| order_item = @order.order_items.build order_item.order_id = @order.id order_item.product_id = cart_item.product_id order_item.quantity = cart_item.quantity order_item.order_price = cart_item.product.price order_item.save cart_item.destroy #order_itemに情報を移したらcart_itemは消去 end render :thanks else redirect_to customer_top_path flash[:danger] = 'カートが空です。' end end def show @order = Order.find(params[:id]) if @order.customer_id != current_customer.id redirect_back(fallback_location: root_path) flash[:alert] = "アクセスに失敗しました。" end end def new @order = Order.new end def confirm @order = Order.new @cart_items = current_customer.cart_items @order.how_to_pay = params[:order][:how_to_pay] # 住所のラジオボタン選択に応じて引数を調整 @add = params[:order][:add].to_i case @add when 1 @order.post_code = @customer.post_code @order.send_to_address = @customer.address @order.addressee = @customer.family_name + @customer.first_name when 2 @sta = params[:order][:send_to_address].to_i @send_to_address = Address.find(@sta) @order.post_code = @send_to_address.post_code @order.send_to_address = @send_to_address.address @order.addressee = @send_to_address.addressee when 3 @order.post_code = params[:order][:new_add][:post_code] @order.send_to_address = params[:order][:new_add][:address] @order.addressee = params[:order][:new_add][:addressee] end end def thanks end private def set_customer @customer = current_customer end def order_params params.require(:order).permit( :created_at, :send_to_address, :addressee, :order_status, :how_to_pay, :post_code, :deliver_fee, order_items_attributes: [:order_id, :product_id, :quantity, :order_price, :make_status] ) end endいつものようにform_withで送られた情報をそのまま保存できれば楽なのですが、form_withは確認画面をはさむことができません。そのため、confirm、createアクションではviewから受け取ったパラメータをparams[:hoge]の形で取り出しています。
また、createにおいては一つのアクション内で,「Orderデータを新規作成」「CartItemからOrderItemに商品データを移す(OrderItemの新規作成と登録済みCartItemの削除)」Addressモデル内に一致するデータがない場合のみ新規登録」の3つをこなさなければなりません。一つ一つの動作は複雑ではありませんが、全体のコード量がかなり多くなります。
View
new
app/views/orders/.html.erb<div class="col-lg-10 offset-1 space"> <div class="row"> <div class="col-lg-4"> <h2>注文情報入力</h2> </div> </div> <%= form_with(model: @order, local: true, url: {action: 'confirm'}) do |f| %> <!-- 支払方法 --> <div class="row space"> <h3><strong><%= f.label :支払方法 %></strong></h3> </div> <div class="row"> <div class="col-lg-4 btn-group" data-toggle="buttons"> <label class="btn btn-outline-secondary active" style="width:50%"> <%= f.radio_button :how_to_pay, true, {checked: true} %> クレジットカード </label> <label class="btn btn-outline-secondary" style="width:50%"> <%= f.radio_button :how_to_pay, false, {} %> 銀行振込 </label> </div> </div> <!-- お届け先 --> <div class="row space"> <h3><strong><%= f.label :お届け先 %></strong></h3> </div> <!-- 自身の住所 --> <div class="row"> <p> <label><%= f.radio_button :add, 1, checked: true, checked: "checked" %>ご自身の住所</label><br> <%= @customer.post_code %> <%= @customer.address %> <%= @customer.full_name %> </p> </div> <!-- 登録済み住所 --> <div class="row space-sm"> <p> <label><%= f.radio_button :add, 2, style: "display: inline-block" %>登録住所から選択</label><br> <%= f.collection_select :send_to_address, @customer.addresses, :id, :address %> </p> </div> <!-- 新しい住所 --> <div class="row space-sm"> <p><label><%= f.radio_button :add, 3 %>新しいお届け先</label></p> </div> <div class="row"> <div class="col-lg-12"> <%= f.fields_for :new_add do |na| %> <div class="row"> <div class="col-lg-3"> <strong>郵便番号(ハイフンなし)</strong> </div> <div class="col-lg-6"> <%= na.text_field :post_code, class: 'form-control' %> </div> </div> <div class="row"> <div class="col-lg-3"> <strong>住所</strong> </div> <div class="col-lg-6"> <%= na.text_field :address, class: 'form-control' %> </div> </div> <div class="row"> <div class="col-lg-3"> <strong>宛名</strong> </div> <div class="col-lg-6"> <%= na.text_field :addressee, class: 'form-control' %> </div> </div> <% end %> </div> </div> <!-- お届け先ここまで --> <div class="row space"> <div class="col-lg-2 offset-lg-7"> <%= f.submit "確認画面へ進む", class: "btn btn-danger"%> </div> </div> <% end %> </div>confirm
app/views/orders/.html.erb<div class="col-lg-10 offset-1 space"> <div class="row"> <h2>注文情報確認</h2> </div> <%= form_with(model: @order, local: true) do |f| %> <div class="d-none d-lg-block space"> <div class="row"> <div class="col-lg-5"><h4>商品名</h4></div> <div class="col-lg-2"><h4>単価(税込)</h4></div> <div class="col-lg-2"><h4>数量</h4></div> <div class="col-lg-2"><h4>小計</h4></div> </div> </div> <% sum_all = 0 %> <% @cart_items.each do |cart_item| %> <div class="row space-sm"> <div class="col-lg-3"> <%= link_to product_path(cart_item.product) do %> <%= attachment_image_tag(cart_item.product, :image, :fill, 100, 100, fallback: "no_img.jpg") %> <% end %> </div> <div class="col-lg-2"> <%= link_to product_path(cart_item.product) do %> <%= cart_item.product.name %> <% end %> </div> <div class="col-lg-2"> <%= price_include_tax(cart_item.product.price) %> </div> <div class="col-lg-2"> <%= cart_item.quantity %> </div> <div class="col-lg-2"> <%= sum_product = price_include_tax(cart_item.product.price).to_i * cart_item.quantity %>円 <% sum_all += sum_product %> </div> </div> <% end %> <div class="row space"> <div class="col-lg-12"> <div class="row"> <div class="col-lg-3"> <strong>送料</strong> </div> <div class="col-lg-3"> <%= @order.deliver_fee %>円 </div> </div> <div class="row"> <div class="col-lg-3"> <strong>商品合計</strong> </div> <div class="col-lg-3"> <%= sum_all.to_i %>円 </div> </div> <div class="row"> <div class="col-lg-3"> <strong>ご請求額</strong> </div> <div class="col-lg-3"> <% billling_amount = sum_all + @order.deliver_fee.to_i %> <%= billling_amount.to_i %>円 </div> </div> </div> </div> <div class="row space-sm"> <div class="col-lg-2"> <h3>支払方法</h3> </div> <div class="col-lg-4"> <%= how_to_pay(@order.how_to_pay) %> </div> </div> <div class="row space-sm"> <div class="col-lg-2"> <h3>お届け先</h3> </div> <div class="col-lg-4"> <%= @order.post_code %> <%= @order.send_to_address %> <%= @order.addressee %> </div> </div> <%= f.hidden_field :customer_id, :value => current_customer.id %> <%= f.hidden_field :post_code, :value => "#{@order.post_code}" %> <%= f.hidden_field :send_to_address, :value => "#{@order.send_to_address}" %> <%= f.hidden_field :addressee, :value => "#{@order.addressee}" %> </div> <div class="row space"> <div class="col-lg-2 offset-lg-5"> <%= f.submit "購入を確定する", class: "btn btn-danger btn-lg" %> </div> </div> <% end %> </div>thanks
app/views/orders/.html.erb<div class="col-lg-10 offset-1 space"> <h2>ご購入ありがとうございました!</h2> <h3><%= link_to 'TOPへ戻る', customer_top_path %></h3> </div>「購入を確定する」ボタンを押した後に遷移するページです。静的なページでこれといった機能はないので、上記のように質素な文言だけ載せるも良し、スライダーなどで写真を表示するも良しです。
index
app/views/orders/.html.erb<div class="col-lg-10 offset-1 space"> <div class="row"> <h2>注文履歴一覧</h2> </div> <div class="d-none d-lg-block space"> <div class="row"> <div class="col-lg-2">注文日</div> <div class="col-lg-3">配送先</div> <div class="col-lg-2">注文商品</div> <div class="col-lg-2">支払金額</div> <div class="col-lg-1">状況</div> <div class="col-lg-2">注文詳細</div> </div> </div> <% @orders.each do |order| %> <div class="row space-sm"> <div class="col-lg-2"> <%= simple_time(order.created_at) %> </div> <div class="col-lg-3"> <div class="row"> <%= order.post_code + " " + order.send_to_address %> </div> <div class="row"> <%= order.addressee %> </div> </div> <div class="col-lg-2"> <% sum_all = 0 %> <% order.order_items.each do |order_item| %> <%= order_item.product.name %><br> <% sub_total = price_include_tax(order_item.order_price).to_i * order_item.quantity %> <% sum_all += sub_total.to_i %> <% end %> </div> <div class="col-lg-2"> <%= sum_all += order.deliver_fee.to_i %>円 </div> <div class="col-lg-1"> <%= order_status(order) %> </div> <div class="col-lg-2"> <%= link_to '表示する', order_path(order), class: "btn btn-sm btn-danger" %> </div> </div> <% end %> </div>show
app/views/orders/.html.erb<div class="col-lg-10 offset-1 space"> <div class="row"> <h2>注文履歴詳細</h2> </div> <div class="row"> <div class="col-lg-7"> <div class="row space"> <h3>注文情報</h3> </div> <div class="row"> <div class="container"> <div class="row space-sm"> <div class="col-lg-3"> <strong>注文日</strong> </div> <div class="col-lg-9"> <%= simple_time(@order.created_at) %> </div> </div> <div class="row space-sm"> <div class="col-lg-3"> <strong>配送先</strong> </div> <div class="col-lg-9"> <%= @order.send_to_address %> </div> </div> <div class="row space-sm"> <div class="col-lg-3"> <strong>支払方法</strong> </div> <div class="col-lg-9"> <%= how_to_pay(@order.how_to_pay) %> </div> </div> <div class="row space-sm"> <div class="col-lg-3"> <strong>状況</strong> </div> <div class="col-lg-9"> <%= order_status(@order) %> </div> </div> </div> </div> <div class="row space"> <h3>注文内容</h3> </div> <div class="d-none d-lg-block"> <div class="row"> <div class="col-lg-4"> <strong>商品</strong> </div> <div class="col-lg-3"> <strong>単価(税込)</strong> </div> <div class="col-lg-2"> <strong>個数</strong> </div> <div class="col-lg-2"> <strong>小計</strong> </div> </div> </div> <% sum_all = 0 %> <% @order.order_items.each do |order_item| %> <div class="row space-sm"> <div class="col-lg-4"> <%= order_item.product.name %> </div> <div class="col-lg-3"> <%= price_include_tax(order_item.order_price) %> </div> <div class="col-lg-2"> <%= order_item.quantity %>個 </div> <div class="col-lg-2"> <%= sub_total = price_include_tax(order_item.order_price).to_i * order_item.quantity %>円 <% sum_all += sub_total %> </div> </div> <% end %> </div> <div class="col-lg-5"> <div class="row space"> <h3>請求情報</h3> </div> <div class="row"> <div class="container"> <div class="row space-sm"> <div class="col-lg-6"> <strong>商品合計</strong> </div> <div class="col-6"> <%= sum_all %>円 </div> </div> <div class="row space-sm"> <div class="col-lg-6"> <strong>配送料</strong> </div> <div class="col-lg-6"> <%= @order.deliver_fee %>円 </div> </div> <div class="row space-sm"> <div class="col-lg-6"> <strong>ご請求額</strong> </div> <div class="col-lg-6"> <%= sum_all + @order.deliver_fee.to_i %>円 </div> </div> </div> </div> </div> </div> </div>上図において、「請求情報」欄に商品の合計額を表示したいのですが、上から順番に画面を構築すると「請求情報」と「注文内容」でそれぞれループを回す必要が出てきて二度手間になってしまいます。そこで、ズルいやり方だよなと思いつつ、colで画面を縦に2分割してまず「注文情報」「注文内容」の欄を作り、「注文内容」のループ内でついでに合計額を計算して変数に格納し、「請求情報」欄で変数を呼び出す、ということをしています。
(「請求情報」欄において、DB内で配送料のdefault値を設定し忘れたので商品合計とご請求額が同じ金額になっています。本来は商品合計に配送料800円がご請求額に加算される仕様で、viewのコードもそれに対応したものとなっています。)後記
このECサイトの中枢機能だけあって、コード量も多いし内容も複雑でしたね。今回の実装では、おおよその部分では以前私がスクールのチーム実装で作ったものを踏襲しつつ、ヘルパーやモデルのメソッドを新たに定義してコードを見やすくしたり、画面をレスポンシブ対応にしたりといった改変を加えました。
createアクションで新しい住所を選択したらOrderと同時にAddressにも住所データが登録される仕様について追記です。上記の記述ではAddressモデルのaddressカラムを検索して、一致するものがなければ登録する流れになっています。より正確な検索をするなら、
unless Address.find_by(addressee: @order.addressee, address: @order.send_to_address).exists?として、宛名・住所ともに一致するかを確かめても良いかも知れません。コードは余計に長くなりますが。
何はともあれ、これでcustomerサイトの機能は揃いました。めでたしめでたし。
- 投稿日:2020-07-14T19:06:48+09:00
【Prefix】
Prefixとは
ルーティングを確認する際にrails routesコマンドを実行すると上の画像のような画面が表示されるが、上の画像の一番左の項目のことであり、何を表しているかというと真ん中あたりの項目のURI patternのルーティングを変数にしたもの。
なのでlink_toメソッドを使用する際の遷移先の記述を以下のように
としてルーティングにURI patternを記述していたところを以下のように
のようにPrefixで記述することができる。
二つとも遷移先は全く同じ。tweet_pathというprefixの引数であるtweet.idは今回の場合はdestroyアクションへのルーティングなのでどのツイートを削除するのかという情報が必要なため該当のツイートのidを引数に指定している。
- 投稿日:2020-07-14T17:25:22+09:00
【ストロングパラメーター】
ストロングパラメーターとは何か
コントローラーにアクションを定義する際に出てくるストロングパラメータとは何か解説していきます。
createアクションの実行内容の部分にcreate(tweet_params)と記述がありますがこの引数であるtweet_paramsはprivateメソッドの中に定義されています。
privateメソッド
privateとはクラスの外から呼び出すことのできないメソッドのことです。
privateメソッドを使うことのメリットは以下の二点です1.classの外部から呼び出されると困るメソッドを隔離する
メソッドの中にはclassの外部から呼び出されるとエラーを起こすメソッドもあるため隔離しておくことでエラーを事前に防ぐことができる。
2.コードの可読性を高める
privateとそうでない部分を明確に分離することでコードとしての可読性が上がる
このprivateメソッドの中に
tweet_paramsというメソッドが定義されており、処理の内容は以下のようになっています。requireの引数にtweetモデルを取り、
permitの引数に:name :image :textを取っています。この意味はフォームから送られてきたデータのうち、:name :image :textというpermit以下で指定したキーを持つパラメーターのみを受け取るように制限するということです。
この指定したキーを持つパラメーターのみを受け取る仕組みのことをストロングパラメータといいます。
ストロングパラメータを指定しておくことによって仕様以外のパラメータが送信されてくることを防ぎ、意図しないデータの更新をされないようにすることができます。
例えば、他人のログインパスワードを更新するパラメータを送信すれば勝手に他人のパスワードを変更できてしまうため、
こういったことを防ぐためにストロングパラメータを使用する必要があります。再度コードを見ていくと
createメソッドは引数にtweet_paramsを指定しているため、tweet_paramsメソッドを経由して新たなデータが作成、保存される。
そのためこの場合は新しいツイートは必ずpermitの引数に指定したストロングパラメータのみを持っているということになります。
- 投稿日:2020-07-14T16:49:10+09:00
【Rails】form_withに記述するlocal: trueについて
- 投稿日:2020-07-14T15:09:32+09:00
macOS CatalinaにRailsをインストールする
はじめに
macOS CatalinaにRailsをインストールする方法です。
Railsチュートリアルなどでローカル環境にセットアップする場合は、ご参考ください。前提条件
- OS: macOS Catalina
- バージョン: 10.15.5
- Ruby: 2.7.1
- Rails: 5.1.6
インストール手順
1. Command line tools をターミナルからインストール:
xcode-select --install
2. Homebrewをインストール:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
3. rbenvをインストール:
brew update
brew install rbenv
echo 'eval "$(rbenv init -)"' >> ~/.zshrc
source ~/.zshrc
※macOS Catalinaからbashからzshに変更になっています。
4. rbenvを使ってRubyをインストール:
rbenv install 2.7.0
5. デフォルトのRuby を設定:
rbenv global 2.7.0
6. 必要なソフトウェアをインストール:
brew install yarn
7. Railsのインストール:
gem install rails -v 5.1.6
※ターミナルを再起動しないとデフォルトのRubyが呼び出されてエラーになる可能性があります。参考
- 投稿日:2020-07-14T13:51:33+09:00
コードを書くということ Ruby
rubyでコードを書くって行為とは・・・
rubyの書き方、思考方法は本で読んで、取り敢えず動かしてみるだけでは見えてこないものが多いです。他者のコードを読むにも読解力が低い状態だと、習慣的な表現にさえ詰まる。また、こちらがその習慣を知らないと習慣慣れした人たちから逆に自分の書いたコードで驚かれることだってある。前に書いた命名の仕方だったりがそれにあたりますが、今回はそれとはまた別種の学んだものを纏めておこうと思います。
クラスメソッドのself
このクラスメソッドに書かれてるselfってなんなのか・・・書かないとそのメソッドを呼ぶこともできない。なんでやねんって話なんですよね。一番最初に思う疑問は。オブジェクト指向にそもそも慣れてないと、ここで躓くのです。
答えはクラスメソッドを示すためのものです。レシーバーがクラス名になるメソッドです。
下記は書籍で見たクラスメソッドの書き方です。呼び出す際に、クラス名であるGreeterをレシーバーにして、greetメソッドを呼んでいます。class_method.rubyclass Greeter class << self def greet puts "おはこんばんちわ" end end end Greeter.greet下記はselfをレシーバーみたいに使った書き方ですね。クラス自身のgreetメソッドということだと思っています。初っ端はこれだけを見たのでなんのこっちゃわかりませんでした。
self.rbclass Greeter def self.greet puts "おはこんばんちわ" end end Greeter.greetクラス自身のgreetメソッド、と捉える理由として、下記の書き方ができるからです。selfではなく、直接クラス名をレシーバーみたいに書いても、greetメソッドを呼び出すことができます。
class.rbclass Greeter def Greeter.greet puts "おはこんばんちわ" end end Greeter.greetではなぜ、クラスメソッドにおいて、selfを使うのか?
- selfを書いておいて、インスタンスメソッドと差異化
- 二度書き防止
の2点にあると思われます。後日、インスタンスメソッドとの違いも記述する予定です。
クラス関係の余談
とあるコードではrailsのActive Supportにおけるclass_attributeにおいても、slefが使用されているのを見たことがあります。これもselfの部分をFirebaseクラスに書き換えてやっても動いたことから、このclass_attributeも呼んで字の如く、Class#に分類されるもののようですね。
firebase.rbclass Firebase require 'google/cloud' class_attribute :contact self.contact = Google::Cloud.new(ENV['GOOGLE_PROJECT']).firestore endロジックの初手は肝要
ビリヤード台で左上から右下45度でボールを発射して壁で理想的な反射をする場合、ボールはどの座標を通って何ステップでコーナーの穴に落ちるか
ゲームプログラミングのコードを課題として頂いた時(ゲームプログラミングであることは後になって気づいたことですが)に、今まで有限ループでのロジック組みに取り組んでいたために、(例えば、1ヶ月のユーザー登録者数を計算するので、DB上から取り出したユーザーの分だけループさせたいのような)この手の無限ループロジックの発想が出てこなかった。そも思考ロジックの土台すらままなっておらず、この課題は答え合わせになるまで解く事ができなかったものです。
billiard.rbx_max = ARGV[1] y_max = ARGV[0] if !x_max || !y_max puts "引数を指定してください" exit 1 end x_max = x_max.to_i y_max = y_max.to_i #状態 x = 1 y = 1 step = 1 x_way = 1 y_way = 1 puts " #{step} (#{x},#{y})" x += 1 y += 1 loop do step += 1 puts " #{step} (#{x},#{y})" if y == 1 && x == x_max || x == 1 && y == y_max || x == 1 && y == 1 || x == x_max && y == y_max puts "GOAL!!" break elsif x == x_max x_way = -1 elsif y == y_max y_way = -1 elsif x == 1 x_way = 1 elsif y == 1 y_way = 1 end x += x_way y += y_way end以下、学んだものを纏めます。
状態(state)管理
プログラムは扱うデータの状態変化である。
今回のビリヤード問題において具体的化させると、
- 引数で与えられた数によって作られる盤面
- ボールの初期値
- ボールの進行方向
何の状態を変化させ、何が固定化されていると目的のコードを作れるかを考える。
境界条件
何がきっかけで、状態の変化に繋がるのか、もしくは終了になるのかを見極めて条件式、状態に対し動きを与える処理を書く。
今回のビリヤード問題において具体的化させると、
- 四方の壁にあるx軸とy軸の方向にある最小値と最大値
ということになります。これを抑えることでどのタイミングでボールの移動状態が変化するかを判断できます。条件式を考える上で、重要になる。ロジックの走りは、境界条件を意識する。
正しい無限ループ
プログラミングにおける無限ループは、
ループを実行しながら終了条件が見えてくるものに関して無限ループを使用する。言い換えると、プログラムを書いてる時に終了条件が明らかに分からないものを利用して無限ループを使用する。
ゲームのプログラムなんてのはまさにそれキャラのダッシュモーションはユーザーの入力に依存するので、入力される限り無限ループさせる必要がある、という事ですね。
無限ループにおける利用はDB関係でも必要になる。大量のデータをループで処理する場合、殆ど無限ループと変わらない数のループを要するので、一定条件を満たしたら、終了するような設計にする必要がある。
感想
ロジックを組むにおいて、そもそも有限ループか、一定条件まで続くループなのか、でコードを書く事ができていなかった故に、今回の課題はろくに辿り着けることができなかったが、重要な思考としての無限ループは理解できてよかった。
- 投稿日:2020-07-14T13:29:10+09:00
1つの商品に複数画像を登録。ちょっと何言ってるか、わかる!!~part3~
前提
- ruby on rails 6.0.0 を使用。
- ユーザー機能はdeviseにより導入されているものとする。
- viewファイルは全てhaml形式とする。
- ちなみに使っているのはMacBook Air(Retina, 13-inch, 2020)です。
はじめに
前回(part2)のあらすじです。carrierwaveのuploaderを使って商品登録の際に画像を投稿できる機能を実装しました。
前置きや手順などは part1 に詳しく記載してあるので気になったら騙されたと思ってクリックしてLGTMを押してみましょう。特に何も起こりません。
前回で画像1枚のみの投稿はできるようになったので今回は最大5枚まで一気に登録できるようにしていきます。手順の3番目にあたる、 「jQueryを導入して複数画像の投稿を実装」ですね。
1つ画像を登録するのに3万年かかったのに5つ登録しようと思ったらそれはもう15万年かかるのでは?(あほ)
そんなことはさておき、
画像を複数投稿する
前置きが長くなってしまいましたが、尻込みしていても仕方がないのでさっそくやっていきましょう。
現在の状態だと、画像のフォームは一つしか表示されておらず、一つ選択してしまったらそれで終わりとなってしまっています。そこで登場するのがjavascript。(今回はjQueryを使います)
最終的に、一つ画像を入力したら新しいフォームが出現し、最大5回まで画像を登録できるといった状態ができればゴールとします。jQueryについて
まずはjQueryの導入からですが、Rails6において、私の知る限りjQueryの導入方法には2つの方向性があります。
- gemとしてjQueryをインストールする方法
- webpackerを通してjQueryを呼び出す方法
今回は先述のgemを通した方法でやりました。混乱する人も多いと思うのでjQueryの導入方法は番外編として後ほど投稿しようと思います。というか僕が大混乱しました。
時を飛ばそう
というわけで、無事jQueryが導入できたていで進めていきましょう。
app/views/products/_form.html.haml= form_with model: @product, local: true do |f| = f.text_field :name, placeholder: 'name' #image-box = f.fields_for :images do |i| .group{ data: { index: i.index } } = i.file_field :src, class: 'file' = f.submit 'SEND'まずはformのビューファイルにクラスやIDをつけていきます。{ data: ~ } の部分はカスタムデータ属性の指定です。他は特に問題ないと思います。基礎的なことをhamlで書いているだけです。
app/assets/javascripts/product.js$(function() { const buildFileField = (index)=> { const html = `<div data-index="${index}" class="group"> <input class="file" type="file" name="product[images_attributes][${index}][src]" id="product_images_attributes_${index}_src"><br> <div class="remove">削除</div> </div>`; return html; } let fileIndex = [1,2,3,4,5]; $('#image-box').on('change', '.file', function(e) { $('#image-box').append(buildFileField(fileIndex[0])); fileIndex.shift(); fileIndex.push(fileIndex[fileIndex.length - 1] + 1) }); $('#image-box').on('click', '.remove', function() { $(this).parent().remove(); if ($('.file').length == 0) $('#image-box').append(buildFileField(fileIndex[0])); }); });こちらがjQueryの記述となります。番外編で詳しく書きますが、Rails6のassetsフォルダにjavascriptはないので、自分で作成していただく必要があります。
それでは順を追って解説していきましょう。
let fileIndex = [1,2,3,4,5]; $('#image-box').on('change', '.file', function(e) { // ファイルが選択されたときfileIndexの最初の数字をindexとして持ったフォームを新しく作成する。 $('#image-box').append(buildFileField(fileIndex[0])); // fileIndexの最初の数字を削除して数字をひとつずつ左へずらす。 fileIndex.shift(); // fileIndexの最後の数字に1を足した数字を最後尾に挿入する。 fileIndex.push(fileIndex[fileIndex.length - 1] + 1) });まずはこちら、表示されているフォームで画像を選択した際に、新しいフォームが表示される。といった処理ですね。一行ずつの考え方を追記しておきました。これを繰り返すことで、ひとつ一つのフォームに固有のindexを持たせることができます。
const buildFileField = (index)=> { const html = `<div data-index="${index}" class="group"> <input class="file" type="file" name="product[images_attributes][${index}][src]" id="product_images_attributes_${index}_src"><br> <div class="remove">削除</div> </div>`; return html; }先ほどの buildFileField の処理を記述したものです。先ほど作った固有のindexを引数として渡しています。``で囲われている部分は、_form.html.haml で記述されていたフォームをhtmlとして記述し直し、あとで参照できるようにidとnameをつけているだけです。
$('#image-box').on('click', '.remove', function() { // クリックされた.removeの親要素を削除する。 $(this).parent().remove(); // フォームの数が0になった際、新しいフォームを表示させる。 if ($('.file').length == 0) $('#image-box').append(buildFileField(fileIndex[0])); });最後にフォームを削除する処理ですね。こちらも一行ずつ書いておきました。
さて、ここまでで登録画面における処理はひとまずできました。jQueryの書き方さえ分かっていれば特に難しいこともなかったかと思います。
そういえばfileやremoveなど、一部メソッドなのかクラスなのかがわかりにくかったかもしれませんが、'.~'という形で記述されているのは全てクラスになります。慣れれば簡単に見分けられます。最後に
大袈裟な前置きをしていましたが、実はデータベースへの複数登録自体は前回で終わっているのです。なので今回はフロントにおける作業がメインでしたね。
ようやく形になってきました。今回で画像の複数登録はできたので、次のpartでは複数画像の編集を行っていきたいと思います。
ではまた次のpartでお会いしましょう。
- 投稿日:2020-07-14T13:04:26+09:00
[Rails]ransackで関連するモデル(親や子)のカラムをまたいで検索する方法
実現したいこと
1つのモデルに関連(ネスト)するモデルのカラムまで、検索対象にしたい
具体的には、古着屋の店舗名だけでなく、エリア名(1対多)や取り扱いブランド名(多対多)まで含めて一括検索したい。
結論
フォームタグの要素名に、
関連するモデル名_関連するモデルのカラム名
を指定する関連するモデルが、
対1
(belongs_to: hoge、やhas_one: fuga)例えば、shopモデルに紐づくareaモデルのエリア名(name)を検索条件にしたい時
= f.フォームヘルパー :要素名
の要素名
をarea_name_cont
とする。分解すると
area → 関連するモデル名
name → 関連するモデルのカラム名
cont → 部分一致を指定する述語となります。
= search_form_for(@q, url: shop_search_path) do |f| = f.text_field :area_name_cont # shopモデルに紐づくareaモデルの、エリア名(name)関連するモデルが、
対多
(has_many: hogesなど)例えば、shopモデルに紐づくbrandsモデルのブランド名(name)を検索条件にしたい時
= f.フォームヘルパー :要素名
の要素名
をbrands_name_cont
とする。分解すると
brands → 関連するモデル名 ※shop has_many: brandsなので複数形
name → 関連するモデルのカラム名
cont → 部分一致を指定する述語となります。
= search_form_for(@q, url: shop_search_path) do |f| = f.text_field :brands_name_or_genres_name_cont # shopモデルに紐づくbrandモデルの、ブランド名(name) # shopモデルに紐づくgenreモデルの、ジャンル名(name) # ※紐づくモデルが複数の時は、モデル名が複数形になることに注意ちなみに、
_or_
などでカラム名を繋ぐと、複数カラムを検索対象にできます。実際のransackの使い方などについては、[Rails]ransackを利用した色々な検索フォーム作成方法まとめなどの記事を参考にしてください。
モデル間のアソシエーション
※関連する箇所のみ記載
shop.rb# shopモデル belongs_to :area, optional: true has_many :shop_genres has_many :shop_brands has_many :genres, through: :shop_genres has_many :brands, through: :shop_brandsarea.rb# areaモデル has_many :shopsbrand.rb# brandモデル has_many :shop_brands has_many :shops, through: :shop_brandsshop_brand.rb# shop_brandモデル belongs_to :shop belongs_to :brandshop_genre.rb# shop_genreモデル belongs_to :shop belongs_to :genre関連を調べる方法
ransackable_associations
というメソッドを使うと便利です。1. アプリケーションディレクトリでrails c
terminal# 該当のアプリケーションディレクトリで実行 $ rails c Running via Spring preloader in process 61541 Loading development environment (Rails 5.0.7.2) [1] pry(main)>2.
モデル名.ransackable_associations
を実行terminal# 今回はShopモデルとの関連を調べたいので、Shop.ransackable_associationsとすると Shopモデルに紐づくモデルが表示される [1] pry(main)> Shop.ransackable_associations => ["user", "area", "shop_genres", "shop_brands", "genres", "brands"] [2] pry(main)>参考
Ransackで簡単に検索フォームを作る73のレシピ -026 関連
Ransackで親テーブルや子テーブルのカラムで複数検索する方法
- 投稿日:2020-07-14T12:48:51+09:00
【随時更新】Ruby on Rails 便利なメソッド
- 投稿日:2020-07-14T12:32:24+09:00
Digdag公式ドキュメントからDigdagを学ぶ-Language API-Ruby
目標
Digdagの公式サイトのドキュメントのOperatorsの翻訳+α
DigdagのRubyを使ってRailsにバッチを作るまでが最後の目標
http://docs.digdag.io/operators/scripting.htmlhttp://docs.digdag.io/operators/scripting.html目次
Getting started
Architecture
Concepts
Workflow definition
Scheduling workflow
Operators
Command reference
Language API -RubyLanguage API-Ruby
Programmable workflow in Ruby
workflow1.dig_export: rb: require: 'tasks/my_workflow' +step1: rb>: MyWorkflow.step1 +step2: rb>: MyWorkflow.step2my_workflow.rbclass MyWorkflow def step1 puts "ruby step1" end def step2 puts "ruby step2" end end結果$ digdag run workflow1.dig --rerun 2020-07-12 17:37:09 +0900 [INFO] (0017@[0:default]+workflow1+step1): rb>: MyWorkflow.step1 ruby step1 2020-07-12 17:37:10 +0900 [INFO] (0017@[0:default]+workflow1+step2): rb>: MyWorkflow.step2 ruby step2Defining variables
step1でmy_valueに1を保存してstep2では保存した変数を出力する
class MyWorkflow def step1 Digdag.env.store(my_value: 1) end def step2 puts "step2: %s" % Digdag.env.params['my_value'] end endMethod argument mapping
step1で設定した変数はstep2で関数のパラメーターとして受け取れる
my_workflow.rbclass MyWorkflow def step1 Digdag.env.store(my_value1: 1) Digdag.env.store(my_value2: 2) end def step2(my_value1: 0, my_value2: 0) puts "my_value1: #{my_value1} my_value2: #{my_value2} " end end結果$ digdag run workflow1.dig --rerun 2020-07-12 17:47:41 +0900 [INFO] (0017@[0:default]+workflow1+step1): rb>: MyWorkflow.step1 2020-07-12 17:47:42 +0900 [INFO] (0017@[0:default]+workflow1+step2): rb>: MyWorkflow.step2 my_value1: 1 my_value2: 2Generating child tasks
Digdag.env.add_subtaskを使ってRubyでサーブタスクを生成可能です。
my_workflow.rbclass MyWorkflow def step1 puts "step1 start" Digdag.env.add_subtask(MyWorkflow, :step3, arg1: 3) Digdag.env.store(my_value: 2) end def step2(my_value: "default") puts "step2: %s" % my_value end def step3(arg1:) puts "step3: %s" % arg1 end end結果を見るとわかると思いますがstep1が実行される時、step1で追加したサブタスクstep3が実行される。
結果$ digdag run workflow1.dig --rerun 2020-07-12 17:56:27 +0900 [INFO] (0017@[0:default]+workflow1+step1): rb>: MyWorkflow.step1 step1 start 2020-07-12 17:56:27 +0900 [INFO] (0017@[0:default]+workflow1+step1^sub+subtask0): rb>: ::MyWorkflow.step3 step3: 3 2020-07-12 17:56:28 +0900 [INFO] (0017@[0:default]+workflow1+step2): rb>: MyWorkflow.step2 step2: 2これでDigdagとRubyとの連携についての説明は完了です。
次回からRubyでバッチを作ってDigdag上で実行してみたいと思います。
- 投稿日:2020-07-14T11:19:35+09:00
Rubyで配列の中のハッシュの値を取り出す方法
久しぶりにRubyに触れたらびっくりするぐらいわからなかったので調べました。
こんな気持ち悪い配列とハッシュの組み合わせの場合。
test.rbuser_data = [ { user: { profile: { name: 'George', sex: 'man' } } }, { user: { profile: { name: 'Alice', sex: 'woman' } } }, { user: { profile: { name: 'Taro', sex: 'man' } } } ]配列の中に、userというハッシュがあり、更にネストしてprofileというハッシュがあり、nameというキーに値が入ってる・・・もはやわけがわからんですね。
ではこれを取り出してみます。
'George'を取り出す。
こんな感じ
test.rbputs user_data[0][:user][:profile][:name]'Alice'を取り出すなら当然こんな感じ
test.rbputs user_data[1][:user][:profile][:name]配列の位置を変えただけ。
'Alice'のハッシュ内にある性別を取り出す場合は
test.rbputs user_data[1][:user][:profile][:sex]:nameを:sexに変えただけ。
配列内にある:nameをすべて取り出す
配列なのでeachを使って1つずつ取り出す。
test.rbuser_data.each do |values| puts values[:user][:profile][:name] end結果はこうなる
test.rbGeorge Alice Tarodigメソッドを使う場合
Ruby 2.3以降からはハッシュクラスで新しく使えるメソッドとしてdigというものが用意されています。
digメソッドで'Gorge'を取り出してみる
test.rbputs user_data.dig(0, :user, :profile, :name)配列に含まれたハッシュなら、何番のデータがほしいか引数に入れるだけ。
digメソッドで ':name'全て取り出してみる
test.rbuser_data.each do |values| puts values.dig(:user, :profile, :name) enddigメソッドを使うメリット
キーが見つからない場合にエラーで返すかnilで返すかの違いのようです。digを使えばnilで返してくれるのでエラーへの対策が不要になるとか。(それまではfetchメソッドを使うなどしていたそうです)
test.rbuser_data = [ {user: {}}, {user: {}}, {user: {}} ] # digを使った場合 user_data.each do |values| puts values.dig(:user, :profile, :age) end # digを使わなかった場合 user_data.each do |values| puts values[:user][:profile][:age] endこんな感じの結果となる
test.rb% ruby tmp/test.rb #=> nil #=> undefined method `[]' for nil:NilClass (NoMethodError)
- 投稿日:2020-07-14T09:44:47+09:00
【rspec】fill_inがあっているのにfetureテストが成功しない原因の1つ
rspecのfeatureテストが通らない
capybaraでわかりやすくて便利なのですが、fill_inハマったので内容をまとめます。
expect内のfiii_inのところでどうしてもエラーが起きてしまう。
検証して生成されたhtmlから見つけたidを指定しても、新たにidを作っても、findを使ってもできませんでした
。。しかし数時間の格闘の末結論が出ました。
この記事のゴール
fill_inのエラー解決策の1つになれば幸いです。
結論 ログインに失敗している
失敗したコード
topics_spec.rbscenario "user creates a new topic" do user = FactoryBot.create(:user) visit root_path click_link "ログイン" fill_in "Email", with: user.email fill_in "Password", with: user.password ココ→ click_link "ログイン" expect { visit new_topic_path fill_in "topic_description", with: "test" click_button "投稿" expect(page).to have_content "投稿しました" expect(page).to have_content "test" expect(page).to have_content "#{user.name}" }.to change(user.topics, :count).by(1)click_linkでログインを指定ためsubmitではなくヘッダーのリンクをクリックしていました。
そのためログインページでメールアドレスとパスワードを入力して、もう一度ログインページをクリックしていました。。
なんと不毛なことを。。。。ということでclick_linkではなく、click_buttonを指定。
topics_spec.rbscenario "user creates a new topic" do user = FactoryBot.create(:user) visit root_path click_link "ログイン" fill_in "Email", with: user.email fill_in "Password", with: user.password ココ→ click_button "ログイン" expect { visit new_topic_path fill_in "topic_description", with: "test" click_button "投稿" expect(page).to have_content "投稿しました" expect(page).to have_content "test" expect(page).to have_content "#{user.name}" }.to change(user.topics, :count).by(1)これでちゃんと通りました。
まとめ
fill_inでエラーが起きているのでfill_inばかり見ていましたが、一度立ち止まって全体を見ることの重要性を再認識できました。
参考になれば嬉しいです!
- 投稿日:2020-07-14T09:44:47+09:00
【rspec】fill_inがあっているのにfetureテストが失敗する原因 ログインで失敗してない?
rspecのfeatureテストが通らない
capybaraでわかりやすくて便利なのですが、fill_inハマったので解決策の1つを紹介します。
expect内のfiii_inのところでどうしてもエラーが起きてしまう。
検証して生成されたhtmlから見つけたidを指定しても、新たにidを作っても、findを使ってもできませんでした
。。しかし数時間の格闘の末結論が出ました。
この記事のゴール
fill_inのエラー解決策の1つとして検証してみてください!
結論 ログインに失敗している
自分の場合は結論ログインがうまくできていないためエラーが起きていました。
失敗したコード
topics_spec.rbscenario "user creates a new topic" do user = FactoryBot.create(:user) visit root_path click_link "ログイン" fill_in "Email", with: user.email fill_in "Password", with: user.password ココ→ click_link "ログイン" expect { visit new_topic_path fill_in "topic_description", with: "test" click_button "投稿" expect(page).to have_content "投稿しました" expect(page).to have_content "test" expect(page).to have_content "#{user.name}" }.to change(user.topics, :count).by(1)click_linkでログインを指定ためsubmitではなくヘッダーのリンクをクリックしていました。
そのためログインページでメールアドレスとパスワードを入力して、もう一度ログインページをクリックしていました。。
なんと不毛なことを。。。。ということでclick_linkではなく、click_buttonを指定。
topics_spec.rbscenario "user creates a new topic" do user = FactoryBot.create(:user) visit root_path click_link "ログイン" fill_in "Email", with: user.email fill_in "Password", with: user.password ココ→ click_button "ログイン" expect { visit new_topic_path fill_in "topic_description", with: "test" click_button "投稿" expect(page).to have_content "投稿しました" expect(page).to have_content "test" expect(page).to have_content "#{user.name}" }.to change(user.topics, :count).by(1)これでちゃんと通りました。
まとめ
fill_inでエラーが起きているのでfill_inばかり見ていましたが、一度立ち止まって全体を見ることの重要性を再認識できました。
参考になれば嬉しいです!
- 投稿日:2020-07-14T09:37:59+09:00
railsにgRPCクラアント導入
経緯
会社でPMを担当しているプロダクト(Rails)の実装で他のサービスから情報を取得する必要があり、そのサービスがOpenAPIでなく、gRPCでAPIインタフェースが定義されていたので、gRPCのクライアントを実装しました。
僕が前職でrailsでのgRPC周りを少し触っていたので、相対的に僕がやった方がチームの開発工数が増えると思ったので自分でやることにしました。
それについてのアウトプットです。実装手順
1. gem導入
※前職で使っていたgrufというライブラリを使いました。
Gemfilegem "google-protobuf" gem "grpc-tools" gem "gruf"※ webというコンテナで開発してる
docker-compose run --rm web bundle install2. protoファイルが管理されているリポジトリをsubmoduleでアプリケーションのサブディレクトリとして登録して、protoファイルを元のリポジトリから取ってくる
$ git submodule add [web URL or ssh key] proto $ git submodule init $ cd proto $ git submodule update3. protoファイルをrubyファイルにコンパイル
docker-compose run --rm web grpc_tools_ruby_protoc -I [コンパイル対象のprotoディレクトリ] --ruby_out=[コンパイル後のファイル保存先のディレクトリ] --grpc_out=[コンパイル後のファイル保存先のディレクトリ] [コンパイル対象のprotoディレクトリ内の対象ファイル]4. コンパイル後のrubyファイル(*_pb.rb)の読み込み設定
コンパイル後のファイルは、ファイル名と、クラス名が噛み合っておらず、Railsの読み込み規則に則っていなく自動読み込まれないので、指定してあげる必要がある。
config/initializers/gruf.rbrequire "gruf" Gruf.configure do Dir.glob(Rails.root.join("[コンパイル後のファイル保存先のディレクトリ]/*_pb.rb")).each do |file| require file end endコンパイル後のファイルでは下記のように自動で指定されており、
auto_load_path
に追加しておく必要がある。
e.g. gruf-demoから
require 'Products_pb'
※コンパイル後のファイルは基本修正しないので。
config/application.rbclass Application < Rails::Application config.paths.add [コンパイル後のrubyファイルディレクトリ], eager_load: true end5. クライアントがサーバをコールする部分の実装
ここまでで、全てのコンパイル後のrubyファイルは使えるようになったでのクライアントの実装。
moduleにしようかとか悩みましたが、既存の実装でクライアント系の処理はservice層にまとめていたので、今回もそれに習う形にしました。
※特に
metadata
周りはサーバ側の実装に依存するので注意。
grufのwikiだと、クライアントの初期化(Gruf::Client.new
)時のoptions
引数のキーでusername
を入れていたりとこの辺が今回の実装と違っており、若干悩みました。app/services/grpc_client_service.rbclass GrpcClientService def initialize @metadata = { login: ENV["GRPC_CLIENT"], password: ENV["GRPC_PASSWORD"] } end def run(service_klass, method, request) client = Gruf::Client.new( service: service_klass, options: { hostname: ENV["GRPC_HOST"], channel_credentials: :this_channel_is_insecure } ) client.call(method, request.to_h, @metadata) end end導入してみての感想とか
前職でgrufは使っていたので、余裕かと思っていましたが、やはり導入するのと、ただ使うだけでは結構違うなと思いました。
しっかり設定周りのコードを読んでおけば良かったと後悔してます。また、今回gRPCサーバがgoで書かれており、クライアントからのコールがうまく行かないときにコード読むのに苦労して結局諦めたので、goも勉強したいなと思いました。
また少し詰まったのは、クライアントの初期化(
Gruf::Client.new
)時にmetadata
を入れると謎の勘違いしており(本当はcall時の引数に入れる)、ライブラリのWikiに書いてないことはやはり、しっかりコード読まないといけないなと初歩的なことを改めて実感しました。
- 投稿日:2020-07-14T07:30:56+09:00
Rubyで与えられた配列の部分集合を列挙(+α)
競技プログラミングの問題を解いていたら、単に場合の数を数え上げるだけでなく、各場合の状態を作成して処理する必要が時々出てきた。Rubyだと
Array#combination
やArray#repeated_permutation
などが用意されているが、他のが欲しいときにパッと組み立てられず時間を取られてしまっている。なので練習も兼ねて、そのとき欲しかったメソッドを作ってみる。
部分集合
総当たり的に解きたいときなど、各要素について含む/含まないパターンを網羅したいことがある。場合の数は
2 ** ary.size
。
Array#combination
を利用要素数は
0
からary.size
まで考えられるので、それぞれについて組合せを列挙すればいい。楽なうえほとんどの処理をRubyの内部実装に任せられるため速い。each_subset.rbdef each_subset(ary, &block) 0.upto(ary.size) { |n| ary.combination(n, &block) } end if $0 == __FILE__ each_subset([*0..2]) { |subset| p subset } endoutput[] [0] [1] [2] [0, 1] [0, 2] [1, 2] [0, 1, 2]二進表記を利用
列挙の順番
k
(0始まり)を二進表記したときの1
0
がそのまま各要素の含む/含まないに対応するようにしてみる。素直にやるならこんな感じ。Rubyは
Integer#[]
でi
ビット目の値を取得できる。each_subset.rbdef each_subset(ary, &block) (1 << ary.size).times do |k| subset = ary.select.with_index { |_, i| k[i] == 1 } block.call(subset) end end if $0 == __FILE__ each_subset([*0..2]) { |subset| p subset } endoutput[] [0] [1] [0, 1] [2] [0, 2] [1, 2] [0, 1, 2]
ary
の先頭要素0
は最下位ビット(=偶奇)に対応させているため有無が毎回切り替わり、一方で末尾要素2
は最上位ビットに対応させているため後半のみに固まって現れている。実装は見ての通り回数固定の二重ループになっていて、時間計算量は O(2N N) 。 2N からすれば N は大したことないので、実用上(要素数が16程度)は問題ないはず。どちらかというと
#select
でひたすら評価していることのほうが気になる。
一応 O(2N) も試しておく。再帰で実装し、
ary
がひとつ少ない場合の処理に落とし込む。※破壊的メソッドを多用してオブジェクトを使い回しているので、安全のため
Array#dup
で保護している。これを全て外せば倍以上速くなる。each_subset.rbdef each_subset(ary, selected = [], &block) if ary.empty? block.call(selected.dup) # 配列が同一オブジェクトだと危険すぎるので複製 return end ary = ary.dup # 入力配列を壊さないように複製 elem = ary.pop each_subset(ary, selected, &block) # elem を含まない selected.unshift(elem) each_subset(ary, selected, &block) # elem を含む selected.shift # ary << elem # 複製してあれば不要 return # ひとまず nil を返す end if $0 == __FILE__ each_subset([*0..2]) { |subset| p subset } end分割
ary = [0, 1, 2, 3, 4]
に対して、例えば[[0, 1], [2], [3, 4]]
などを作りたい。見方を変えると、「配列の各要素間について区切りを入れる/入れない」ということなので、場合の数は
2 ** (ary.size - 1)
。空配列のときは何を返すべきか謎なので、実装に都合良い形にする。こちらも再帰で実装する。
ary
から何個かをグループ化して抽出すれば、残ったものでまたグループを作ればいい。each_split.rbdef each_split(ary, selected = [], &block) if ary.empty? block.call(selected.map(&:dup)) # 配列が同一オブジェクトだと危険すぎるので複製 return end # ary == part1 + part2 を保つように操作していく part1 = [] part2 = ary.dup # 入力配列を壊さないように複製 selected << part1 until part2.empty? part1 << part2.shift # 分割位置を右へずらす each_split(part2, selected, &block) end selected.pop # ary.replace(part1) # 複製してあれば不要 return # ひとまず nil を返す end if $0 == __FILE__ each_split([*0..4]) { |sp| p sp } endoutput[[0], [1], [2], [3], [4]] [[0], [1], [2], [3, 4]] [[0], [1], [2, 3], [4]] [[0], [1], [2, 3, 4]] [[0], [1, 2], [3], [4]] [[0], [1, 2], [3, 4]] [[0], [1, 2, 3], [4]] [[0], [1, 2, 3, 4]] [[0, 1], [2], [3], [4]] [[0, 1], [2], [3, 4]] [[0, 1], [2, 3], [4]] [[0, 1], [2, 3, 4]] [[0, 1, 2], [3], [4]] [[0, 1, 2], [3, 4]] [[0, 1, 2, 3], [4]] [[0, 1, 2, 3, 4]]
- 投稿日:2020-07-14T03:47:01+09:00
Twitterをスクレイピングしてもっとも古いぴえんツイートを見付ける
Twitterをスクレイピングして過去のツイートを取得できる twitterscraper-ruby gem を作成しました。
このtwitterscraper-ruby gemを使えば「もっとも古いツイート」や「ある単語をツイートした最初の人」を簡単に見付けることができます。
告知
SNSデータを用いた分析、Ruby on Railsを用いたWeb開発について、ご依頼やご相談は @ts_3156 までお気軽にご連絡ください。
なぜいまさらスクレイピングなのか
ツイートを大量に取得する方法は、大別するとTwitter Search API(無料版)、Twitter Search API(有料版)、Twitterのスクレイピングの3通りがあります。
Twitter Search API(無料版)
おそらく世の中の99%の方はこの方法を使ってツイートを取得しています。ツイッターが提供しているAPIなので安心して利用できる一方、利用回数に強力な制限があり、また、最大のデメリットとして「直近の7日間のツイート」しか取得できません。このため、「最近のツイートを少しだけ取得する」ことしかできません。
Twitter Search API(有料版)
このAPIを使うと、
月々あたり数百万円
ほど支払うことで、過去の全てのツイートを検索することができるようになります。利用回数に制限はありますが、比較的緩やかな制限でありツイートの取得という意味では不自由することはなくなります。※有料のAPIには様々な種類があり、正確な名称は異なります
Twitterのスクレイピング
twitterscraper-ruby gemが利用している方法です。Twitter Search APIのデメリットである利用回数の制限や対象期間の制限を気にすることなく、高速に大量のツイートを取得することができます。ただし、スクレイピングは利用規約で明確に禁止されている行為であり、完全に自己責任で行う必要があります。
この記事では、できるだけツイッターに負荷をかけない方法でいくつかの調査を行っています。
「ぴえん」をツイートした最初の人を調べる
最近流行っている「ぴえん」を最初にツイートした人を調べてみました。意外に歴史は古く、2008年5月22日の時点で現在のぴえんとほぼ同じ意味で使った人が見つかりました。
twitterscraper-rubyをインストール後に下記のコマンドを実行すれば、最初の「ぴえん」ツイートを取得することができます。
$ twitterscraper --query 'ぴえん' --start_date 2008-03-21 --end_date 2009-03-21 --lang ja --limit 10 --proxy --threads 10ちなみに、現在のぴえんと違う意味であれば、もっとも古いぴえんは2008年1月24日にツイートされています。ぴえんロー(ピェンロー鍋)やぴえん粥(ヤーピエンジョウ)という食べ物があり、この意味でのツイートでした。
年号として「令和」をツイートした最初の人を調べる
この予言ツイートのことは、2019年の春頃にネットでとてつもなくバズっていたので知っている方も多いと思います。
平成の次の年号として「令和」が最初にツイートされたのは「2016年7月13日」です。twitterscraper gemを使えばこのツイートを簡単に見付けることができます。
twitterscraper-rubyをインストール後に下記のコマンドを実行すれば、令和予言ツイートを取得することができます。
$ twitterscraper --query '令和' --start_date 2016-07-13 --end_date 2016-07-14 --limit 10ちなみに、「年号としての令和」ではなく「ただの文字列としての令和」であれば、もっと前にツイートしている人はたくさんいます。中国語で偶然同じ並びになることがあるようです。
取得できる中でもっとも古いツイートを見付ける
公式のTwitter Searchであれば、最大で「2006-03-21」まで遡ってツイートを取得することができます。
試しに、もっとも古いツイートを取得してみました。その結果、2006年3月22日の
just setting up my twttr
がもっとも古いツイートであることが判明しました。twitterscraper-rubyをインストール後に下記のコマンドを実行すれば、もっとも古いツイートを取得することができます。
twitterscraper --query 'just' --start_date 2006-03-21 --end_date 2006-03-22 --limit 10告知
SNSデータを用いた分析、Ruby on Railsを用いたWeb開発について、ご依頼やご相談は @ts_3156 までお気軽にご連絡ください。
参考リンク
- 投稿日:2020-07-14T03:00:54+09:00
【Rails】セッションタイムアウトがうまくいかない事象の解決
前提
・タイムアウトのメソッドを定義
・コード自体間違っていないにもかかわらず、セッションがnilと判定されてしまい、メソッドがうまく処理されずエラーとなる。タイムアウトのメソッド
sessions_controller.rbdef create # ... session[:last_access_time] = Time.current # ... endapplication_controller.rbTIMEOUT = 5.minutes def time_out if session[:last_access_time] > TIMEOUT.ago session[:last_access_time] = Time.current else session.delete(:user_id) flash[:danger] = "タイムアウトしました。" redirect_to :login end end上記のメソッドを組み、その後ログインを試みるとエラーが発生する。
NoMethodError (undefined method `>=' for nil:NilClass): # session[:last_access_time] がnil判定される。解決方法
ブラウザ側のクッキーを削除することで解決しました。
おそらくログインしている状態でメソッドを定義したから、
「session[:last_access_time]なんかないぞ」って怒られてしまったのかもしれません。