ReactNative - "Could not get BatchedBridge, make sure your bundle is packaged properly”
ReactNativeでAndroidをリリースしようとした時に起こったエラーの対処法です。
Android Studioから Build > Generate Signed APK
で app-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 で書く
こんにちは。この記事では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
を用意しました。window
や document
を遣う場所でこれをロードしておけばこのエラーを避けることができます。
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
こんにちは! 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のデプロイプロセスをこのライブラリに当てはめていきます。
参考までに私が作ったレポジトリになります。
使い方
1. 環境設定
書き換える必要がある箇所を"TODO"としていますので、grepしてみてください。
2. サーバを用意
$ ansible-playbook -i production centos.yml -u root
3. Deploy app
$ ansible-playbook -i production deploy.yml -u deploy
基本的にはこれだけになります!細かいところは先程あげたレポジトリに譲りますが、それほど詰まることなくデプロイまで行けると思います。
中規模Railsアプリのアーキテクチャ設計
Railsでアプリを作っていますか?設計に問題を抱えている、あるいは悩んでいませんか?もしそうであればこの記事が役に立つかもしれません。
アプリケーションが大きくなっていくに連れて、コードがカオスになってきたり、どこで何が起こっているのか追いにくくなってくるものです。いわゆる「ファットコントローラ」「ファットモデル」と呼ばれる問題ですね。ところで、どうしてこの問題が起こるのでしょうか。それはMVCアーキテクチャが常に完璧ではないからです。Railsはアプリケーションの立ち上げに関しては本当に簡単な方法を提供してくれています。素晴らしいです。単にMVCと呼ばれる構造に従ってコードを書いていけばアプリケーションを書けるようになるのです。ただ、前述のように、どこかのタイミングで設計の変更を余儀なくされていきます。もうあなたはそのフェーズにいるかも知れません。どうやって何を変更したら良いのでしょう。
ここでは、ひとつの解決方法として、中規模サイズのRailsアプリケーションにおけるストラテジーを紹介します。ここでの方法はDDD(Domein Driven Design)と呼ばれる手法をベースにしています。記事の最後に本を紹介しているので興味があればぜひ手にとってみてください。
Environment
コントローラ層
コントローラ層はすべてのリクエストを受け付けます。基本的に、この層のみがドメイン層を呼べます。この層は可能な限りシンプルに保たれるべきです。
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つのポイントを紹介します。
- デフォルト値の設定方法
- ラジオボタンを使ったラベル選択方法
- ユーザーが次のフォームに移った時にどうやって自動的に値を設定するか
ちなみに開発環境は下記になります
- "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段階認証を有効にして、
アプリ固有のパスワードを作り、実装にそのパスワードを組み込むのが手っ取り早いと思います。
↑ 左上の「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はよく忘れるのでメモ程度に載せただけです。
もっと良いやり方あるかもだけど。