RoadMovie

write down memos or something I found about tech things

ReactNative - "Could not get BatchedBridge, make sure your bundle is packaged properly”

ReactNativeでAndroidをリリースしようとした時に起こったエラーの対処法です。

Android Studioから Build > Generate Signed APKapp-release.apk を作ろうとしました。 この方法はローカル環境ではうまくいっているように見えるのですが、PlayStoreからダウンロードしたものを立ち上げようとするとクラッシュします。

下記エラーログです。

Could not get BatchedBridge, make sure your bundle is packaged properly.


結論から言うと、consoleからsigned apkを作らなければいけなかったということでした。 下記の方法でうまくいくはずです。 facebook.github.io

$ cd android &&./gradlew assembleRelease

もしAndroidバイスデバッグに使っているのであれば、下記のコマンドでUSB経由でapkをインストールできます。

$ adb install android/app/build/outputs/apk/app-release.apk


お役に立てれば嬉しいです。

React.js & redux-form のテストをmocha & chai で書く

reactjs, redux-form, mocha, chai


こんにちは。この記事ではredux-formをReact.jsで使った際のテストについて紹介したいと思います。 話をシンプルにするために、簡単なcomponentとreducerのテストを見せます。

下記がテスト用に使っているライブラリになります。

※コードはES6で書いてwebpack等でコンパイルすることを前提にしてます。

test
├── components
│   └── postcode.spec.js
├── reducers
│   └── index.spec.js
└── setup.js

Component test

ここでテストしているのは

  • componentが正しいタイトルを持っているか
  • componentが正しいfieldを持っているか
  • componentが正しい値を返すか

になります。 redux-formではそれぞれのcomponentにstoreを渡す必要があります。なので、ここではcomponentをProviderでラップしています。また、onSubmitのメソッドとして returnState を渡しているので、送信ボタンを押したあとの値をそこでチェックすることができます。

Reducers test

Tips for jsdom

もしチュートリアル通りにやっていれば、 mocha-jsdom: already a browser environment or mocha-jsdom invoked twice. use 'skipWindowCheck' というエラーをみたことがあるかもしれません。これを避けるために setup.js を用意しました。windowdocument を遣う場所でこれをロードしておけばこのエラーを避けることができます。

Test command

最後に、テストは下記のコマンドで実行することができます。

$ npm run test

in package.json

scripts: {
    test: mocha --compilers js:babel-core/register --colors ./test/**/*.spec.js
}

References

mocha-jsdom (to avoid mocha-jsdom: already a browser environment or mocha-jsdom invoked twice. use 'skipWindowCheck') https://github.com/rstacruz/mocha-jsdom/blob/master/examples/basic/test.js

redux-form integration test https://github.com/tylercollier/redux-form-test/blob/master/tests/integration/index.js

unit test https://github.com/tylercollier/redux-form-test/blob/master/tests/unit/index.js#L76

basic test syntax for redux http://redux.js.org/docs/recipes/WritingTests.html

syntax for chai http://chaijs.com/api/bdd/


それでは!

Elixir/Phoenixで作ったアプリの簡単なデプロイ方法 by ansible

f:id:mr7myself:20190127105617p:plain

こんにちは! Ruby/Railsプログラマーの方がElixir/Phoenixにチャレンジしていると、「Capistranoみたいに簡単にデプロイする方法ないの?」と思うかもしれません。

私が調べたところ、完璧にCapistranoの代替になるようなライブラリはなさそうでした。そこで下記の2つの方法を試してみようと思ったのですが、

  • Deployment via docker
  • Deployment via Ansible

docker管理は慣れているとそれほどですが、プライベートでやるにはtoo much workかなと思い今回は避けました。なので、今回ご紹介する方法はansibleによるデプロイ方法です。これはとても簡単なのでぜひ試してみてください。

なぜ簡単なのか。それはAnsistranoというライブラリがほとんどCapistranoのような振る舞いをしてくれるからです。

Ansible role to deploy scripting applications like PHP, Python, Ruby, etc. in a capistrano style

そう、ここではElixir/Phoenixをあげましたが、それらでなくても問題なくデプロイできます。 今回はPhoenixのデプロイプロセスをこのライブラリに当てはめていきます。

参考までに私が作ったレポジトリになります。

ansible-for-phoenix


