- 投稿日:2019-02-15T23:50:06+09:00
Rubyでnilか空文字を判定する
nil、空文字の判定方法
nilの判定方法
check_nil.rbstr = nil if str.nil? puts "strはnilです。" end # strはnilです。空文字の判定方法
check_empty.rbstr = "" if str.empty? puts "strは空文字です。" end # strは空文字です。nilまたは空文字を判定する
nilと空文字を同時に判定する方法
is_nil_or_empty.rbdef is_nil_or_empty?(str) if str.nil? || str.empty? return true else return false end end check_array = Array.new check_array.push("") check_array.push(nil) check_array.each do |str| if is_nil_or_empty?(str) puts "strはnilまたは空文字です。" end end #strはnilまたは空文字です。 #strはnilまたは空文字です。注意点
if式の条件式は str.nil? || str.empty? とすべきです。
理由は、||演算子は左から右に順に条件式を評価し、結果が真になった時点でif式の中に入りますが、
empty?から先に評価した場合、対象の文字列がnilであればNoMethodErrorを返すためです。
empty?は文字列、配列、ハッシュのみに対応しています。nilには対応していないため、NoMethodErrorが返ってきます。
nil?は全てのオブジェクトに対応しているため、if式の条件式はnil?を左に書くべきです。no_method_error.rbdef is_nil_or_empty?(str) if str.empty? || str.nil? return true else return false end end check_array = Array.new check_array.push("") check_array.push(nil) check_array.each do |str| if is_nil_or_empty?(str) puts "strはnilまたは空文字です。" end end #undefined method `empty?' for nil:NilClass (NoMethodError)余談
to_sメソッドを使って文字列型に変換することで、nilと空文字を判定することも出来ます。
use_to_s.rbdef is_nil_or_empty?(str) if str.to_s.empty? return true else return false end end check_array = Array.new check_array.push("") check_array.push(nil) check_array.each do |str| if is_nil_or_empty?(str) puts "strはnilまたは空文字です。" end end #strはnilまたは空文字です。 #strはnilまたは空文字です。ただし、UTF-8 でしか動かない処理に投入する場合は、上記の判定方法は使ってはいけない。
https://qiita.com/scivola/items/1f6704f81aba18df9012
- 投稿日:2019-02-15T23:50:06+09:00
Rubyでnilか空文字を判定する方法
nil、空文字の判定
nilであるかを判定
check_nil.rbstr = nil if str.nil? puts "strはnilです。" end # strはnilです。空文字であるかを判定
check_empty.rbstr = "" if str.empty? puts "strは空文字です。" end # strは空文字です。nilまたは空文字かどうかを判定する
nilと空文字を判定する方法
is_nil_or_empty.rbdef is_nil_or_empty?(str) if str.nil? || str.empty? return true else return false end end check_array = Array.new check_array.push("") check_array.push(nil) check_array.each do |str| if is_nil_or_empty?(str) puts "strはnilまたは空文字です。" end end #strはnilまたは空文字です。 #strはnilまたは空文字です。注意点
if式の条件式は str.nil? || str.empty? とすべきです。
理由は、||演算子は左から右に順に条件式を評価し、結果が真になった時点でif式の中に入りますが、
empty?から先に評価した場合、対象の文字列がnilであればNoMethodErrorを返すためです。
empty?は文字列、配列、ハッシュのみに対応しています。nilには対応していないため、NoMethodErrorが返ってきます。
nil?は全てのオブジェクトに対応しているため、if式の条件式はnil?を左に書くべきです。no_method_error.rbdef is_nil_or_empty?(str) if str.empty? || str.nil? return true else return false end end check_array = Array.new check_array.push("") check_array.push(nil) check_array.each do |str| if is_nil_or_empty?(str) puts "strはnilまたは空文字です。" end end #undefined method `empty?' for nil:NilClass (NoMethodError)Ruby on Railsの場合
blank?を使用することで、nilと空文字を評価することが出来ます。
なお、blank?の否定(nilかつ空文字でない)はpresent?を使用します。ruby_on_rails.rbdef is_nil_or_empty?(str) if str.blank? return true else return false end end check_array = Array.new check_array.push("") check_array.push(nil) check_array.each do |str| if is_nil_or_empty?(str) puts "strはnilまたは空文字です。" end end #strはnilまたは空文字です。 #strはnilまたは空文字です。余談
to_sメソッドを使って文字列型に変換することで、nilと空文字を評価することも出来ます。
use_to_s.rbdef is_nil_or_empty?(str) if str.to_s.empty? return true else return false end end check_array = Array.new check_array.push("") check_array.push(nil) check_array.each do |str| if is_nil_or_empty?(str) puts "strはnilまたは空文字です。" end end #strはnilまたは空文字です。 #strはnilまたは空文字です。ただし、UTF-8 でしか動かない処理に投入する場合は、上記の方法は使ってはいけない。
https://qiita.com/scivola/items/1f6704f81aba18df9012
- 投稿日:2019-02-15T23:50:06+09:00
Rubyでnilか空文字列を判定する方法
nil、空文字列の判定
nilであるかを判定
check_nil.rbstr = nil if str.nil? puts "strはnilです。" end # strはnilです。空文字列であるかを判定
check_empty.rbstr = "" if str.empty? puts "strは空文字列です。" end # strは空文字列です。nilまたは空文字列かどうかを判定する
nilと空文字列を判定する方法
is_nil_or_empty.rbdef is_nil_or_empty?(str) str.nil? || str.empty? end check_array = ["", nil] check_array.each do |str| if is_nil_or_empty?(str) puts "strはnilまたは空文字列です。" end end #strはnilまたは空文字列です。 #strはnilまたは空文字列です。注意点
if式の条件式は str.nil? || str.empty? とすべきです。
理由は、||演算子は左から右に順に条件式を評価し、結果が真になった時点でif式の中に入りますが、
empty?から先に評価した場合、対象の文字列がnilであればNoMethodErrorを返すためです。
empty?は文字列、配列、ハッシュのみに対応しています。nilには対応していないため、NoMethodErrorが返ってきます。
nil?は全てのオブジェクトに対応しているため、if式の条件式はnil?を左に書くべきです。no_method_error.rbdef is_nil_or_empty?(str) str.empty? || str.nil? end check_array = ["", nil] check_array.each do |str| if is_nil_or_empty?(str) puts "strはnilまたは空文字列です。" end end #undefined method `empty?' for nil:NilClass (NoMethodError)Ruby on Railsの場合
blank?を使用することで、nilと空文字列を評価することが出来ます。
blank? はnilや空文字列以外に「空白文字列のみからなる文字列」もtrueを返すメソッドであるとのこと。
「' '.blank?」や「\t\n\r".blank?」もtrueを返すため、例えば全角空白をfalseとしたい場合は注意が必要です。
なお、blank?の否定はpresent?を使用します。ruby_on_rails.rbdef is_nil_or_empty?(str) str.blank? end check_array = ["", nil] check_array.each do |str| if is_nil_or_empty?(str) puts "strはnilまたは空文字列です。" end end #strはnilまたは空文字列です。 #strはnilまたは空文字列です。余談
to_sメソッドを使って文字列型に変換することで、nilと空文字列を評価することも出来ます。
use_to_s.rbdef is_nil_or_empty?(str) str.to_s.empty? end check_array = ["", nil] check_array.each do |str| if is_nil_or_empty?(str) puts "strはnilまたは空文字列です。" end end #strはnilまたは空文字列です。 #strはnilまたは空文字列です。ただし、UTF-8 でしか動かない処理に投入する場合は、上記の方法は使ってはいけない。
https://qiita.com/scivola/items/1f6704f81aba18df9012
- 投稿日:2019-02-15T23:48:41+09:00
後から名前空間を切ったらPG::UndefinedTable with namespaced Modelが発生した
API作ったが、後から名前空間を別に切り出すことになった
API完成したものの、各モジュールの名前が煩雑になりすぎたので後から名前空間を切り出すことになった。
一通り変えたけどDBでエラー吐いてる
一通り変えて、念のためテストを走らす。
ちゃんとモデル名も変更してるし、DBと合ってるな。。。。
PG::UndefinedTable with namespaced ModelDBがエラー吐いてるやん!
原因
prefixを定義してなかったのが原因でした。
例えば以下の様に名前空間をHogeで切っていて、その中にUserというモデルを定義しており、テーブル名はhoge_usersで定義されていた場合。module Hoge class User < ApplicationRecord end endDBに問い合わせるには
table_name_prefix
というメソッドを定義してやると、ちゃんと頭にhogeを付けてDBに問い合わせてくれるので、テーブルが見つからないと言われなくなります。module Hoge def self.table_name_prefix 'hoge_' end end
- 投稿日:2019-02-15T22:39:04+09:00
SQLの集計結果をクロス表に変形するrubyスクリプト
はじめに
SQLでクロス集計形式にするのって結構大変ですよね?
ということで、単純なSQLの集計クエリの結果をクロス集計に変形するrubyスクリプトを作りました。https://bitbucket.org/cnaos/cross-tab/overview
使い方
SQLの集計結果をTSVファイルとして出力したファイルを
パイプでcross-tab.rbに食わせると
クロス集計型に変形して標準出力に出力します。cat sample.tsv | ./cross-tab.rb
サンプルとして使うデータ
mysqlのSakila Sample Databaseのpaymentテーブルのデータを使います。
https://dev.mysql.com/doc/sakila/en/sakila-structure-tables-payment.htmlpaymentテーブルmysql> desc payment; +--------------+----------------------+------+-----+-------------------+-----------------------------+ | Field | Type | Null | Key | Default | Extra | +--------------+----------------------+------+-----+-------------------+-----------------------------+ | payment_id | smallint(5) unsigned | NO | PRI | NULL | auto_increment | | customer_id | smallint(5) unsigned | NO | MUL | NULL | | | staff_id | tinyint(3) unsigned | NO | MUL | NULL | | | rental_id | int(11) | YES | MUL | NULL | | | amount | decimal(5,2) | NO | | NULL | | | payment_date | datetime | NO | | NULL | | | last_update | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP | +--------------+----------------------+------+-----+-------------------+-----------------------------+ 7 rows in set (0.00 sec)例1:集計項目が1つだけの場合
paymentテーブルのデータをpayment_dateで日付ごとに集計して、
staff_idごとにその日の支払い件数の合計を出します。サンプルクエリ1SELECT DATE_FORMAT(payment_date,'%Y-%m-%d') date, staff_id, count(*) FROM payment GROUP BY date,staff_id;クエリ結果1(抜粋)+------------+----------+----------+ | date | staff_id | count(*) | +------------+----------+----------+ | 2005-05-24 | 1 | 4 | | 2005-05-24 | 2 | 4 | | 2005-05-25 | 1 | 73 | | 2005-05-25 | 2 | 64 | | 2005-05-26 | 1 | 96 | | 2005-05-26 | 2 | 78 | | 2005-05-27 | 1 | 84 | | 2005-05-27 | 2 | 83 | | 2005-05-28 | 1 | 119 | | 2005-05-28 | 2 | 77 |サンプルクエリ1の結果をTSVファイルに出力したのが、sample-data/sample1.tsv です。
sample-data/sample1.tsvdate staff_id count(*) 2005-05-24 1 4 2005-05-24 2 4 2005-05-25 1 73 2005-05-25 2 64 2005-05-26 1 96 2005-05-26 2 78 2005-05-27 1 84 2005-05-27 2 83 2005-05-28 1 119 2005-05-28 2 77このファイルを作成したcross-tabスクリプトに通すと以下のようになります。
head -11 sample-data/sample1.tsv | ./cross-tab.rb処理結果1date 1_count(*) 2_count(*) 2005-05-24 4 4 2005-05-25 73 64 2005-05-26 96 78 2005-05-27 84 83 2005-05-28 119 77例2:集計項目が2つある場合
例1では
payment_dateで日付ごとに集計して、
staff_idごとにその日の支払い件数の合計を出しましたが、
さらに支払い金額(amount)の合計も集計したくなったとしましょう。集計用のクエリを以下のように変更しました。
サンプルクエリ2SELECT DATE_FORMAT(payment_date,'%Y-%m-%d') date, staff_id, count(*), sum(amount) FROM payment GROUP BY date,staff_id;クエリ結果2(抜粋)+------------+----------+----------+-------------+ | date | staff_id | count(*) | sum(amount) | +------------+----------+----------+-------------+ | 2005-05-24 | 1 | 4 | 15.96 | | 2005-05-24 | 2 | 4 | 13.96 | | 2005-05-25 | 1 | 73 | 323.27 | | 2005-05-25 | 2 | 64 | 250.36 | | 2005-05-26 | 1 | 96 | 401.04 | | 2005-05-26 | 2 | 78 | 353.22 | | 2005-05-27 | 1 | 84 | 357.16 | | 2005-05-27 | 2 | 83 | 328.17 | | 2005-05-28 | 1 | 119 | 480.81 | | 2005-05-28 | 2 | 77 | 323.23 |サンプルクエリ2の結果をTSVファイルに出力したのが、sample-data/sample2.tsv です。
このファイルも同様にcross-tabスクリプトに通すと以下のようになります。
head -11 sample-data/sample2.tsv | ./cross-tab.rb処理結果2date 1_count(*) 1_sum(amount) 2_count(*) 2_sum(amount) 2005-05-24 4 15.96 4 13.96 2005-05-25 73 323.27 64 250.36 2005-05-26 96 401.04 78 353.22 2005-05-27 84 357.16 83 328.17 2005-05-28 119 480.81 77 323.23
- 投稿日:2019-02-15T22:30:01+09:00
rails sできない時の対処法
※初学者向けです。
Ruby on Railsで Rails serverコマンドをした時にサーバーが立ち上がらなかった時に対処したことをメモ。
エラー内容
rails s
としてローカル(http://192.168.33.10:3000/ )にアクセスすると,ActiveRecord::ConnectionNotEstablished
No connection pool with 'primary' found.といったようなメッセージが出て上手く動いていない。
ActiveRecordとはRuby on Railsにおいてデータベースとのやりとりに使われているフレームワーク?です。
なのでこのエラーはデータベース関連のエラーであると推測されます。Rails db:migrateで確認
ターミナルを見返すとそもそもdb:migrateが上手くいってません
rails db:migrate
とすると
Gem::LoadError: can't activate sqlite3 (~> 1.3.6), already activated sqlite3-1.4.0. Make sure all dependencies are added to Gemfile.
というエラー。内容はGemのロードエラー。具体的にはsqlite3 (~> 1.3.6)がアクティベートできないけどsqlite3 (1.4.0)はアクティベートされてるよ、というもの。
おそらくRailsが最新版のsqlite3(1.4.0)を受け付けてないと推測。
ちなみに(~>1.3.6)とは '1.3.6以上1.4.0未満'という意味。対処法:Gemfileの更新
Rails new したフォルダにあるGemfileの内容を変えましょう。
(初学者の私にとっては「???」状態でしたが。。。)
Gemfile
の中にある
gem 'sqlite3'
という部分を
gem 'sqlite3', '~> 1.3.6'
に変更しましょう。これで
bundle update
bundle install
というコマンドをターミナル上で実行し、
rails s
晴れてRuby on Railsのデフォルト画面が現れるはず。
初学者でいきなり詰まったところだったのでメモを残します。
- 投稿日:2019-02-15T20:04:36+09:00
比較演算子
- 投稿日:2019-02-15T20:03:09+09:00
変数展開
- 投稿日:2019-02-15T19:33:00+09:00
boxからファイル一覧を取得するLambda関数をServerless Frameworkで構築する
なにこれ
連携会社や他のチームと実績データや素材データをやりとりすることがままあります。
今回はその一例として、「閲覧権限のあるフォルダに定期的にファイルが追加されるので、それを利用して色々処理する定期バッチをLambdaで構築したい」というお話です。
あと、折角なのでServerless Frameworkを使ってみました。
その手順をまとめます。構成図
こんな感じ。
今回は機微な話なのでストレージ保存とかは割愛して取得するところまでの手順を書きます。Serverless Framework
デプロイはServerless Frameworkで行います。
Serverless Framework自体の詳細な説明は、公式でも手厚くしてくれているし記事も沢山あるのでここでは省きます。
ザックリ雑には「CloudFormationとかTerraformで頑張らなくてもコマンド一つでポンとデプロイできるイカしたフレームワーク」という認識で問題ないかと思います。「Serverless Frameworkは一旦いらない」「boxの方説明して」という人はこの節はかっ飛ばしてください。
環境設定
事前準備として以下が設定済みであることと仮定します。
- npm
- awscli(AWSアカウントは持っている前提)
~/.aws/credentials
を設定- bundlerが利用可能
gem install
でもできるけどねまずはインストール。
npmコマンド一つで簡単に使えるようになります$ npm install -g serverlessインストールが終わったら
sls
と叩いてみましょう(デカイので折りたたんでます)。
展開して中身を確認
$ sls Commands * You can run commands with "serverless" or the shortcut "sls" * Pass "--verbose" to this command to get in-depth plugin info * Pass "--no-color" to disable CLI colors * Pass "--help" after any <command> for contextual help Framework * Documentation: https://serverless.com/framework/docs/ config ........................ Configure Serverless config credentials ............ Configures a new provider profile for the Serverless Framework create ........................ Create new Serverless service install ....................... Install a Serverless service from GitHub or a plugin from the Serverless registry package ....................... Packages a Serverless service deploy ........................ Deploy a Serverless service deploy function ............... Deploy a single function from the service deploy list ................... List deployed version of your Serverless Service deploy list functions ......... List all the deployed functions and their versions invoke ........................ Invoke a deployed function invoke local .................. Invoke function locally info .......................... Display information about the service logs .......................... Output the logs of a deployed function metrics ....................... Show metrics for a specific function print ......................... Print your compiled and resolved config file remove ........................ Remove Serverless service and all resources rollback ...................... Rollback the Serverless service to a specific deployment rollback function ............. Rollback the function to the previous version slstats ....................... Enable or disable stats plugin ........................ Plugin management for Serverless plugin install ................ Install and add a plugin to your service plugin uninstall .............. Uninstall and remove a plugin from your service plugin list ................... Lists all available plugins plugin search ................. Search for plugins Plugins AwsConfigCredentials, Config, Create, Deploy, Info, Install, Invoke, Logs, Metrics, Package, Plugin, PluginInstall, PluginList, PluginSearch, PluginUninstall, Print, Remove, Rollback, SlStatsプロジェクトの初期化
プロジェクトディレクトリを作成し初期化します。
初期化は、sls create
にパラメータとしてランタイム、関数名を指定するだけでOKです。
今回はrubyでbox-list
という関数を作成します。$ mkdir box_list $ cd box_list/ $ sls create -t aws-ruby --name box-list Serverless: Generating boilerplate... _______ __ | _ .-----.----.--.--.-----.----| .-----.-----.-----. | |___| -__| _| | | -__| _| | -__|__ --|__ --| |____ |_____|__| \___/|_____|__| |__|_____|_____|_____| | | | The Serverless Application Framework | | serverless.com, v1.37.1 -------' Serverless: Successfully generated boilerplate for template: "aws-ruby"実行すると何やら色々画面に出て、対象のディレクトリに設定ファイルが配置されます。
$ ll ~/box_list/ total 16 -rw-r--r-- 1 maruta-hirokazu staff 151B 2 14 16:34 handler.rb -rw-r--r-- 1 maruta-hirokazu staff 2.8K 2 14 16:34 serverless.yml中身はこんな感じ(デフォルトがこれまたデカイので折りたたんでます)
展開して中身を確認
$ cat ./handler.rb require 'json' def hello(event:, context:) { statusCode: 200, body: JSON.generate('Go Serverless v1.0! Your function executed successfully!') } end $ $ cat serverless.yml # Welcome to Serverless! # # This file is the main config file for your service. # It's very minimal at this point and uses default values. # You can always add more config options for more control. # We've included some commented out config examples here. # Just uncomment any of them to get that config option. # # For full config options, check the docs: # docs.serverless.com # # Happy Coding! service: box-list # NOTE: update this with your service name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details # frameworkVersion: "=X.X.X" provider: name: aws runtime: ruby2.5 # you can overwrite defaults here # stage: dev # region: us-east-1 # you can add statements to the Lambda function's IAM Role here # iamRoleStatements: # - Effect: "Allow" # Action: # - "s3:ListBucket" # Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } # - Effect: "Allow" # Action: # - "s3:PutObject" # Resource: # Fn::Join: # - "" # - - "arn:aws:s3:::" # - "Ref" : "ServerlessDeploymentBucket" # - "/*" # you can define service wide environment variables here # environment: # variable1: value1 # you can add packaging information here #package: # include: # - include-me.py # - include-me-dir/** # exclude: # - exclude-me.py # - exclude-me-dir/** functions: hello: handler: handler.hello # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details # events: # - http: # path: users/create # method: get # - s3: ${env:BUCKET} # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 # - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" # - cloudwatchEvent: # event: # source: # - "aws.ec2" # detail-type: # - "EC2 Instance State-change Notification" # detail: # state: # - pending # - cloudwatchLog: '/aws/lambda/hello' # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp # Define function environment variables here # environment: # variable2: value2 # you can add CloudFormation resource templates here #resources: # Resources: # NewResource: # Type: AWS::S3::Bucket # Properties: # BucketName: my-new-bucket # Outputs: # NewOutput: # Description: "Description for the output" # Value: "Some output value"とりあえずワチャワチャ書いてあるのですが、コメントを全部消してみるとこれしか記述がありません。
service: box-list provider: name: aws runtime: ruby2.5 functions: hello: handler: handler.hello読み解いていくと
* サービス名:box-list
* ランタイム:ruby 2.5
* 実行時の関数:handler.hello
という設定が書かれています。これだけでデプロイの準備がOKになってしまいます。
すごいよね。
で、対象のhandlerファイルは以下。require 'json' def hello(event:, context:) { statusCode: 200, body: JSON.generate('Go Serverless v1.0! Your function executed successfully!') } end定義ファイル側で指定した
ファイル名.ハンドラ名
にし、(event:, context:)
を引数とするだけで動いてくれます。プロジェクトのデプロイ
先ほど初期化した状態で
serverless deploy
をしてあげるだけでデプロイ完了です。
また、以下のようにprofileを指定することで環境の切り替えもできます。
(何も指定しない場合はdefault
のプロファイルが利用されます)$ sls deploy --aws-profile myenvこれによって間違って会社のアカウントにLambda関数作っちゃって、あとで「ねぇ、知ってたらいいんだけどこれ何かわかる?・・・あ、君の?いや、勉強に使うのはとてもいいことなんだけど、試し打ち用のアカウントあるんだからさ。まぁ確かに最近そっちの利用料がちょっと高いかなとは思っていたんだけど、社内の方にあげるのは流石にやめてね。」ってチームメンバーに諭される心配もなくなりますね!
で、デプロイを実行すると以下のようにモジャモジャターミナルが動きあっという間に完了してしまいます
Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service box-list.zip file to S3 (266 B)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ....................... Serverless: Stack update finished... Service Information service: box-list stage: dev region: us-east-1 stack: box-list-dev resources: 5 api keys: None endpoints: None functions: hello: box-list-dev-hello layers: None Serverless: Removing old service artifacts from S3...マネジメントコンソール上ではこんな感じ。
詳細。
ばっちり。なお、デプロイは「プロジェクトフォルダにあるもの全てがzipに圧縮されてアップロードされる」という方式なので、必要なパッケージ(rubyだとgem周りとか)はプロジェクトフォルダ内に全部入れてあげる必要があります。
Box API
本題。
Boxにあるファイルを取得していいようにいじくりまわします。
今回はrubyを使うのでBoxrという公式のgemを利用します。Boxerのインストール
プロジェクトファイル内で以下を実行。
$ bundle init Writing new Gemfile to /Users/maruta-hirokazu/box_list/Gemfile $ echo "gem 'boxr'" >> Gemfile $ cat Gemfile # frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } # gem "rails" gem 'boxr' $ bundle install --path vendor/bundle以上。
Box側でアプリの作成
アプリを作成しないことにはAPIは利用できないので、まずはBoxアプリを作成します。
開発者コンソールにアクセスします
認証設定を選びます。
ここについてですが、ユーザー認証はバッチ処理には向かないし、値を環境変数か何かに持たせるだけで認証できた方が便利やろっていうところからJWTを利用します。
アプリ名はユニークであればなんでもOKです。
ここまでで「アプリの作成」を実行するとアプリができます(できるまでに結構時間がかかります)。
完了すると以下のような画面が出るので、書かれているコマンドを実行してみてください。curl https://api.box.com/2.0/folders/0 -H "Authorization: Bearer ***" -vvv | jq .
200OK
でファイル情報が返ってくればとりあえず準備OKです。アプリの構成設定
Developerトークンを利用することで一時的なアクセスとかもできるのですが、今回は認証キーペアを作成してしまいます。
公開キーの追加と管理
で公開/秘密キーペアを生成
を押してください。
キーペアの生成ボタンを押すと秘密鍵がダウンロードされます(今回は資料としてダウンロード画面を出したかったためfirefox使っていますが、ブラウザはなんでもいいです)。
中身はこんな感じです。
{ "boxAppSettings": { "clientID": "***", "clientSecret": "***", "appAuth": { "publicKeyID": "***", "privateKey": "-----BEGIN ENCRYPTED PRIVATE KEY-----\n******-----END ENCRYPTED PRIVATE KEY-----\n", "passphrase": "***" } }, "enterpriseID": "***" }構成の設定自体はこれでOKで、boxerではこのjsonにある情報を利用します。
なのでキーをなくさないように注意。Boxの認証設定
ユーザー側からアプリを認証します。
この認証は2種類あるので要件に合わせて対応を変えてください。
- 全てのユーザーから認証させる場合
- アプリユーザーからのみ認証させる場合
全てのユーザーから認証させる場合
こちらの方が設定自体は簡単です。
アプリの構成で
[アプリケーションアクセス]
をEnterprise
に指定します。
ここのチェックが外れるので入れなおしてください。
[管理コンソール]
→[Enterprise設定]
→[アプリ]
→[カスタムアプリケーション]
新しいアプリケーションを承認
を押します。
ポップアップするのでクライアントIDを入れます
アクセス対象ユーザー
がすべてのユーザー
になっていればOKです。
こちらの場合は設定は以上です。アプリユーザーからのみ認証させる場合
boxには
アプリユーザー
という概念があります。
詳細の説明は公式に任せますが、端的にはより堅牢なパターンです。
ただし手順が複雑になります。アプリの構成で
[アプリケーションアクセス]
をアプリケーション
に指定します
[管理コンソール]
→[Enterprise設定]
→[アプリ]
→[カスタムアプリケーション]
で新しいアプリケーションを承認
を押します(この処理自体はすべてのユーザーの場合と同じ)。
ポップアップされるものが[このアプリのApp Usersのみ]
になっていることに注意してください。
こちらのケースではAdminユーザーからアクセスした場合Cannot obtain user token based on the enterprise configuration for your app
みたいなエラーが出るので、以下で作成するアプリユーザーを利用しなければなりません。アプリユーザーの作成
コマンドで作成します。
Bearer
の部分はアプリ構成のDeveloperトークン
で生成してください(60分で利用できなくなります)。curl https://api.box.com/2.0/users \ -H "Authorization: Bearer ***" \ -d '{"name": "box-list-user", "is_platform_access_only": true}' \ -X POST -vvvコマンドを実行すると、以下のようなレスポンスが来るのでUserIDを取っておいてください。
{ "type": "user", "id": "これを保存してください", "name": "box-list-user", "login": "AppUser_***@boxdevedition.com", "created_at": "2019-02-15T01:15:53-08:00", "modified_at": "2019-02-15T01:15:53-08:00", "language": "en", "timezone": "America/Los_Angeles", "space_amount": ***, "space_used": 0, "max_upload_size": ***, "status": "active", "job_title": "", "phone": "", "address": "", "avatar_url": "***" }また、
[管理コンソール]
→[ユーザーとグループ]
にAppUserが追加されているはずです。ユーザー設定で
[フォルダの追加または作成]
から管理したいフォルダを選んでください。
以上で設定は終わりです。boxのファイル一覧を取得するプログラムの作成
プライベートキーの切り出し
プログラム作成の前に、秘密鍵からprivateKey情報を別のファイルとして切り出します。
$ cat key.json | jq .boxAppSettings.appAuth.privateKey適当にファイル名をつけて(
box_private_key
とか)プロジェクトフォルダ内(input/
配下とか)に保存しておきます。Serverless Frameworkで作成した
handler.rb
を以下のように編集します。
(単純にファイルの一覧を表示し、ファイルの数を調べるプログラムです)require "boxr" def run(event:, context:) token = Boxr::get_user_token( ENV.fetch('BOX_USER_ID'), private_key: File.open('input/box_private_key').read.gsub("\\n", "\n"), private_key_password: ENV.fetch('BOX_JWT_PRIVATE_KEY_PASSWORD'), public_key_id: ENV.fetch('BOX_JWT_PUBLIC_KEY_ID'), client_id: ENV.fetch('BOX_CLIENT_ID'), client_secret: ENV.fetch('BOX_CLIENT_SECRET')) client = Boxr::Client.new(token.access_token) cnt = show(client, '0', '') { statusCode: 200, num_of_files: JSON.generate(cnt) } end # ファイルの一覧を全て捜査する def show(client, folder_id, parent_path) cnt = 0 items = client.folder_from_id(folder_id).item_collection.entries items.each do |item| if item.type == 'folder' cnt += show(client, item.id, "#{parent_path}/#{item.name}") elsif item.type == 'file' cnt += 1 p "#{parent_path}/#{item.name}" end end cnt end補足すると
Boxr::get_user_token
でクライアントに接続client.folder_from_id(folder_id)
でフォルダ内の内容の取得
0
を指定するとルートフォルダからになります- フォルダIDはブラウザから確認できるので、指定のフォルダに対し検索もかけられます
item.type
でフォルダなのかコンテンツなのかを判断していますhandlerができたら、
serverless.yml
を以下のようにします。
ただし、BOX_USER_ID
の部分はすべてのユーザーの場合
は自身のアカウントIDを、AppUserのみの場合
はAppUser生成時に得られたIDを指定してください。service: box-list provider: name: aws runtime: ruby2.5 stage: dev region: us-east-1 environment: BOX_USER_ID: <user_id / app_user_id> BOX_JWT_PRIVATE_KEY_PASSWORD: *** BOX_JWT_PUBLIC_KEY_ID: *** BOX_CLIENT_ID: *** BOX_CLIENT_SECRET: *** functions: box: handler: handler.run events: - schedule: rate(100 minutes) timeout: 300
- 今回は本当に雑に100分ごとに実行していますが、好きなようにスケジュール式を変えてください。
- environment配下に、それぞれの値を入れてください。
- セキュリティ的にはシステムの環境変数から取得する方がいいかも。
動作確認
ここまで行ったら一旦動作確認をします。
Serverless Frameworkがイカしている部分としてローカルで簡単に試せるというところがあります。sls invoke local -f boxこれで動いているみたいならデプロイするだけ!
Tips
CSVファイルをパースする。
CSV.parse(client.download_file(item.id).encode('UTF-8', 'Shift_JIS'), headers: true)他のファイルも
client.download_file
で制御できそうです。
詳しくは公式のAPI仕様を確認してください。
FYI: https://box-content.readme.io/referenceまとめ
マジレスするとものすごく大変でした
ドキュメントが地味にわかりにくいので色々試し試しやった感じです。
でも、一回動いてくれるようになると大変便利&色々応用できそうです
- 投稿日:2019-02-15T18:27:35+09:00
酒が飲めるワンライン
perl (5.18.2 で確認)
perl -E 'say"$_月は横浜で酒が飲めるぞ"for(1..12)'ruby
ruby -e '12.times{|i|puts"#{i+1}月は横浜で酒が飲めるぞ"}'bash
echo -e {1..12}月は横浜で酒が飲めるぞ"\n"python3 (3.7.2 で確認)
python3 -c 'for n in range(1, 13): print(f"{n}月は横浜で酒が飲めるぞ")'emacs-lisp (GNU Emacs 26.1 で確認)
(require 'cl)(let ((x 0))(while (< x 12) (cl-incf x)(insert (format "%d月は横浜で酒が飲めるぞ\n" x))))php (PHP 5.6.30 で確認)
php -r 'for($i=1;$i<13;$i++){echo $i."月は横浜で酒が飲めるぞ\n";}'Elixr (1.6.4 で確認)
elixir -e "1..12 |> Enum.map(&(\"#{&1}月は横浜で酒が飲めるぞ\n\")) |> IO.puts"MySQL版 (MySQL 8 で確認)
CREATE DATABASE s;CREATE TABLE s.y (m int auto_increment, PRIMARY KEY (`m`));INSERT INTO s.y value (),(),(),(),(),(),(),(),(),(),(),();SELECT CONCAT(m, '月は横浜で酒が飲めるぞ') FROM s.y;DROP DATABASE s;PostgreSQL (9.3 で確認)
psql -c "WITH RECURSIVE seq(i) AS (SELECT 1 UNION ALL SELECT i + 1 FROM seq WHERE i < 12) SELECT i || '月は横浜で酒が飲めるぞ' FROM seq;"JavaScript (node.js v10.14.2)
node -e "for(let i=1;i<13;i++)console.log(i+'月は横浜で酒が飲めるぞ')"Haskell (ghc8.4.4)
ghc -e 'mapM_ (\n-> putStrLn $ show n ++ "月は横浜で酒が飲めるぞ") [1..12]'GAWK(GNU Awk 4.1.3)
gawk 'BEGIN{for(i=1;i<13;i++) print i"月は横浜で酒が飲めるぞ"}'
- 投稿日:2019-02-15T18:21:51+09:00
Codewars で複数のプログラミング言語で全ての問題を解いていきたい時に便利な操作
クリックすると私が解いた問題が見れます。
# 今回は画像(スクショ)だけで手抜き~!# Codewars についてご存じない方は、先に『【Codewars】ブラウザでコーディングの基礎からトレーニングできるサイト (ブラウザでvimが使えて32種類のプログラミング言語に対応。4000個以上の問題が投稿されています!)』 をお読み頂くようお願い致します。
このような感じで、View Profile メニューから Kata タブを選択することで、他の言語で解き忘れがないか確認できます。すでにやった問題を自分の学習中の言語全てで解いてみたい方におススメです。
- 投稿日:2019-02-15T17:16:23+09:00
rails で セレクトタグの生成
Railsでセレクトタグの生成
今回、ECサイトの買い物かご的な物を再現してみようと思い作成していた所、少々分かりづらかった所があったので記事にしてみました。
select_tag
Rails ドキュメント
http://railsdoc.com/references/selectここに説明が載っているものの、僕には理解が難しかった・・・
select_tagの使い方
ドキュメントには、
select_tag(要素名, タグを表す文字列 [, オプション])の様に書かれていました。
色々試してみた所、要素名の所で指定した値がname属性として送信され、
params[:要素名]で値を取得できるようです。
値の作成
Railsドキュメント
http://railsdoc.com/references/options_for_selectセレクトタグの値を作成する場合は、options_for_selectを使います。
<%= select_tag("num", options_for_select((タグの配列 or ハッシュ)) %>とすることで値を設定できます。
今回は1から100までの値を設定したので、
<%= select_tag("num", options_for_select((1..100)) %>この様に記述しました。
selected設定
今回は、買い物かごの作成ということだったので、購入予定の個数設定をしておかなくてはならないためselected指定をする必要がありました。
これは、options_for_selectの:selectedオプションを使用することで可能になります。
そのため、options_for_selectの第2引数に
<%= select_tag("num", options_for_select((1..100) , :selected => item.item_number) ) %>とすることで、購入予定数をselected指定しました。
上記の様に、
:selected => 値とすることで、指定ができます。
感想
ドキュメントを読むのは難しい・・・
言葉の定義など、理解があいまいな部分が多く、
用語を明確に覚える必要があると感じた。
- 投稿日:2019-02-15T16:05:34+09:00
Ruby.学習その9
投稿目的
- 個人の学習目的で投稿していきます。
- Rubyの入門を読みながら自分なりの思考で記述しています。
参考資料
クラスの高度な話
インスタンス変数を簡略的に定義
- 下記が簡略的ではない書き方になります。
class Greeting # Greetingクラス定義 def morning # morningメソッド定義 @morning # morningインスタンス変数定義 end def morning=(text) # morning=メソッドに引数1つ定義 @morning = text # 受け取った引数をインスタンス変数に代入 end end greeting = Greeting.new # インスタンス作成 greeting.morning = "おはようございます" # morning=メソッドを呼び出し puts greeting.morning # morningメソッドを呼び出し # 結果: おはようございます
- 下記のように記述することでインスタンス変数を簡略的にすることが可能です。
attr_reader :インスタンス変数名attr_readerメソッド
- 上記のGreetingクラスに追加すると下記のようになります。
- attr_readerメソッドは同名のインスランス変数を戻り値とするメソッドを定義します。
- @を記述せずに:に繋げて記述します。
class Greeting attr_reader :morning # 戻り値が同名のインスタンス変数のメソッドを定義 def morning=(text) @morning = text end end greeting = Greeting.new greeting.morning = "おはよう" puts greeting.morning # 結果: おはようattr_writerメソッド
- attr_writerメソッドを利用することで引数を受け取って同名のインスタンス変数に代入するメソッドを簡略的に定義することができます。
class Greeting attr_reader :morning attr_writer :morning # 同名のインスタンス変数に引数を受け取り代入するメソッドを定義 end greeting = Greeting.new greeting.morning = "おはよう!!" puts greeting.morning # 結果: おはよう!!attr_accessorメソッド
- さらに上記の2つはセットで利用することが多いのでその2つの機能を持つメソッドがattr_accessorメソッドです。
class Greeting # 同名のインスタンス変数に代入、戻り値が同名のインスタンス変数 attr_accessor :morning end greeting = Greeting.new greeting.morning = "Good morning" puts greeting.morning # 結果: Good morningself
- selfは自分自身(レシーバ)を指している。
- レシーバとはメソッドを呼び出せるオブジェクト.
- メソッド内でselfを指定すると返ってくるオブジェクトはインスタンスメソッドならクラスのインスタンス、クラスメソッドならクラスのように自分自身を指している。
インスタンスメソッド
class Greeting def me # インスタンスメソッドを定義 self.morning # インスタンスメソッドなのでクラスのインスタンスを指している end def morning # インスタンスメソッドを定義 puts "おはよう" end end greeting = Greeting.new greeting.me # 結果: おはようクラスメソッド
- クラスメソッドの場合はメソッド名の前にselfを付ける必要がある。
class Greeting def self.me # クラスメソッドを定義 self.morning # クラスメソッドなのでクラス自身を指している end def self.morning #クラスメソッドを定義 puts "おはよう" end end Greeting.me # 結果: おはよう
- 自分自身のメソッドを呼び出すのにself(レシーバ)と付けるのは手間なのでself(レシーバ)指定を省略することができる。
class Greeting def me morning end def morning puts "おはよう" end end greeting = Greeting.new greeting.me # 結果: おはようインスタンスメソッドとクラスメソッドが持つインスタンス変数は別物
- インスタンス変数の持ち主はselfが指すオブジェクトです。
- クラスにインスタンスとクラスはオブジェクトが違うのでインスタンス変数の持ち主も別々になります。
class Greeting def morning # インスタンスメソッドを定義 @morning = "おはよう" # インスタンスメソッドのインスタンス変数に代入 end def self.morning # クラスメソッドを定義 @morning # クラスメソッドのインスタンス変数が戻り値で返る end end greeting = Greeting.new greeting.morning # インスタンスメソッドのmorningを呼び出し p Greeting.morning # クラスメソッドのmorningを呼び出し # 結果: nilクラス変数
- インスタンス変数、ローカル変数の他にクラス変数が存在します。
- クラス変数は変数名の前に@@を付けることで定義できます。
- クラス変数はクラスを継承した先でも利用することができる。
class GreetingA @@morning = "おはよう" # クラス変数に代入 def morning @@morning end end greetingA = GreetingA.new puts greetingA.morning # 結果: おはよう class GreetingB < GreetingA # GreetingAのクラスをGreetingBに継承 def morning @@morning # greetingAのクラス変数を利用 end end greetingB = GreetingB.new puts greetingB.morning # 結果: おはよう文字列を調べる正規表現
文字列を含むかを判定
- match?メソッドを利用することで特定の文字列を含んでいるかが判定できます。
- match?の引数にパターンを渡すことで含むかを判定できます。
"文字列".match?(/正規表現パターン/)
- /で囲むことで正規表現(Regexp)オブジェクトとなりこれをパターンと呼びます。
/正規表現パターン/
- 下記はフルーツと文字が含むものがtrueとなります。
p "グレープフルーツ".match?(/フルーツ/) # true p "りんご".match?(/フルーツ/) # false p "ドラゴンフルーツ".match?(/フルーツ/) # trueその他の正規表現
- \zを加えることで末尾に指定された文字が含まれているかを判定します。
- \Aを加えることで先頭に指定された文字が含まれているかを判定します。
p "グレープフルーツ".match?(/フルーツ\z/) # true p "フルーツレモン".match?(/\Aフルーツ/) # true
- よく使われる正規表現.
- []で囲むと中の1文字どれかが含まれているかを判定します。
/[abc]/ # aかbかcが含まれていればtrue
- []の中に範囲指定で記述して判定することもできます。
- アルファベットの大文字か小文字、数字のいずれか1文字が含まれているかを判定します。
p "りんごv".match?(/[A-Za-z0-9]/) # true p "りんご5".match?(/[A-Za-z0-9]/) # true
- .は任意の1文字。
p "a0b".match?(/a.b/) # true p "agb".match?(/a.b/) # true
- *は前の文字が0回以上繰り返すかどうかを判定します。
p "abc".match?(/ab*c/) # true p "c".match?(/ab*c/) # false p "aaac".match?(/ab*c/) # true
- +は前の文字が1回以上繰り返すかを判定します。
p "ac".match?(/ab+c/) # false p "abc".match?(/ab+c/) # true p "bbc".match?(/ab+c/) # false条件と一致する文字を置換
- gsubメソッドを利用することで文字の置換をすることができます。
- 第1引数が置換元で第2引数が置換先になります。
p "フルーツオレ".gsub("フルーツ", "カフェ") # カフェオレ p "グレープフルーツサワー".gsub("サワー", "ジュース") # グレープフルーツジュース p "フルーツフルーツ".gsub(/\Aフルーツ/, "ミックス") # フルーツミックス正規表現とif文
- 正規表現を利用してif文で分岐することができます。
- 下記のプログラムはフルーツと含まれているものだけを表示する条件です。
["バナナジュース", "グレープフルーツサワー", "フルーツオレ"].each do |drink| puts drink if drink.match?(/フルーツ/) end # 結果: グレープフルーツサワー フルーツオレブロックの高度な話
- eachメソッドなどを使用する際にdoからendをブロックと呼びます。
- ブロックとはプログラムのかたまりをメソッドへと渡すことができる仕組みです。
- ブロックは引数と似ています引数はオブジェクトを渡してブロックは処理のかたまりを渡すイメージです。
- ブロックは1つしか渡すことができません。
渡されたブロックを実行
- block_given?メソッドを利用することでブロックが渡されたどうかを判別してくれます。
def foo p block_given end foo #=> false foo do end #=> true
- 渡されたブロックを実行するにはyieldを利用します。
def dice if block_given? # ブロックの有無を判別 puts "run block" yield # 渡されたブロックを実行 else # ブロックが渡されていない場合の処理 puts "normal dice" puts [1, 2, 3 , 4, 5, 6].sample end end dice # 結果: ブロックを渡していないので1~6をランラムで表示 dice do # ブロックが渡されたのでブロックの処理を実行 puts [4, 5, 6].sample # 結果: 4~6をランダムに表示 end渡されたブロックを引数で受け取る
- 引数にブロックを渡す際は変数名の前に&を付けることで受け取ることができます。
- callメソッドで変数に代入されたブロックを実行することができる。
- 変数に代入されているブロックをprocオブジェクトと呼びます、プログラムの処理もオブジェクトとして扱うことができます。
def foo(&greeting) # &でブロックを受け取る greeting.call # 代入されたブロックを実行 end foo do puts "おはようございます!" end # 結果: おはようございます!ゼロからわかるRuby超入門の教本は終了です。
- 投稿日:2019-02-15T14:54:39+09:00
【Ruby】シーザー暗号作ってみた
Rubyで簡易なシーザー暗号プログラムを作りました。
初投稿で、至らぬ点も多いですが、よろしくお願いします。シーザー暗号とは?
平文の各文字を辞書順に3文字のみシフトし、暗号文をつくる暗号のこと。
単一換字式暗号の一種で、カエサル暗号とも呼ばれている。要はアルファベット順に文字をシフトさせて、暗号化するってことですね。
例: enter → bkqbo実現するには?
コードを実現するにあたって、考えたことは以下になります。
- 文字列って、数字で表現できるのでは?
- 文字列 → 数値 → シフト後の数値 → 文字列と変換すれば可能?
- 文字列が長くても対応できるように!
上記の3点を意識して、コードを書いてみました。
実際に書いたコード
code.rbputs "キーワードを入力してください(アルファベット小文字のみ)" keyword = gets.chomp puts "暗号化前: #{keyword}" # 暗号化した文字列を格納するための配列を定義 code = [] # keyword.charsで入力された文字列を1文字ずつ分解して配列へ keyword.chars.each do |char| num = (char.ord - 3) # .ord で文字コードへ変換(数値化)し、3つ分、シフトする code << num.chr # 文字コードを文字列へ戻し、配列codeに入れていく end # 配列codeに入った文字列を全て連結させて出力 puts "暗号化後: #{code.join("")}"ターミナルでの出力
キーワードを入力してください(アルファベット小文字のみ) desk 暗号化前: desk 暗号化後: abphやった!できた! 他のも試してみよう・・・
キーワードを入力してください(アルファベット小文字のみ) apple 暗号化前: apple 暗号化後: ^mmibaが記号に変換されてしまった・・・
調べてみると、
aの文字コード: 97 → シフト後: 94 となり、文字コード: 94は「^」みたいです...となると、「a ~ z」の文字コードでの範囲は「97 ~ 122」。この範囲外では、アルファベットの小文字ではなくなってしまう。
abcやxyzにも対応できるようにしなければ...
そこでコードを以下のように改良しました。改良版
code.rbputs "パスワードを入力してください(aからzまでの小文字)" keyword = gets.chomp puts "暗号化前: #{keyword}" code = [] keyword.chars.each do |char| num = char.ord - 97 num2 = (num - 3) % 26 num3 = num2 + 97 code << num3.chr end puts "暗号化後: #{code.join("")}"each文の中の処理を少し変更してみました!
each内部の処理ですが...
d を例として説明すると、
dの文字コードは「100」のため、numに入る値は「3」になります。
次にnumに対して、「3」(シフトさせる分の数値)を引き、 %26 してあげると num2は「0」です。
その後、num2に「97」を再び足すことでnum3が「97」となります。
最後に、このnum3に 「.chr」することで文字列に戻り、「a」となります。num2の計算ですが、%26してあげることで、計算結果は「0 ~ 25」のどれかになります。
たとえ、num2が負の値であってもです。これは割り算の余りの基本式に当てはめて考えてみると分かるので、気になる方はやってみてください。
(割られる数)=(割る数)×(商)+(余り)
最後に書き換えたコードをターミナルで実行してみましょう!
パスワードを入力してください(aからzまでの小文字) apple 暗号化前: apple 暗号化後: xmmibアルファベットの小文字限定ですが、無事暗号化することができました。
まだ大文字や数字には対応していないので、時間があればやってみようかと思います。分かりづらかったかもしれませんが、ここまで読んでいただきありがとうございました。
- 投稿日:2019-02-15T14:16:53+09:00
CodinGame の TRON BATTLE で FloodFill アルゴリズムを実装してみた
# この記事は、『CodinGame はBOT(AIプログラム)でバトルするのが正しい楽しみ方かもしれません』 の続きです。
以下のようなデバッグ出力ができるように CodinGame の TRON BATTLE プログラムを改造してみた。
P=0
という出力から、自機のプレイヤー番号は0であることが分かる。Input{X0:2, Y0:2, X1:10, Y1:0}
という出力からプレイヤー0のスタート位置(tail)は、(2, 2) であることと、現在位置(head)の座標が (10, 0) であることが分かる。 *自機のプレイヤー(プレイヤー0)のヘッドから見て現時点で到達可能であるマスが+
記号で表示されている。ちなみにマイナス記号は何もない印である。P=0 Input{X0:2, Y0:2, X1:10, Y1:0} + + + + + + + + + + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -1. 出来たソース
- コメントは全部削除しました。(読めるでしょ?w)
- 自己流でFloodFillアルゴリズムを実装してみました。
- FloodFillで到達可能範囲を計算しながら「一切」その結果を使ってないので「強くない」です。『CodinGame はBOT(AIプログラム)でバトルするのが正しい楽しみ方かもしれません』 で紹介したソースと強さは全く変わってませんw(誰か助けてw)
using System; using System.Linq; using System.IO; using System.Text; using System.Collections; using System.Collections.Generic; class Player { static void Main(string[] args) { Player control = new Player(); string[] numbers; Input[] inputs; // game loop while (true) { numbers = Console.ReadLine().Split(' '); int N = int.Parse(numbers[0]); int P = int.Parse(numbers[1]); Console.Error.WriteLine("P={0}", P); inputs = new Input[N]; for (int i = 0; i < N; i++) { numbers = Console.ReadLine().Split(' '); int X0 = int.Parse(numbers[0]); int Y0 = int.Parse(numbers[1]); int X1 = int.Parse(numbers[2]); int Y1 = int.Parse(numbers[3]); inputs[i] = new Input(i, X0, Y0, X1, Y1); } Input me = inputs[P]; string dir = control.HandleInputs(me, inputs); control.DumpMap(); Console.WriteLine(dir); } } Cell[,] map = new Cell[30, 20]; Queue<Cell> ffQueue = new Queue<Cell>(); private Player() { for(int y=0; y<20; y++) { for(int x=0; x<30; x++) { map[x, y] = new Cell(x, y, -1); } } } private string HandleInputs(Input me, Input[] inputs) { Console.Error.WriteLine(me); foreach(var input in inputs) { if (input.X1 < 0) DeleteIdsFromMap(input.Id); else AddInputToMap(input); } ExecuteFloodfill(me); if (CanMoveTo(me, -1, 0)) return "LEFT"; if (CanMoveTo(me, 1, 0)) return "RIGHT"; if (CanMoveTo(me, 0, -1)) return "UP"; if (CanMoveTo(me, 0, 1)) return "DOWN"; return "?"; } void AddInputToMap(Input input) { this.map[input.X1, input.Y1].V = input.Id; } void DeleteIdsFromMap(int id) { for(int y=0; y<20; y++) { for(int x=0; x<30; x++) { if (map[x, y].V == id) map[x, y].V = -1; } } } bool CanMoveTo(Input me, int xOffset, int yOffset) { int x = me.X1+xOffset; int y = me.Y1+yOffset; if (x < 0) return false; if (x > 29) return false; if (y < 0) return false; if (y > 19) return false; return map[x, y].V == -1 || map[x, y].V == 9; } bool IsEmptyCell(Cell center, int xOffset, int yOffset) { int x = center.X+xOffset; int y = center.Y+yOffset; if (x < 0) return false; if (x > 29) return false; if (y < 0) return false; if (y > 19) return false; return map[x, y].V == -1; } void DumpMap() { for(int y=0; y<20; y++) { for(int x=0; x<30; x++) { if (map[x, y].V == -1) Console.Error.Write("-"); else if (map[x, y].V == 9) Console.Error.Write("+"); else Console.Error.Write(map[x, y].V); Console.Error.Write(" "); } Console.Error.WriteLine(); } } void ExecuteFloodfill(Input me) { for(int y=0; y<20; y++) { for(int x=0; x<30; x++) { if (map[x, y].V == 9) map[x, y].V = -1; } } Cell c = map[me.X1, me.Y1]; if (IsEmptyCell(c, -1, 0)) ffQueue.Enqueue(map[c.X-1, c.Y]); if (IsEmptyCell(c, 1, 0)) ffQueue.Enqueue(map[c.X+1, c.Y]); if (IsEmptyCell(c, 0, -1)) ffQueue.Enqueue(map[c.X, c.Y-1]); if (IsEmptyCell(c, 0, 1)) ffQueue.Enqueue(map[c.X, c.Y+1]); FloodfillLoop(); } void FloodfillLoop() { while(ffQueue.Count > 0) { Cell c = ffQueue.Dequeue(); if(map[c.X, c.Y].V != -1) continue; c.V = 9; if (IsEmptyCell(c, -1, 0)) ffQueue.Enqueue(map[c.X-1, c.Y]); if (IsEmptyCell(c, 1, 0)) ffQueue.Enqueue(map[c.X+1, c.Y]); if (IsEmptyCell(c, 0, -1)) ffQueue.Enqueue(map[c.X, c.Y-1]); if (IsEmptyCell(c, 0, 1)) ffQueue.Enqueue(map[c.X, c.Y+1]); } } } class Input { public int Id; public int X0; public int Y0; public int X1; public int Y1; public Input(int id, int x0, int y0, int x1, int y1) { this.Id = id; this.X0 = x0; this.Y0 = y0; this.X1 = x1; this.Y1 = y1; } public override string ToString() { return String.Format("Input{{X0:{0}, Y0:{1}, X1:{2}, Y1:{3}}}", X0, Y0, X1, Y1); } } class Cell { public int X; public int Y; public int V; public Cell(int x, int y, int v) { this.X = x; this.Y = y; this.V = v; } public override string ToString() { return String.Format("Cell{{X:{0}, Y:{1}, V:{2}}}", X, Y, V); } }2. 最後に
TRON BATTLE については書く記事としてはこれで終わりになるかもしれません。
C# で挑戦される方の「手始め」の参考にでもなればと思い投稿してみました。# Wood 1 League を抜け出したいのだが…
- 投稿日:2019-02-15T13:44:18+09:00
メモ:RailsのActionMailerでテキストメールを自動生成する
この記事は
- 技術メモです
やりたかったこと
- RailsのActionMailerを使ってメールを送信しているサービスがあります
- そこですでにHTMLメールを送信しているのですが、一部メーラでHTMLメールは受信できないという話がありました
- そこで全メールについてTEXTとのマルチパートメールで送出する必要がでてきました
普通にやると
- 既存のHTMLメールのテンプレートに加えて、TEXTメールのビューテンプレートも作成するとマルチパートで送信することが可能です
- しかし、TEXT/HTMLのビューテンプレートを両方メンテナンスしていくのは結構きついです
actionmailer-text gem
- actionmailer-textを使うと、自動的にHTMLメールからタグを除去していい感じにテキストメールを送出してくれます
- こんな感じっぽく動きます
<br/>
は改行に置換- リンクは
リンク文字列(URL)
みたいな感じに置換- そのほかの普通のタグは除去
- このへんを見るとなんか一生懸命正規表現で置換してるっぽい
使い方
- gemを入れます
Gemfilegem 'actionmailer-text'
- application_mailerに所定のモジュールをincludeします
application_mailer.rbclass ApplicationMailer < ActionMailer::Base # 追記 include ActionMailer::Text end
- ちなみにdeviseを使っている場合、deviseから送られるメールはApplicationMailerを継承していないことがあるので、devise設定ファイルで指定します
config/initializers/devise.rbconfig.parent_mailer = 'ApplicationMailer'
- 以上で普通に全てのメールがTEXTとHTMLのマルチパートメールになって送信されます
終わりに
- ガラケーとかのテキストしか受けられないメーラ爆発しろ
- 投稿日:2019-02-15T13:39:25+09:00
[rails] モデルに存在しない値をjsonフォーマットで返却する方法
概要
モデルに存在しない値をjsonフォーマットで返却したい場合どうするのか。
答えは、アクション関数のrender json
する際にto_json
を使いmethods
のオプションにモデルの任意の関数を指定してあげればいいです。言葉では伝えづらいので、、実際にやってみた結果を御覧ください。
改修前
Reportのスキーマがどうなっているかは説明を省きますが、
コントローラーで以下のようなアクションの実装をしていて、reports.jsonにGETリクエストするとモデルの内容をjsonで出力してくれます。reports_controller.rb# GET /reports # GET /reports.json def index @reports = Report.all respond_to do |format| format.html { render :index } format.json { render json: @reports, status: :ok } end endreports.json[ { "id": 4, "text": "hoge", "created_at": "2019-02-13T11:16:23.000+09:00", "updated_at": "2019-02-13T11:16:23.000+09:00", "deleted_at": null } ]改修後
では、実際にどう実装するかをやってみます。
まずは、モデル側に任意の関数を実装してください。以下の関数ではcreated_atの値を日本人がわかりやすい日時の文字列フォーマットに変換しています。report.rbclass Report < ApplicationRecord # 省略 def created_at_formatted self.created_at.strftime("%Y年%-m月%-d日") end end次に、アクションの実装の
render json
してあげている箇所を以下のように変更してあげます。reports_controller.rb# GET /reports # GET /reports.json def index @reports = Report.all respond_to do |format| format.html { render :index } format.json { render json: @reports.to_json(methods: :created_at_formatted), status: :ok } end end- format.json { render json: @reports, status: :ok } + format.json { render json: @reports.to_json(methods: :created_at_formatted), status: :ok }そうしますと以下のようにモデルには存在しない値をjsonで返却することができました。
reports.json[ { "id": 4, "text": "hoge", "created_at": "2019-02-13T11:16:23.000+09:00", "updated_at": "2019-02-13T11:16:23.000+09:00", "deleted_at": null, "created_at_formatted": "2019年2月13日" } ]以上になります。
- 投稿日:2019-02-15T11:19:53+09:00
Rails × Mountain View(マウンテンビュー)で作るCSSスタイルガイド&コンポーネント
はじめまして。
Mountain View導入の経緯
普段 Ruby On Railsでプロダクトを開発しており、gemで完結するCSSのスタイルガイドを探していました。
有名なgemをいくつか素振りしてみたのですが、LivingStyleGuideはスタイルガイドを簡単にカテゴリ分けできず、Hologramはメンテナンスコストが高そうに感じました。
そんな時に、Railsのビューコンポーネントをそのままスタイルガイド化できるMountainViewと言うgemを知りました。
Mountain Viewとは
https://github.com/devnacho/mountain_view
With Mountain View you create reusable components for your Rails frontend, while generating a living style guide.
Mountain Viewを使用すると、スタイルガイドを生成しつつ、Railsフロントエンド用の再利用可能なコンポーネントを作ることができます。
この記事で紹介すること
紹介しておいてなんですが、私はパフォーマンス上の理由からMountain Viewのコンポーネント機能をそれほど使っていません。
(コンポーネントはRailsのパーシャルに任せれば済む話で、どちらかと言うとスタイルガイドとしての機能が欲しい...)この記事では私がMountain Viewを導入し、コンポーネントとして使用することを諦め、スタイルガイドとして使用することに落ち着くまでに試行錯誤した内容をまとめています。
javascriptを使ったモダンなフロントエンド開発からは遠い話になりますのでご容赦ください。
導入 ~ スタイルガイドの作成
Rails 5.2.1環境で公式のREADMEだけを参考に導入することができました。
まずは
Gemfile
にmountain_view
を追加します。# コンポーネント機能を利用する場合はグローバルに読み込みます gem 'mountain_view'
bundle install
を実行し、routes.rb
ファイルに次の行を追加します。mount MountainView::Engine => "/mountain_view" if Rails.env.development?下記のコマンドでMountain Viewの新しいコンポーネントを作成します。
rails generate mountain_view:component buttonすると、次のようなディレクトリとファイルが作成されます。
app/ components/ button/ _button.html.erb button.css button.js button.yml拡張子にscssやslimなど、Railsで使っているプリプロセッサを使うことができます。
rails s
でサーバーを立ち上げてみましょう。
ローカル環境からbuttonコンポーネントのスタイルガイドのページが作成されていることが確認できると思います。
http://localhost:3000/mountain_viewi18n対応
i18n対応で日本語化(default_localeをjaに変更)していた場合、Mountain Viewにja.ymlが存在しないためtitleタグの表示で下記のようなエラー出ているかもしれません。
<title><span class="translation_missing" title="translation missing: ja.mountain_view.layout.styleguide_title">Styleguide Title</span></title>私はこちらの en.ymlファイルの内容をコピペした
config/locales/mountain_view.ja.yml
のようなファイルを作成しました。buttonコンポーネントを作ってみる
ここでは
bootstrap
を使用してコンポーネント作りを試してみます。
Gemfile
に以下を追加してbundle install
します。gem 'bootstrap'buttonコンポーネントのscssでbootstrapを読み込みます。
components/button/button.scss@import "bootstrap";bootstrapのbuttonコンポーネントをMountainViewに反映させた例です。
app/components/button/_button.html.erb<button type="button" class="btn <%= properties[:modifier] %>"> <%= properties[:title] %> </button>app/components/button/button.yml- :modifier: btn-primary - :modifier: btn-secondary - :modifier: btn-success - :modifier: btn-danger - :modifier: btn-warning - :modifier: btn-info - :modifier: btn-light - :modifier: btn-darkキャッシュの問題
Mountain ViewではCSSの更新やコンポーネントの作成を行った後にキャッシュが残ってしまい、下記のコマンドを実行しないとコンポーネントのデザインがうまく反映されないことがありました。
bin/rake tmp:cache:clearbuttonコンポーネントを使ってみる
定義したコンポーネントは
render_component
メソッドを使用することで使うことができます。
第一引数にコンポーネント名、第二引数にハッシュ値を渡して使います。<%= render_component("button", { title: "Btn Primary", modifier: "btn-primary" }) %>コンポーネントにブロックを渡し、
properties[:yield]
で読み込んで使用することもできます。app/components/button/_button.html.erb<button type="button" class="btn <%= properties[:modifier] %>"> <%= properties[:yield] %> </button>使い方.html.erb<%= render_component("button", {modifier: "btn-primary" }) do %> Btn Primary <% end %>プレゼンターを使ってみる
コンポーネントの階層に
{コンポーネント名}_component.rb
ファイルを追加してMountainViewコンポーネント用のプレゼンターを定義することができます。app/ components/ button/ _button.html.erb button.css button.js button.yml + button_component.rbMountainView::Presenterを継承して使います。
プロパティのデフォルト値なども使用できます。app/components/button/button_component.rbclass ButtonComponent < MountainView::Presenter properties :modifier, :title property :element, default: 'btn' def modifier_title title || properties[:modifier].titleize end end定義したメソッドやプロパティをコンポーネントのパーシャルで利用することができます。
app/components/button/_button.html.erb<button type="button" class="<%= element %> <%= modifier %>"> <%= title %> </button>Railsのコンポーネント管理がMountainViewで完結して最高!
...と思ったのですが、このコンポーネント機能には、後述するパフォーマンス上の問題があるようです。MountainViewのボトルネック
MountainViewのrender_component機能をeachすると、パフォーマンスがとても残念なことになってしまいます。
これを避けるために公式READMEではMountainViewのプレゼンターでrenderメソッドをオーバーライドする方法を紹介しています。しかし、個人的にはそれよりも素直にRailsのパーシャルを使用した方がメリットが大きいなのではないかと思います。特にrender_component機能ではパーシャルのコレクション機能が使えないのが痛いので、私はeachする要素でMountain Viewのrender_component機能は使いません。
例えば、Mountain Viewを
app/components/list_item/_list_item.html.erb<%= properties[:list_item].title %>こんな風に定義するよりも、Railsのパーシャルで
app/views/components/_list_item.html.erb<%= list_item.title %>このように定義しておけば、renderをキャッシュしてくれますし、collection機能でn+1対策もできます。
使い方.html.erb<% @lists = List.all %> # @listsが100個あったら100回render <% @lists.each do |list_item| %> <%= render_component("list_item", {list_item: list_item}) %> <% end %> # @listsの結果が100個あっても1回のrenderで済む <%= render partial: 'components/list_item', collection: @lists, as: :list_item %>最高のビューコンポーネントはRailsのパーシャルでした...?
と、言うわけで私はMountain ViewをRailsコンポーネント管理用のスタイルガイドとして割り切って使うことにしました。MountainViewをスタイルガイドとして使用する
MountainViewはデフォルトではスタイルガイドのHTMLタグが表示されません。
デフォルトでprism.js
が使われているので、views/mountain_view/styleguide/show.html.erbをオーバーライドして、render_componentの記述の上部に下記のコードを追記しただけでスタイルガイドっぽくなってくれます。app/views/mountain_view/styleguide/show.html.erb... <div class="mv-component__description__properties" style="margin-bottom: 20px;"> <code class="language-html"><%= CGI::pretty("#{render_component(@component.name, component_stub.properties.clone)}") %></code> </div> ...また、MountainViewのサイドメニューがレスポンシブ表現に邪魔なので、メディアクエリで非表示にしてみました。
app/assets/stylesheets/mountain_view/layout.scss.mv-main { @media screen and (max-width: 768px) { width: 100%; padding: 30px 0; } } .mv-sidebar { @media screen and (max-width: 768px) { display: none; } }イニシャライザでMountain Viewでグローバルに読み込みたいCSSを指定することができます。
config/initializers/mountain_view.rbMountainView.configure do |config| config.included_stylesheets = ["mountain_view/layout"] endCSS読み込みの問題
MountainViewはディレクトリ名のコンポーネントをmountain_view.css.erbでディレクトリ名と同名のcssをまとめて読み込んでいるようです。
例えばbuttonコンポーネントでbootstrapをimportした後、別のコンポーネントを作ろうとしてみたところ、button.scssでしかimportしていないbootstrapが既に読み込まれています。
MountainViewの動作がCSS設計の方針と違った場合、私はマニュフェストファイルやレイアウトファイルをオーバーライドして調整するようにしています。
プレゼンターをスタブとして使ってしまう
Tipsとして、私は既存のコードをコンポーネント化する際、一旦Mountain Viewのプレゼンターをスタブとして一旦置いてみる方法をとっています。
app/components/post/post_component.rbclass PostComponent < MountainView::Presenter # コンポーネント作りに必要なデータを取得 def current_user User.find_by(email: 'necessary-user@exapmle.com') end endこんな感じでプレゼンターのメソッドを使うと既存のビューをコンポーネントでリプレースする工程がスムーズにいって便利です。
最後に
Railsのgemで完結するスタイルガイドを探したところ、Mountain Viewを改造しながら使っていくと言う道に辿り着きました。
もっとオススメのgemや、Mountain Viewの便利な使い方をご存知の方がいらっしゃいましたらぜひコメント欄で教えてください...?
- 投稿日:2019-02-15T10:59:56+09:00
Symbolオブジェクト
- 投稿日:2019-02-15T10:54:48+09:00
オブジェクト指向の強みは変更可能性にこそあると思った。
オブジェクト指向って再利用できるから良いってどこの記事、本にもまとめられています。
そんなの誰でもわかってるけど、それだけではいまいちピンと来ていなかった。たぶんそれは僕がオブジェクト指向ネイティブだから。
オブジェクト指向が普及していなかった時にプログラミングをしていた人からするとオブジェクト指向は画期的だったのだろう。でもそんなことは知ったこっちゃない、そうしろとプログラミング言語に言われるのだから。誰かに「あーしろ」「こーしろ」と言われるわけではない。そうしないと基本的に動かなかったり、意味不明な挙動をするのだ。無意識にできていることは素晴らしい事ではあるが、なぜそれが良いのか、どうしてそうするのかを知っていないとエンジニアとは言えない。
そんなある日クライアントにデモを見せるタイミングがあって、デモ中にこんな風にできない?あんな風だといいよね?みたいな言葉が出る。
そんなときに
「あー、それなら秒でできますよ。... リロードしてください。」
「おー!!」
みたいな体験があるとかっこいい。
オブジェクト指向で書いていると挙動の簡単な変更はすぐにできる。
しかもオプションでいろいろ変更できるようにしているとなおさら。
Javascriptのプラグインなどは基本的にoptionで簡単な挙動は変更できる。
クライアントは基本的に表面の動きを見ているので、表面の変えたいところが目の前で変わるのを見れば魔法か何かだと思ってしまうのも無理がない。エンジニアにとっては当たり前であるが、クライアント(非エンジニア)にとっては魔法に見えることが多くある。その芸を支えるのがオブジェクト指向だと思った。
単に書けるエンジニアではなく、デモで魅せられるエンジニアは強いとおもった。
- 投稿日:2019-02-15T10:27:55+09:00
Wercker × parallel_tests
why
Wercker上でのspecテスト完遂に35分ほど要するが、その時間を短縮して開発効率を改善したい。
※Wercker:Oracle提供のCIツール
what
テストを並列実行する。
how
環境Ruby:2.2.3 Rails:5.0.0.1 RSpec:3.5.2 sqlite:1.3.13local開発環境での実行
テスト並列実行用のgem "parallel_tests" を導入。
gemfile
に parallel_test を追加。Gemfilegroup :development, :test do ... gem 'parallel_tests' ... endgemパッケージをインストール。
$ bundle install
config/database.yml
に追加config/database.ymltest: database: yourproject_test<%= ENV['TEST_ENV_NUMBER'] %>テスト用DB:yourproject_testの複製
config/database.yml
の設定に応じてDBを作成。$ bundle exec rake parallel:create RAILS_ENV=testschemaコピー
$ bundle exec rake parallel:prepare[4]rpsec_test実行
$ bundle exec rake parallel:spec[4]※
[]
は並列実行させるプロセス数Wercker環境での実行
Wercker の docker内で複数DB作成
- 投稿日:2019-02-15T10:27:55+09:00
[WIP] Wercker × parallel_tests
why
Wercker上でのspecテスト完遂に35分ほど要するが、その時間を短縮して開発効率を改善したい。
※Wercker:Oracle提供のCIツール
what
テストを並列実行する。
how
環境Ruby:2.2.3 Rails:5.0.0.1 RSpec:3.5.2 MYSQL2:0.4.5local開発環境での実行
テスト並列実行用のgem "parallel_tests" を導入。
gemfile
に parallel_test を追加。Gemfilegroup :development, :test do gem 'parallel_tests' endgemパッケージをインストール。
$ bundle installtest用DB名に テスト環境変数 を追加
config/database.ymltest: database: yourproject_test<%= ENV['TEST_ENV_NUMBER'] %>テスト用DB:yourproject_testの複製
config/database.yml
の設定に応じてDBを作成。$ bundle exec rake parallel:create RAILS_ENV=testschemaコピー
$ bundle exec rake parallel:prepare[4]rspec_test実行
$ bundle exec rake parallel:spec[4]※
[]
は並列実行させるプロセス数Wercker環境での実行
WerckerのdockerコンテナにDB作成
wercker.ymlservices: - id: mysql:<version> env: MYSQL_USER: *** # DBのuser名 MYSQL_PASSWORD: *** # DBのpassword MYSQL_DATABASE: *** # DBの名前後の
rails-database-yml
コマンドはservices/env
プロパティで指定した内容のdatabase.yml
(DB名、ユーザー名、パスワードのDB)を作成。build: steps: - rails-database-ymlWerckerの同一dockerコンテナでDB複製
Initializing a fresh instance
When a container is started for the first time, a new database with the specified name will be created and initialized with the provided configuration variables. Furthermore, it will execute files with extensions .sh, .sql and .sql.gz that are found in /docker-entrypoint-initdb.d. Files will be executed in alphabetical order. You can easily populate your mysql services by mounting a SQL dump into that directory and provide custom images with contributed data. SQL files will be imported by default to the database specified by the MYSQL_DATABASE variable.コンテナーが初めて開始されると、指定された名前の新しいデータベースが作成され、提供されている構成変数で初期化されます。 さらに、/ docker-entrypoint-initdb.dにある拡張子.sh、.sql、および.sql.gzのファイルを実行します。 ファイルはアルファベット順に実行されます。
というわけで、
/docker-entrypoint-initdb.d
にSQLファイルを置いたりすればいい感じにできます。
が、SQLファイルでは、MYSQL_DATABASE
に指定したDBに対してダンプファイルを流すだけですので、他のDBを作ったりすることはできません。wercker.ymlservices: - id: mysql:*.*.** volumes: - "./mysql:/var/lib/mysql" - "./mysql/init:/docker-entrypoint-initdb.d" env: MYSQL_ROOT_PASSWORD: *** MYSQL_USER: *** MYSQL_PASSWORD: *** MYSQL_DATABASE: ***file_organization.+ docker-compose.yml +- mysql +- init + 1_create_db.sql + 2_import.sh + dump1.gz + dump2.gz参考
- 投稿日:2019-02-15T02:13:40+09:00
Vue on RailsでActive Storageを使って画像を保存する
はじめに
Rails API モードと Vue.js で作成した 自作ブログ で Active Storage を使う際に、画像の受け渡しでハマったので実装方法を残します。
実装するのは Active Storage を使って、eyecatch (アイキャッチ画像) 付きの Post (記事) を投稿できるようなサンプルです。環境
$ ruby -v ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin16] $ bundle exec rails --version Rails 5.2.2実装方法
API 部分 (Rails)
Ⅰ. サンプルアプリケーション新規作成
Rails API モードで作成します。
$ bundle exec rails new sampleapp --database=mysql --api --force $ cd sampleapp/ $ bundle exec rails db:createⅡ. Active Storage インストール
Active Storage をインストールします。
$ bundle exec rails active_storage:install $ bundle exec rails db:migrateⅢ. Post リソースの作成
今回利用する Post モデルとコントローラを作成します。
$ bundle exec rails g resource post title body:text $ bundle exec rails db:migrateⅣ. 各種ファイルの修正
各 Post に eyecatch を設定できるようにモデルを修正します。
追加する eyecatch= メソッドで、Active Storage に画像を保存します。このメソッドは、Base64 形式で受け取った image データをエンコードし、一時的に /tmp 配下に画像ファイルを作成、作成した画像ファイルをアタッチ、その後画像ファイルを削除します。
app/models/post.rbclass Post < ApplicationRecord has_one_attached :eyecatch attr_accessor :image def eyecatch=(image) if image.present? prefix = image[/(image|application)(\/.*)(?=\;)/] type = prefix.sub(/(image|application)(\/)/, '') data = Base64.decode64(image.sub(/data:#{prefix};base64,/, '')) filename = "#{Time.zone.now.strftime('%Y%m%d%H%M%S%L')}.#{type}" File.open("#{Rails.root}/tmp/#{filename}", 'wb') do |f| f.write(data) end eyecatch.detach if eyecatch.attached? eyecatch.attach(io: File.open("#{Rails.root}/tmp/#{filename}"), filename: filename) FileUtils.rm("#{Rails.root}/tmp/#{filename}") end end endモデルで追加した eyecatch= メソッドに POST で受け取る画像のパラメータを渡すように修正します。
app/controllers/posts_controller.rbclass PostsController < ApplicationController def create post = Post.new(post_params) if post.save post.eyecatch = post_params[:image] render json: post, status: :created else render json: post.errors, status: :unprocessable_entity end end private def post_params params.require(:post).permit(:title, :body, :image) end endⅤ. 動作確認
アプリケーションを起動し、curl コマンドでアイキャッチ付き Post が作成できるか確認します。
$ bundle exec rails s $ curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"post": {"title": "Sample title.", "body": "Sample body.", "image": "data:application/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAYAAACALL/6AAAACXBIWXMAADXU\nAAA11AFeZeUIAAAAgUlEQVQYlZWQMQ6DMAxFnyMGJFZCc4be/yisRYyVmDN0\niTuQULCCoH9z/PLtb/hTsi8SaA1yO85ZWHxgAqb8HuVoJAX+iKPtB17L++D+\nyN6drpOa0mj7AYBg1pmz99NmSKDiVzzmKTM/uOTYMjgQNetYuKoEqj7oCHp2\nteqn2/CVvuDZJy0n3DrVAAAAAElFTkSuQmCC\n"}}' http://localhost:3000/postscurl コマンドで作成後、Rails Console でアイキャッチが正常に追加できているか確認します。
$ bundle exec rails c irb(main):001:0> Post.find_by(title: "Sample title.").eyecatch.attached? => true # 画像のパスは以下のように取得できます。 # irb(main):002:0> app.url_for(Post.find_by(title: "Sample title.").eyecatch) # => "画像のパス"フロント部分 (Vue.js)
Ⅰ. Webpacker をインストール
webpacker gem を追加する。
Gemfilegem 'webpacker', '~> 3.5'Webpacker をインストールします。
$ bundle $ bundle exec rails webpacker:installⅡ. Vue.js をインストール
Webpacker で Vue.js をインストールします。
$ bundle exec rails webpacker:install:vueⅢ. Home ページを作成
Vue.js を返すための Home ページを作成します。
$ bundle exec rails g controller Pages HomeRoot パスに Home ページを設定します。
config/routes.rbRails.application.routes.draw do root 'pages#home' end標準の JSON ではなく、ERB を返すために ActionController::Base に修正します。
app/controllers/pages_controller.rbclass PagesController < ActionController::Base def home end endHome ページに利用する View を作成します。
$ mkdir -p app/views/pages/ $ touch app/views/pages/home.html.erbⅣ. Vue.js を利用
Vue.js を利用するための設定を行います。
app/views/pages/home.html.erb<%= javascript_pack_tag 'main' %>利用する各種ファイルを作成します。
$ touch app/javascript/packs/main.js $ touch app/javascript/packs/App.vueapp/javascript/packs/main.jsimport Vue from 'vue' import App from './App.vue' document.addEventListener('DOMContentLoaded', () => { const el = document.body.appendChild(document.createElement('main')) new Vue({ el, render: h => h(App) }) })app/javascript/packs/App.vue<template> <div id="app"> <p>投稿フォーム</p> </div> </template>Ⅴ. 投稿フォームを作成
API コールに利用する axios をインストールします。
$ yarn add axios
画像投稿するフォームを用意します。
画像は POST する前に Base64 にデコードしています。app/javascript/packs/App.vue<template> <div id="app"> <p>投稿フォーム</p> <form v-on:submit.prevent="postItem()"> <p> <label>Title</label> <input name="post.title" type="text" v-model="post.title"><br /> </p> <p> <label>Body</label> <input name="post.body" type="text" v-model="post.body"><br /> </p> <p> <label>画像</label> <input name="uploadedImage" type="file" ref="file" v-on:change="onFileChange()"><br /> </p> <input type="submit" value="Submit"> </form> </div> </template> <script> import axios from 'axios' export default { data() { return { post: {}, uploadedImage: '' } }, methods: { onFileChange() { let file = event.target.files[0] || event.dataTransfer.files let reader = new FileReader() reader.onload = () => { this.uploadedImage = event.target.result this.post.image = this.uploadedImage } reader.readAsDataURL(file) }, postItem() { return new Promise((resolve, _) => { axios({ url: '/posts', data: { post: this.post }, method: 'POST' }).then(res => { this.post = {} this.uploadedImage = '' this.$refs.file.value = '' resolve(res) }).catch(e => { console.log(e) }) }) } } } </script>Ⅵ. 動作確認
アプリケーションを起動し、投稿フォームから画像を投稿します。
http://localhost:3000/$ bundle exec rails sRails Console でアイキャッチが正常に追加できているか確認します。
$ bundle exec rails c irb(main):001:0> Post.last.eyecatch.attached? => true参考記事
https://qiita.com/ozin/items/5ec81a4b126b8ebf7a96
最後に
読んでいただいてありがとうございます。
間違っている点などがありましたら、ご指摘いただけると喜びます!
- 投稿日:2019-02-15T01:59:55+09:00
CodinGame はBOT(AIプログラム)でバトルするのが正しい楽しみ方かもしれません
『【CodinGame】ブラウザでコーディングの基礎からトレーニングできるサイト (疑似ゲーム開発環境を使って学べます。解答は25種類のプログラミング言語から選択して記述可能!)』 という記事で、CodinGame に対してなにやら否定的なコメントを書いてしまいましたが、Twitter で「codingame」を検索してみると、「CodinGame はBOT(AIプログラム)でバトルするのが正しい楽しみ方」的な発言がみられたので、AIについては素人ながら挑戦してみました。
- まだ、挑戦し始めなのでログ(ブログ)っぽく、やったことをそのまま記述…
- 勝ち方の指南なんてできないので…「他の人が自分もやってみたい」と思えるような紹介風で…
という目標で書いてます。
長くなる(と思う)ので記事分けながら書いて、あとでマトメの記事が上手くできればいいなと考えています。
それでは、以下本文へ
1. https://www.codingame.com/start にアクセスします。
2. 「Sign up with Google」を選択します。
3. サインアップ完了後、https://www.codingame.com/home に自動的に遷移します。
4. https://www.codingame.com/multiplayer に遷移します。
5. https://www.codingame.com/multiplayer/bot-programming に遷移します。
6. TRON BATTLE のリプレイ動画
以下のツイート内の画像をクリックすると「TRON BATTLE のリプレイ動画」に飛びます。
この動画を見て分かるように、各プレイヤーの車は異なった色のリボンを残しながら進んでいきます。各プレイヤーの車は自分のリボンも他人のリボンも踏んではいけません(踏んだらアウト)。もちろん場外に出てもいけません。上記に違反した時点でそのプレイヤーはアウトとなり、そのプレイヤーのリボンがゲーム画面から消えます(ここもポイント!)。
最後までアウトにならずに生き残ったプレイヤーの勝ちです。(実際には1位、2位、…と順位がつきます)
Tron Battle - Replayhttps://t.co/0sMOpmueln
— JavaCommons (@javacommons) 2019年2月14日7. TRON BATTLE のメイン画面の「JOIN」を押してプログラム編集画面(IDE)を開きます
8. コードエディタに初期に表示される内容(C#の場合)
解答に使うプログラミング言語は、C#, C++, Java, Javascript, Python3, Bash, C, Clojure, Dart, F#, Go, Groovy, Haskell, Kotlin, Lua, ObjectiveC, OCaml, Pascal, Perl, PHP, Python2, Ruby, Rust, Scala, Swift, VB.NET の中から自由に選べます。
CodinGame のプログラミング問題はほとんど(全て?)、刻々と標準入力から情報を読み取り、刻々と標準出力に指示を書き出すというループから成り立っています。
- このおかげでプログラミング言語間の差異を吸収しやすくなっています。ユーザーが書くプログラムを取り巻く親プロセスのプログラムは共通の物が使えるからです。
static void Main(string[] args)
に合わせて、全プログラムを static メソッドで書こうとするとクラスを導入する際にハマることがありますのでご注意。コードエディタに初期に表示される内容(C#の場合)using System; using System.Linq; using System.IO; using System.Text; using System.Collections; using System.Collections.Generic; /** * Auto-generated code below aims at helping you parse * the standard input according to the problem statement. **/ class Player { static void Main(string[] args) { string[] inputs; // game loop while (true) { inputs = Console.ReadLine().Split(' '); int N = int.Parse(inputs[0]); // total number of players (2 to 4). int P = int.Parse(inputs[1]); // your player number (0 to 3). for (int i = 0; i < N; i++) { inputs = Console.ReadLine().Split(' '); int X0 = int.Parse(inputs[0]); // starting X coordinate of lightcycle (or -1) int Y0 = int.Parse(inputs[1]); // starting Y coordinate of lightcycle (or -1) int X1 = int.Parse(inputs[2]); // starting X coordinate of lightcycle (can be the same as X0 if you play before this player) int Y1 = int.Parse(inputs[3]); // starting Y coordinate of lightcycle (can be the same as Y0 if you play before this player) } // Write an action using Console.WriteLine() // To debug: Console.Error.WriteLine("Debug messages..."); Console.WriteLine("LEFT"); // A single line with UP, DOWN, LEFT or RIGHT } } }9. さて、TRON BATTLE の課題(問題)文は以下のような内容です
(長いので折りたたみ中。展開してご覧ください)
◎The Goal
◎目標In this game your are a program driving the legendary tron light cycle and fighting against other programs on the game grid.
このゲームであなたが目指すのは、ゲームグリッド上で、伝説のトロンライトサイクルを運転して他のプログラムと戦うことのできるプログラムです。
The light cycle moves in straight lines and only turn in 90° angles while leaving a solid light ribbon in its wake. Each cycle and associated ribbon features a different color.
Should a light cycle stop, hit a light ribbon or goes off the game grid it will be instantly deactivated.ライトサイクルは真っすぐに進むか90°の角度でしか曲がれず、起動時から固形分からなる光のリボンを残しながら進みます。それぞれのライトサイクルと関連付けられたリボンは異なる色を放ちます。
ライトサイクルが停止せざるを得ない、または光のリボンに衝突した、またはゲームグリッドの外に出た場合、そのライトサイクルは即座に非活性化されます。The last cycle in play wins the game. Your goal is to be the best program: once sent to the arena, programs will compete against each-others in battles gathering 2 to 4 cycles. The more battles you win, the better your rank will be.
最後まで残ったライトサイクルがゲームの勝者となります。あなたの目標はベストプログラムを目指すことです: アリーナに送られれば(訳注: SUBMITボタンを押せば)、プログラム達が、2~4台でのライトサイクルバトルでお互いに競争となります。より多くかつほどあなたのランクが上がります。
◎Rules
◎ルールEach battle is fought with 2 players. Each player plays in turn during a battle. When your turn comes, the following happens:
それぞれのバトルは2プレイヤーで戦います。それぞれのプレイヤーが順番にプレイします。あなたのターンが来たら、以下が発生します:
- Information about the location of players on the grid is sent on the standard input of your program. So your AI must read information on the standard input at the beginning of a turn.
- グリッド上のプレイヤーの位置情報があなたのプログラムの標準入力に送信されます。そのため、あなたのAIはターンの最初に標準入力上の情報を読み込まなければなりません。
- Once the inputs have been read for the current game turn, your AI must provide its next move information on the standard ouput. The output for a game turn must be a single line stating the next direction of the light cycle: either UP, DOWN, LEFT or RIGHT.
- 現在のゲームターンのための情報を読み込んだら、AIは次の移動のための情報を標準出力に提供しなければなりません。ゲームターン時の出力は、ライトサイクルの次の移動方向を宣言する一行の出力でなければなりません: UP, DOWN, LEFT, RIGHT のいずれかを出力します。
- Your light cycle will move in the direction your AI provided.
- あなたのライトサイクルはAIが出力した方向に動きます。
- At this point your AI should wait for your next game turn information and so on and so forth. In the mean time, the AI of the other players will receive information the same way you did.
- この時点で、あなたのAIは次のゲームターンの情報を待たなければなりません。後は、ここまでの繰り返しとなります。一方で、他のプレイヤーのAIもあなたと同様に情報を受け取ります。
If your AI does not provide output fast enough when your turn comes, or if you provide an invalid output or if your output would make the light cycle move into an obstacle, then your program loses.
もし、あなたのAIがあなたのターンが来た時に、十分高速に出力を提供できない場合、または妥当でない出力をした場合、または出力に従うとライトサイクルが障害物に衝突してしまう等の場合には、あなたのプログラムの負けとなります。
If another AI loses before yours, its light ribbon disappears and the game continues until there is only one player left.
もし他のAIがあなたより前に負けた場合は、その光のリボンは消滅し、一人のプレイヤーのみが残るまでゲームは継続します。
The game grid has a 30 by 20 cells width and height. Each player starts at a random location on the grid.
ゲームグリッドは、30x20 のセルで構成されます。それぞれのプレイヤーはグリッド上のランダムな位置からスタートします。
◎Victory Conditions
◎勝利条件Be the last remaining player
最後まで残るプレイヤーとなること。◎Game Input
◎ゲームの入力Input for one game turn
ゲームターン毎の入力Line 1: Two integers N and P. Where N is the total number of players and P is your player number for this game.
一行目: N と P の2つの整数。Nはプレイヤーの総人数で、Pはこのゲームでのプレイヤー番号です。
The N following lines: One line per player. First line is for player 0, next line for player 1, etc. Each line contains four values X0, Y0, X1 and Y1. (X0, Y0) are the coordinates of the initial position of the light ribbon (tail) and (X1, Y1) are the coordinates of the current position of the light ribbon (head) of the player. Once a player loses, his/her X0 Y0 X1 Y1 coordinates are all equal to -1 (no more light ribbon on the grid for this player).
続くN行: プレイヤー毎に一行。最初の行はプレイヤー0に対するもの、次の行はプレイヤー1、という形になります。それぞれの行は4つの値 X0, Y0, X1, Y1 を含みます。(X0, Y0) は光のリボンの初期位置(tail)で (X1, Y1) はプレイヤーの光のリボンの現在位置(head)です。あるプレイヤーの負けが決定すると、そのプレイヤーの X0 Y0 X1 Y1 の値hあ全て -1 となり、そのプレイヤーの光のリボンはグリッド上には存在しないことを意味します。
Output for one game turn
ゲームターン毎の出力A single line with UP, DOWN, LEFT or RIGHT
UP, DOWN, LEFT, RIGHT のいずれかを一行で出力。
Constraints
制約2 ≤ N ≤ 2
0 ≤ P < N
0 ≤ X0, X1 < 30
0 ≤ Y0, Y1 < 20Your AI must answer in less than 100ms for each game turn.
あなたのAIは各ゲームターンに対して100ms未満で応答しなければなりません。10. とりあえず、壁への激突、リボンへの激突を避ける目的で作ったプログラム
まったくAI的なことしてませんが、ゲームターン毎に隣(上下左右)のセルだけ見て、障害物がなければそちらに進む(判定順序: 左⇒右⇒上⇒下)。毎ターン、自キャラも含めて位置情報を二次元配列に格納(死んだキャラのリボン情報の消去も一応実装済み。初期は2キャラしかいないのでテストできませんw)。
- コメントに大体書いたので一点だけ補足すると、ライトサイクルが曲がるとき90°までしか曲がれない(来た方向に戻るようなことはできない)というのをどう表現しようかと迷っていたんですが、自キャラの光のリボンも配列(マップ)に記録して障害物と見做しているので、とりあえず障害物判定するだけでいける方向に進めば良いことだと気づきました。
using System; using System.Linq; using System.IO; using System.Text; using System.Collections; using System.Collections.Generic; class Player { static void Main(string[] args) { Player control = new Player(); string[] inputs; Position[] positions; // game loop while (true) { inputs = Console.ReadLine().Split(' '); int N = int.Parse(inputs[0]); // total number of players (2 to 4). int P = int.Parse(inputs[1]); // your player number (0 to 3). Console.Error.WriteLine("P={0}", P); positions = new Position[N]; for (int i = 0; i < N; i++) { inputs = Console.ReadLine().Split(' '); int X0 = int.Parse(inputs[0]); // starting X coordinate of lightcycle (or -1) int Y0 = int.Parse(inputs[1]); // starting Y coordinate of lightcycle (or -1) int X1 = int.Parse(inputs[2]); // starting X coordinate of lightcycle (can be the same as X0 if you play before this player) int Y1 = int.Parse(inputs[3]); // starting Y coordinate of lightcycle (can be the same as Y0 if you play before this player) positions[i] = new Position(i, X0, Y0, X1, Y1); } string dir = control.HandleVehicless(positions, P); control.DumpMap(); Console.WriteLine(dir); } } // 自分も含めて誰かが通った座標を記憶しておくために使う。 // 誰も通ってない場合は -1。通った、または現在いるマスに対してはプレイヤーのメンバーIDを格納する。 int[,] map = new int[30,20]; // メインコントロールクラスのコンストラクタ(map内の値を-1(=誰も通ってない)に初期化しておく。) private Player() { for(int y=0; y<20; y++) { for(int x=0; x<30; x++) { map[x, y] = -1; } } } private string HandleVehicless(Position[] positions, int myIndex) { Position me = positions[myIndex]; // me = 自分の座標情報 Console.Error.WriteLine(me); // me を標準エラーに出力(public override string ToString()の定義による) foreach(var p in positions) { // (me も含めて)全キャラの座標を通ってはいけない場所に登録。 AddToMap(p); // ただし、死にキャラの場合はそのキャラの座標情報を全消去する。 } // 上下左右のマスを判定し通ってはいけない場所でなければその方向を返す。 if (!FoundFromMap(me, -1, 0)) return "LEFT"; if (!FoundFromMap(me, 1, 0)) return "RIGHT"; if (!FoundFromMap(me, 0, -1)) return "UP"; if (!FoundFromMap(me, 0, 1)) return "DOWN"; return "LEFT"; // ここに来た時点でどの方向も通れないが一応正式な値の一つとして "LEFT" を返す。 } void AddToMap(Position p) { if (p.X1 < 0) { DeleteMemberIdsFromMap(p.Id); // 現在座標がマイナス値で来たら死にキャラなのでマップから消す。 return; } this.map[p.X1, p.Y1] = p.Id; // 配列にプレイヤーのメンバーIDを登録する。 } void DeleteMemberIdsFromMap(int id) { for(int y=0; y<20; y++) { for(int x=0; x<30; x++) { if (map[x, y] == id) map[x, y] = -1; } } } // map を検索して通れない場所の場合 true を返す。通れる場合は false。 // me(自機の座標)に xOffset と yOffset を加えた場所について判定(検索)する。 bool FoundFromMap(Position me, int xOffset, int yOffset) { int x = me.X1+xOffset; int y = me.Y1+yOffset; if (x < 0) return true; if (x > 29) return true; if (y < 0) return true; if (y > 19) return true; return map[x, y] != -1; } // デバッグ用に 30x20 のマップを表示(現在生きているメンバーのIDを表示。空のマスは '-' を出力) void DumpMap() { for(int y=0; y<20; y++) { for(int x=0; x<30; x++) { if (map[x, y] == -1) Console.Error.Write("-"); // -1の場合はマイナス記号を出力。 else Console.Error.Write(map[x, y]); // -1でなければプレイヤーID(0以上)を出力。 Console.Error.Write(" "); } Console.Error.WriteLine(); } } } // キャラクターの座標を登録・記憶しておくための入れ物。 // Main 関数が受け取る標準入力の情報を格納するための構造体のようなもの。 // Player インスタンスの各メソッドの引数は標準入力とのやり取りを意識せず、この構造体を期待できる。 class Position { public int Id; public int X0; public int Y0; public int X1; public int Y1; public Position(int id, int x0, int y0, int x1, int y1) { this.Id = id; this.X0 = x0; this.Y0 = y0; this.X1 = x1; this.Y1 = y1; } // デバッグなどで出力される際のフォーマットを制御する。 public override string ToString() { //return "{X0:" + X0 + ", Y0:" + Y0 + ", X1:" + X1 + ", Y1:" + Y1 + "}"; return String.Format("{{X0:{0}, Y0:{1}, X1:{2}, Y1:{3}}}", X0, Y0, X1, Y1); } }11. アリーナでリーグ戦をする前に「PLAY MY CODE」ボタンで確認
12. 対戦実行速度を上げてサクサクデバッグ
13. アリーナ(リーグ戦)に挑戦
14. リーグ戦でボスに勝ったら以下のような画面が表示されます
- 最初のリーグではプレイヤーの数は2ですが、リーグが上がっていくと増えていくみたいです。
15. リーグ戦で勝てず上位リーグに上がれなかった場合の対処法
他にも負けた相手がいる場合には、同様の手順でIDEに読み込んで対戦しながらプログラム(AI)を強くするとよいでしょう。
16. 最後に
AIを作るノウハウを持っていないことと、強いプログラムを記事で晒すのはいいアイディアではないかなと思ってますので、今回は TRON BATTLE を紹介しましたが、次はまた別のプログラムについて紹介したいと思っています。以下のツイートの画像をクリックしていただければ、そのゲームのリプレイ画面が表示されます。
もし、分かりにくいところなどありましたらコメント等をよろしくお願いいたします。それでは…Coders Strike Back - Replayhttps://t.co/DM0yyVfGSl
— JavaCommons (@javacommons) 2019年2月14日P.S.
https://www.codingame.com/multiplayer/bot-programming/tron-battle/leaderboard