使い方

1. 環境設定
書き換える必要がある箇所を"TODO"としていますので、grepしてみてください。

2. サーバを用意

$ ansible-playbook -i production centos.yml -u root

3. Deploy app

$ ansible-playbook -i production deploy.yml -u deploy

基本的にはこれだけになります!細かいところは先程あげたレポジトリに譲りますが、それほど詰まることなくデプロイまで行けると思います。

ansible-for-phoenix

中規模Railsアプリのアーキテクチャ設計

Railsでアプリを作っていますか?設計に問題を抱えている、あるいは悩んでいませんか?もしそうであればこの記事が役に立つかもしれません。

アプリケーションが大きくなっていくに連れて、コードがカオスになってきたり、どこで何が起こっているのか追いにくくなってくるものです。いわゆる「ファットコントローラ」「ファットモデル」と呼ばれる問題ですね。ところで、どうしてこの問題が起こるのでしょうか。それはMVCアーキテクチャが常に完璧ではないからです。Railsはアプリケーションの立ち上げに関しては本当に簡単な方法を提供してくれています。素晴らしいです。単にMVCと呼ばれる構造に従ってコードを書いていけばアプリケーションを書けるようになるのです。ただ、前述のように、どこかのタイミングで設計の変更を余儀なくされていきます。もうあなたはそのフェーズにいるかも知れません。どうやって何を変更したら良いのでしょう。

ここでは、ひとつの解決方法として、中規模サイズのRailsアプリケーションにおけるストラテジーを紹介します。ここでの方法はDDD(Domein Driven Design)と呼ばれる手法をベースにしています。記事の最後に本を紹介しているので興味があればぜひ手にとってみてください。


Environment

f:id:mr7myself:20190126174244j:plain
設計図



コントローラ層

コントローラ層はすべてのリクエストを受け付けます。基本的に、この層のみがドメイン層を呼べます。この層は可能な限りシンプルに保たれるべきです。

  def save
    @request.assign_attributes(assignable_request_params)
    if @request.save
      RequestFlow::ReceiveFromAPI.execute(@request, @request_token)
      render json: { result: 'success' }
    else
      render json: { result: 'failed' }
    end
  end

ここでの RequestFlow::ReceiveFromApiドメインです。

ドメイン

これは最も重要な層のひとつです。ドメインは技術者のみでなく、ビジネスやカスタマーサポートの人にも理解可能な共通言語で名前付けしてください。このタイミングでみんなで話し合うのもいいですね。例えば私は SelectXXX, SendXXX, CreateXXX のようにつけることが多いです。ここでEmailなどの通知のようなフックイベントを定義します。理想的にはドメインはプロセス(手順)であったほうがよいです。

module RequestFlow
  module Update

    module_function

    def execute(request, options={})
        GeocodeJob.perform_later(request) unless request.geocoded?
        Pipedrive::Updator.update_parameters(
          PipedriveStage.hash[request.status],
          request.pipedrive_deal,
          request
        )
        request.update_status
      end

      unless options[:skip_notification] == true
        # This is a Service Layer
        Notifier::Request.complete_updating(request_offer)
      end
    end
  end
end 

ドメインはモデルとサービスを呼ぶことができます。しかし彼らから"呼ばれる"のは禁止です。 ドメインは中で何が起こっているのか理解しやすいようにしましょう。そうすると、このような会話が生まれることが想定されます。

CS: ユーザーがリクエストを更新したとき、何がおきるんだっけ? IT: ドメインをチェックしてみるね。そのリクエストは位置情報を付与されて、pipedriveに情報を投げているね。その後アップデート完了という流れだね。

いい感じですね。

サービス層

ここはステートレスな層です。サービスはコマンド(命令)のようなものだと思ってください。例えば、geocode_setter, locale_finder, distance_calculator などです。私の経験では Notifier (通知者)をここに置くのは良いと思います。

  class Notifier::Request
    def initialize(request, timing = :wait, skip_validation = false)
    end

    ...

    def complete_request
      klass = new(request)
      klass.notify_internaly
      klass.via_sms
      klass.via_email
    end

    def via_email
      # call ActionMailer
    end
  end

モデル層

サービス層とドメイン層のおかげで、モデル層は薄くできるようになったはずです。モデルは自身が何を表しているかだけに関心を持ちます。純粋なRubyクラスもどんどん作りましょう。ActiveRecord::Base を継承してることは必須ではないですからね。pureなRubyクラスは綺麗なデザイン、理解しやすい設計に大いに役立つはずです。

バリデーション層

この層もモデル層を薄く保つのに役立ちます。もしRailsにおいてバリデーション層をどう実装していいか知らなければ検索してみてください。実装方法は簡単で、ActiveModel::Validator を継承するだけです。 それから validates_with などを実装しましょう。当然ですが、ここではバリデーションのみに関心を持ちましょう。



これが基本的な設計の考え方にになります。もちろん、実際のプロダクトではもっと複雑になるでしょうけど、いままで私が関わったプロダクトではこのやり方はスケーラビリティにおいてもうまくいきました。この設計で重要なのは「誰が誰を呼べるか」の「方向(矢印)」です。これが正しく守られていないと、またカオスに逆戻りです。

もしもっとこの設計の理解を深めたければ下記の本がおすすめです。

それでは、Enjoy coding!

redux-formでwizard実装

最近onBoardingページにReact.jsをredux, redux-formと使い始めました。 今回はその際に私が躓いた3つのポイントを紹介します。

  1. デフォルト値の設定方法
  2. ラジオボタンを使ったラベル選択方法
  3. ユーザーが次のフォームに移った時にどうやって自動的に値を設定するか

ちなみに開発環境は下記になります

  • "react": "15.4.2"
  • "redux": "3.6.0"
  • "redux-form": "6.4.3"

デフォルト値の設定方法

もしデフォルトのフォームを使っているのであれば、単純にinitialValueを使って実現できます。

const FirstForm = (props) -> (
  <div>
    ...
  </div>
)

export default reduxForm({
  form: "wizard",
  initialValue: { name: "My Name" }
})

しかし、redux-formのwizardを利用している場合、少し厄介なことになります。wizard formでは、複数個のformが出現しますが、値をイニシャライズするタイミングが最初の一度しかないからです。各フォームが出現するたびに何度もイニシャライズを走らせ直して値を再設定する方法もありますが、少し気持ち悪いですよね。結果として、単純ですが、各Componentの初期化タイミングで設定するとうまく行きます。具体的には componentWillMount() で設定してます。

実装はこんな感じです。

class FirstForm extends React.Component {
  componentWillMount() {
    this.props.change("form-name", "value");
  }
}

export default reduxForm({
  form: 'wizard',                 // <------ same form name
  destroyOnUnmount: false,        // <------ preserve form data
  forceUnregisterOnUnmount: true,  // <------ unregister fields on unmount
  validate
})(FirstForm);

ラジオボタンを使ったラベル選択方法

ラジオボタンをカスタマイズしたい時によくぶつかる問題です。これは単純に下記のように実装できます。

const setSelected = element => {
  document.getElementById(element.currentTarget.htmlFor).checked = true
  element.currentTarget.className = "selected"

  return false
}

export default setSelected;

そして、onClickメソッドとしてこのメソッドを呼び出します。

<label htmlFor="form-name" onClick={setSelected}>
...
</label>
<Field name="form-name" className="hidden">

ユーザーが次のフォームに移った時にどうやって自動的に値を設定するか

私は電話番号の入力に react-phone-input というライブラリを使用しました。この時、2つのフォームを用意しました。1つ目が、このライブラリが返してくるユーザーインプット。もうひとつが、redux-formが提供するhidden formです。 ユーザーが何かを入力すると、そのhidden-formを更新することになります。

setPhoneNumber = () => {
  var phoneNumber = document.getElementsByClassName("react-tel-input")[0].children[0].value
  this.props.change("phone_number", phoneNumber)
}

...

<ReactPhoneInput
  defaultCountry="de"
  onChange={() => this.setPhoneNumber()}
/>
<Field name="phone_number" type="text" component={renderPhoneNumberField} className="hidden" />

こんな感じになります。redux-formは便利ですが癖があるのでよくdocumentを読むことをおすすめします。

【メール送信エラー】Net::SMTPAuthenticationError

メール送信周りでちょっとはまりかけたのでメモ。
ponyというgemを使ってSMTPでメール送信をしようとしてました。
サイトからユーザーが申し込みしてきたら、申込完了メールをユーザーに送信するイメージです。

★環境
Ruby
・pony on sinatra
Gmailで送信(from @gmail.com)

たぶんRails(ActionMailer)でもあまり変わらないと思います。

普通にドキュメントに書かれてる通り実装すると、

Net::SMTPAuthenticationError - 534-5.7.14 <https://accounts.google.com/ContinueSignIn ...

というエラーが出てしまいました。
http://www.google.com/accounts/DisplayUnlockCaptcha
上記にアクセスして許可すればいいとう記事もありましたが、うまくいかず。

結論をいうと、送信したいGmailアカウントの設定から2段階認証を有効にして、
アプリ固有のパスワードを作り、実装にそのパスワードを組み込むのが手っ取り早いと思います。

スクリーンショット 2014-10-24 11.22.22.png

↑ 左上の「2段階認証プロセス」の設定を有効にすると「アプリパスワード」という項目が現れるので設定してください。

以上でうまくいくと思います。

※参考までにponyを使った実装を載せておきます。

def send_complete_mail
  # charsetはデフォルトでutf-8なのですが、指定してないと怒られた気がします。
  Pony.mail(
    to: '送信先メールアドレス',
    subject: '件名',
    body: send_mail_body,
    charset: 'utf-8',
    via: :smtp,
    via_options: {
      address: 'smtp.gmail.com',
      user_name: 'アカウント名@gmail.com',
      password: 'アプリ固有のパスワード',
      authentication: :plain,
      domain: 'サイトのドメイン名'
    }
  )
end

def send_mail_body
  # send_mail.erbというファイルを用意しておけばよいです。
  # localsのハッシュでview内の変数に値を渡せます。
  erb :send_mail, locals: {
    hoge: session[:hoge],
    fuga: session[:fuga]
  }
end

Mechanizeでページ遷移しながらスクレイピング

ちょっとダルいポイントが有ったのでメモ程度に。
スクレイピング対象サイトとスクレイピングの流れは

  • ページャで何ページか一覧ページがある
  • 一覧ページのタイトルをクリックすると詳細ページが見れる
  • 詳細ページの一部を使用
  • また他のタイトルをクリックしていく
  • CSVで出力(別にいらないけどメモ代わりに。。)

みたいな感じです。mechanizeだけでやります。

require 'mechanize'
require 'csv'

class ScrapingPages

  def initialize
    @agent = Mechanize.new
    @data = []
  end

  def retrieve
    # 1ページ目から10ページ目までスクレイピングする
    (1..10).each do |i|
      page = @agent.get(url(i)
      each_section(page) do |section|
        title = section.css('h2.title > a').first.text
        detail = @page.links_with(text: title).first.click
        @data << {
          title: title,
          detail: detail.links.first.text
        }
      end
    end
  end

  def each_section(page)
    page.search('.articleBox').each do |section|
      yield section
    end
  end

  def url(current_page)
    "https://hogehoge.com/#{current_page}"
  end

  def make_file_as_csv
    CSV.open("./csv/scraping-#{Time.now.to_i}.csv", "wb", encoding: 'Shift_JIS') do |csv|
      csv << %w(title detail)
      @data.each do |record|
        csv << [record[:title], record[:detail]]
      end
    end
  end
end

scraping_pages = ScrapingPages.new
scraping_pages.retrieve
scraping_pages.make_file_as_csv

コードは実際のものとちょこちょこ変更箇所あるので流しでいいのですが、ポイントはclickのところ。
mechanizeは中でnokogiriを使っているようで、
上記の@agent, pageとかはmechanizeクラスが 親のオブジェクトなのですが、
.cssとか使うと返ってくるオブジェクトがnokogiriクラスのインスタンスオブジェクトが返ってきます。

で、clickメソッドはmechanizeクラスに対してしか使えないので、微妙に工夫が必要。

title = section.css('h2.title > a').first.text
detail = @page.links_with(text: title).first.click

# .css('h2.title > a').first.clickとかすると、
# nokogiriがclickメソッド持ってないのでエラーになる。

ここです。title変数で一旦クリック個所のテキストを格納しておいて、
mechanizeクラス継承の@pageに対してリンクを辿って
テキストを指定してクリックする、と。

csvはよく忘れるのでメモ程度に載せただけです。

もっと良いやり方あるかもだけど。