- 投稿日:2022-02-24T23:47:01+09:00
【Rails】ログイン機能をGem 'devise'で実装
こんにちは。 早く駆け出したい修行僧です。 今回は、Gemの 'devise' を使ったログイン機能を学んだので、 こちらにアウトプットさせていただきます。 尚、「それは違うよ」など訂正箇所がございましたら、 愛のあるご指摘を頂けたら幸いです。 環境 macOS Monterey(M1) 12.2.1 ruby 3.1.0 Rails 6.1.4.6 devise 4.8.1 'devise'とは 特定のページを閲覧したりユーザに制限をかけたりするためにはログイン認証が必要となってきます。ログイン認証とはログインやログアウトの機能を指します。 そして、それらは新規登録をしないと始まりません。そんな機能を簡単に実装してくれる優れたgemが 'devise' です。 インストール rails new でアプリ作成済みであることを前提とします。 まずは導入したいアプリケーションのGemfileに以下のコードを挿入します。 Gemfile gem 'devise' 挿入ができましたら次はターミナルにて以下のコマンドを実行してgemをインストールします。 terminal $ bundle install 次はdeviseを使用するための設定ファイルをdevise専用コマンドで作成する必要があります。 以下のコマンドをターミナルにて実行します。('g'は'generate'の省略です) terminal $ rails g devise:install 上記のコマンドを実行すると、以下のような表示がされたらとりあえず成功です。 terminal =============================================================================== Depending on your application's configuration some manual setup may be required: 1. Ensure you have defined default url options in your environments files. Here is an example of default_url_options appropriate for a development environment in config/environments/development.rb: config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } In production, :host should be set to the actual host of your application. * Required for all applications. * 2. Ensure you have defined root_url to *something* in your config/routes.rb. For example: root to: "home#index" * Not required for API-only Applications * 3. Ensure you have flash messages in app/views/layouts/application.html.erb. For example: <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> * Not required for API-only Applications * 4. You can copy Devise views (for customization) to your app by running: rails g devise:views * Not required * =============================================================================== 何やらたくさんの英文が出てきて一瞬驚かれると思いますが、一つずつ進めていきましょう。 1. 設定するファイルにデフォルトURLの指定 公式にもある通り、デフォルトのURLの指定が必要となります。設定するファイルは以下の引用にも記載されている config/environments/development.rb です。 At this point, a number of instructions will appear in the console. Among these instructions, you'll need to set up the default URL options for the Devise mailer in each environment. Here is a possible configuration for config/environments/development.rb: 引用:公式リファレンス では具体的には何を指定するかは先ほどターミナルで出力された '1' に記載されています。 config/environments/development.rb config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } 2. ルートの設定 続いてルートの設定を行なっていきます。'devise'は新規登録完了後、指定のルートへ飛ぶ設定になっています。ですので、先ほどのメッセージに記載されている config/routes.rb ファイルに設定をしていきます。 config/routes.rb Rails.application.routes.draw do root "homes#index" get "/news", to: "news#news" end *現時点で、まだページを作成されていない方は、 rails g controller コントローラ名 index show などで適当に作っておいても良いと思います。今回、筆者はindexとnewsのページを作成しております。 3. フラッシュメッセージをビューファイルに挿入する フラッシュメッセージとはログインした際に、ヘッダー付近に「ログインしました」のようなメッセージを指します。Progateを既に学習された方は何となく見覚えがあるのではないでしょうか。 では、どこにフラッシュメッセージを挿入するかというと、こちらも先ほどのメッセージに記載されている app/views/layouts/application.html.erb へ挿入をしていきます。 app/views/layouts/application.html.erb <!DOCTYPE html> <% # 省略 %> <body> <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> <%= yield %> </body> 4. deviseのビューを作成する 最後にdeviseのviewを自分でカスタマイズするためには以下のコマンドを実行して作成されたファイルを編集していく必要があります。 terminal $ rails g devise:views 実行した後、以下のような表示がされていれば成功です。 terminal create app/views/devise/shared create app/views/devise/shared/_error_messages.html.erb create app/views/devise/shared/_links.html.erb create app/views/devise/confirmations create app/views/devise/confirmations/new.html.erb create app/views/devise/passwords create app/views/devise/passwords/edit.html.erb create app/views/devise/passwords/new.html.erb create app/views/devise/registrations create app/views/devise/registrations/edit.html.erb create app/views/devise/registrations/new.html.erb create app/views/devise/sessions create app/views/devise/sessions/new.html.erb create app/views/devise/unlocks create app/views/devise/unlocks/new.html.erb create app/views/devise/mailer create app/views/devise/mailer/confirmation_instructions.html.erb create app/views/devise/mailer/email_changed.html.erb create app/views/devise/mailer/password_change.html.erb create app/views/devise/mailer/reset_password_instructions.html.erb create app/views/devise/mailer/unlock_instructions.html.erb 詳しく何のファイルなのか知りたい!っていう方は @cigalecigales さんの 記事 をご覧になっていただけると理解が深まると思います。 モデルの作成 'devise'の設定が終わったら、今度はモデルを作成していきます。通常、モデルを作成する際は rails g model MODEL を実行すると思います。しかし、'devise'を使用する際は、'devise'専用のコマンドを実行してログイン機能に対応したモデルを作成します。 それでは、以下のコマンドを実行しましょう。 terminal $ rails generate devise <任意のモデル名> 実行した後、以下のような表示がされていると思います。 terminal create db/migrate/20220224141911_add_devise_to_users.rb insert app/models/user.rb route devise_for :users そして config/routes.rb を確認してみると、 devise_for :users と自動でルーティングが設定されていることがわかります。 Rails.application.routes.draw do devise_for :users root "homes#index" get "/news", to: "news#news" end モデルが作成できたら、忘れないうちにマイグレーションを実行して、ログイン機能に必要なテーブルを作成しておきましょう。 terminal $ rails db:migrate 最後に たったこれだけの設定でログイン機能が実装できることは非常にありがたいことだと思います。 'devise'にはもっと便利な機能があるみたいなので、もっと触っていき自由に使いこなせるようになりたいです。(追記で更新もしていこうかなとも思っています) 最後までご覧になっていただきありがとうございます。 参考文献 公式レファレンス [Rails] deviseの使い方(rails6版) - @cigalecigalesさん 【Rails】 deviseの使い方とは?ログイン認証機能を実装しよう
- 投稿日:2022-02-24T23:15:00+09:00
[AtCoder 初心者] Rubyで競プロ!!! 標準入力
はじめに AtCoderを始めたので、自分用の整理、メモとして投稿します。 間違えている箇所があれば指摘して下さると幸いです!!!!!!!!!!!! 1つの要素を取得 test.rb A = gets.chomp p A 例: 1を入力 -標準入力- 1 -出力- "1" ・chompメソッドは文字列末尾の改行文字 \n を削除するメソッド。 ・getsメソッドで入力した値は、文字列(Stringクラス)で返される。数値(Integerクラス)として扱いたい場合は、to_iメソッドを使う。 to_iメソッドは 「整数と見なせない文字があればそこまでを変換対象とする」 ため、改行文字 \n は変換対象になる。ゆえに、chompメソッドは不要。 test.rb A = gets.to_i B = gets p A p B 例: Aに1、Bに2を入力 -標準入力- 1 2 -出力- 1 "2\n" 1行の複数の要素を取得 test.rb A = gets.spilt(" ") p A 例: 1 2 3 を入力 -標準入力- 1 2 3 -出力- ["1", "2", "3"] ・splitメソッドは引数に従って、文字列を分割し、分割された文字列を要素とする配列をつくる。今回は空白ごとに分割している。引数を指定しない場合は、デフォルトで空白で分割する。 ・split(" ")のとき(引数を指定しないとき)、先頭と末尾の空白を除いたうえで、空白で分割する。また、入力した文字列末尾の改行文字 \n は空白と見なせるから、ここではchompメソッドは不要。 !!!配列の要素を数値(Integerクラス)にしたいとき!!! 配列(arrayクラス)に、to_iメソッドは使えない!!!この場合はmapメソッドを使う。 -mapメソッド- ブロック内の処理を配列の要素それぞれに行い、その結果を返すメソッドのこと。 test.rb A = gets.spilt.map{|n| n.to_i} p A -標準入力- 1 2 3 -出力- [1, 2, 3] ちなみに、下記のように省略できる。 test.rb A = gets.spilt.map(&:to_i) p A 二次元配列 H行、W列の行列を扱う問題で用いる。 例: 3行3列の行列 test.rb H, W = gets.split.map(&:to_i) A = Array.new(H) {gets.split.map(&:to_i)} p A -標準入力- 3 3 1 2 3 4 5 6 7 8 9 -出力- [[1, 2, 3], [4, 5, 6], [7, 8, 9]] ・A = Array.new(size) で要素の数を指定して、新しい配列Aを作成している。また、{ }内の処理を配列の各要素に行っている。Array.new(size) { } という記法なため、mapメソッドは不要。 ちなみに、下記のようにすると test.rb H, W = gets.split.map(&:to_i) A = Array.new(H) {gets.split.map(&:to_i)} A.each{|row| puts row.join(" ")} -標準入力- 3 3 1 2 3 4 5 6 7 8 9 -出力- 1 2 3 4 5 6 7 8 9 ・joinメソッドは、配列の要素を引数で結合して、1つの文字列をつくるメソッド。今回は配列Aの要素である配列の要素(配列Aの要素[1,2,3]の要素)を空白で結合している。 !!!!個人的には3行目の puts がポイント!!!! p、print、putsメソッドそれぞれの出力後の改行について -pメソッド- 出力後に改行される -printメソッド- 出力後に改行されない -pメソッド- 出力後に改行される test.rb a = 1 p a print a puts a -出力- 1 11 これらのことから、putsであるから綺麗な行列として出力されるといえる。 おわりに 実はQiita初投稿です~てへへ 少しでも誰かのお役に立てれば嬉しいです!! 今後もRubyや競プロについて投稿していこうと思います。
- 投稿日:2022-02-24T22:34:09+09:00
Rspecのallow_any_instance_ofを書き換える
はじめに インスタンスメソッドのスタブを allow_any_instance_of で書いていたのですが、どうやらこれが非推奨らしいので、これを書き換える方法をメモがてら記事にしました。 allow_any_instance_of を使ったテスト テスト対象のファイルはこちら test.rb class Test def test_method hoge = Hoge.new hoge.hello end end class Hoge def hello "hello!" end end そして allow_any_instance_of を使ったスペックファイルがこちら test_spec.rb require_relative 'test' describe 'Test' do context 'test_method' do before do allow_any_instance_of(Hoge).to receive(:hello).and_return('hello!(stub)') end it 'returns hello!(stub)' do expect(Test.new.test_method).to eq('hello!(stub)') end end end これでも一応動くのですが、はじめにも書いたように allow_any_instance_of は非推奨なので、今回はこれを instance_double を使った形に書き換えます。 instance_double を使ったテスト 先ほどのスペックファイルを instance_double を使って書き換える test_spec.rb require_relative 'test' describe 'Test' do context 'test_method' do it 'returns hello!(stub)' do hoge_moc = instance_double(Hoge) allow(Hoge).to receive(:new).and_return(hoge_moc) allow(hoge_moc).to receive(:hello).and_return('hello!(stub)') expect(Test.new.test_method).to eq('hello!(stub)') end end end Hoge で new メソッドを呼ばれた際に、instance_double で作ったモックを呼ぶようにしています。 このテストも書き換え前のテスト同様ちゃんと通ります。 おわりに 今回は書き換え前後をただメモ感覚で書き殴っているだけですが、ちゃんと理解したらまた追記するなり別の場所で書くなりしたいと思います。 書き換えの方法もこればっかりではないと思うので、このあたりについてはまた記事にしたいですね。
- 投稿日:2022-02-24T21:09:28+09:00
【個人開発】面倒くさがり屋のための糖質収支ジャッジアプリ「Sugarjudge」をリリースしました
はじめに みなさんは食事管理が得意ですか? 「痩せたくて食事制限をしようとしても面倒ですぐにやめてしまう」 そんなことありませんか? 私はそんな面倒くさがり屋のために食事管理のハードルを下げたアプリ「Sugarjudge」を作成しました。 サービス概要 アプリURL https://www.sugarjudge.com/ GitHub https://github.com/udakohei/Sugarjudge 痩せたいけど食事を管理、制限することが面倒で難しいという悩みを持った人に、 簡単でざっくりと楽しく糖質を管理、制限できる環境を提供する、 糖質収支ジャッジアプリです。 糖質収支を、一食の摂取糖質量から一食に摂取しても良い上限の糖質量を引いた値として考える。糖質を取りすぎたら赤字、制限内だったら黒字。 続けることは考えず一食一食の管理、制限を目指し、簡単な入力だけでその食事による自身の糖質収支が黒字か赤字かを判定 自己管理は難しいので誰かの目が必要。アプリ内、SNSで食事の写真と判定内容を投稿、共有する なぜ作ったのか 私には「食欲が抑えられず、ついついご飯をおかわりするなど、食べすぎて太ってしまう」という悩みがあります。学生の頃、糖質制限をおこない、半年で10キロ痩せた経験がありますが、その後2年でまた10キロ太ってしまいました。それなのに、面倒くさがり屋なので現在は運動や食事制限などはあまり続かず、習慣にするのにはハードルを感じています。自己管理だけじゃ続かないと考え、「簡単に緩くはじめられて、投稿した食事の写真がアプリ内で自動で共有されることで誰かの目を感じつつ、だけどネタにできるサービス」があったらいいなと思いこのサービスを作りたいと思いました。 機能 一般ユーザー 自身の名前、性別、制限レベルの3つの質問の入力と食事の写真と写真の解析から出した選択肢から選択することで、その食事の糖質収支が黒字か赤字かジャッジされる機能 判定内容をアプリ内で自動共有させる 自分の食事が赤字だった場合に謝罪文を打ち込むことができる 全判定にコメントを打ち込むことができる 他のユーザーの判定内容を閲覧できる 判定内容をアプリ内、Twitterで共有できる ログインユーザー 上記に加え、自身の判定内容、累積収支を振り返られる機能 使用技術 Ruby 3.0.2 Rails 6.0.4.1 jQuery Bootstrap v5.0 Chart.js Google Cloud Vision API Google Translate API Sorcery Heroku AWS S3 苦労した点 画像投稿→食品候補リスト表示までの流れの実装 ユーザーが自分の食べた食事をアップロードしてから、 自分の食べた食品を選択する表を表示させるまでの流れの実装が、難しかったです。 なぜ難しかったか 「外部のGoogleのサービスで画像解析を行っている + 解析結果を食品のデータベース内で検索し、候補の食品のデータを集めて表示させる」 といった、データの動きが見えないところで多方面かつ内容を変更させると言った複雑性をもっていたからです。 逆にいうと、ここの機能の部分がサーバーサイドのメインの実装となったので楽しかったです。 どうやって解決したか 紙に書いて、データがどのように流れるか図に書いて、整理しました。 流れの各順序をどのように実装するか考え、タスクを分解していきました。 meals_controller.rb def create @meal = using_user.meals.build(meal_params) if @meal.save sent_image = File.open(meal_params['meal_image'].tempfile) @meal.update!(analyzed_foods: @meal.image_analysis(sent_image)) redirect_to edit_meal_path(@meal), success: t('.success') まずはコントローラーでアップロードされた画像(画像を属性にもつ食事オブジェクト)が保存された場合、4行目で画像を取得し、5行目のimage_analysisメソッドに渡します。 meal.rb def image_analysis(meal_image) image_annotator = Google::Cloud::Vision.image_annotator translate = Google::Cloud::Translate::V2.new response = image_annotator.label_detection( image: meal_image ) results = [] response.responses.each do |res| res.label_annotations.each do |label| translation = translate.translate label.description.downcase, to: 'ja' results << translation.text end end results.join(',') image_analysisメソッドはモデルのインスタンスメソッドとして定義されております。 meal.rbの2行目と3行目でそれぞれ「Google Cloud Vision API」と「Google Translate API」にメソッドを呼び出しただけでリクエスト通信できるようになるオブジェクトを作りました。これはGoogleの提供するGemを使用することで実現しました。(APIキーなどの設定は別途で記述する必要あり) 次に5~7行目で先ほどのオブジェクトのメソッドを呼び出すことで「Google Cloud Vision API」に通信します。引数の画像であるmeal_imageを飛ばしてその画像に何が写っているかなど複数の情報が言葉でコレクションの形でかえってきます。コントローラーで渡したsent_imageを実際の挙動では解析しています。 そこから下の部分では、まず上の結果をeachメソッドで全ての結果を一つひとつ取り出し、使いたい部分だけ取り出して再度eachメソッドを使います。この最後に絞られた一つひとつを「Google Translate API」の機能で日本語に翻訳します。これも最初に作ったオブジェクトのメソッドを呼び出すだけで実行しています。 その結果を空の配列resultsに入れていき、最後にjoinメソッドで文字列にします。 文字列にする理由はデータベースに保存するためです。なぜならGoogleの解析結果が保存されていないと、後に紹介する食品選択ページに遷移されるたびに同じ写真を毎回APIに飛ばす必要が出てしまうからです。そうなってしまうと外部と通信するためリロード時間が毎回かかったり、APIの使用回数が無駄に上がってしまうからです。 meals_controller.rb def create @meal = using_user.meals.build(meal_params) if @meal.save sent_image = File.open(meal_params['meal_image'].tempfile) @meal.update!(analyzed_foods: @meal.image_analysis(sent_image)) redirect_to edit_meal_path(@meal), success: t('.success') そしてコントローラーに戻り、5行目で結果をanalyzed_foods属性に保存しています。 6行目で食品選択ページに遷移します。 食品選択ページでは食べた食品の候補の表(チェックボックス)を表示させます。 meals_controller.rb def edit @meal = using_user.meals.find(params[:id]) @concrete_foods = Food.searched_foods(@meal) 3行目のsearched_foodsというFoodクラスのクラスメソッドで候補の食品を検索、取得しています。 詳細はモデルに記述しており、 food.rb def self.searched_foods(meal) foods_from_foods = concrete.search_foods(meal.pass_to_sql) foods_from_genres = Genre.search_genres(meal.pass_to_sql).map(&:foods) (foods_from_foods + foods_from_genres).flatten.uniq end 2行目と3行目のpass_to_sqlはMealモデルのインスタンスメソッドであり、先ほど保存した結果の文字列を配列に戻しています。 search_foodsとsearch_genresメソッドは、解析結果の配列の全ての要素をfoodsテーブルとgenresテーブル内で検索するスコープです。 food.rb scope :search_foods, lambda { |analyzed_foods| where('name LIKE ? OR name LIKE ? OR name LIKE ? OR name LIKE ? OR name LIKE ? OR name LIKE ? OR name LIKE ? OR name LIKE ? OR name LIKE ? OR name LIKE ?', "%#{analyzed_foods[0]}%", "%#{analyzed_foods[1]}%", "%#{analyzed_foods[2]}%", "%#{analyzed_foods[3]}%", "%#{analyzed_foods[4]}%", "%#{analyzed_foods[5]}%", "%#{analyzed_foods[6]}%", "%#{analyzed_foods[7]}%", "%#{analyzed_foods[8]}%", "%#{analyzed_foods[9]}%") } こうすることで、解析結果をもとに当てはまる食品を検索、取得することができます。 genresテーブルでも検索しているのは、解析の精度がジャンル名で返ってくることが多いためです。例えば、ラーメンとは返ってこないけど、麺とは返ってくるようなイメージです。 取得できたらビューで表にして表示させて、終わりです。 edit.html.erb <table class="table table-hover table-bordered"> <thead> <tr> <th><%= Food.human_attribute_name(:name) %></th> <th>選択する/しない</th> </tr> </thead> <tbody> <%= f.collection_check_boxes :food_ids, @concrete_foods, :id, :name do |food| %> <%= food.label do %> <tr class="js-checkbox-<%= food.object.role %>"> <td><%= food.object.name %></td> <td><%= food.check_box class: 'js-chk' %></td> </tr> <% end %> <% end %> </tbody> </table> 表は実際の食品を選べるように、チェックボックスの表になるようtableタグとcollection_check_boxesメソッドを組み合わせました。 以上で無事、画像投稿→食品候補リスト表示までの流れの実装を終わりました。 工夫したところ ジャッジをしたら自動でアプリ内に共有されるところ Chart.jsを使ってグラフで黒字、赤字の大小がわかりやすくなるよう実装 * 画像解析の精度をカバーするため候補の表に食品がないとき、抽象的な食品を選べるように実装 赤字の(糖質を取りすぎた)場合謝罪文を投稿できる機能。ネタにする意味をこめました。 終わりに 糖質収支ジャッジアプリ、使ってくださると嬉しいです! また、何かご意見、ご感想がございましたら、こちらのDMでくださると嬉しいです! https://twitter.com/udaudakohei/ ぜひ、皆さんのジャッジお待ちしております。
- 投稿日:2022-02-24T15:08:10+09:00
作ったシェアリングプラットフォームが2年目にしてGMV580%成長するまでにやったこと。
こんにちは!HANOWAの新井です。 歯科医療人材のシェアリングプラットフォームやってます。 12月末で3期目を終え、ローンチ2年目の2021年度は年初来GMV(流通人件費)6.8倍、580%の成長で終えることが出来ました。 (年が明けてもグイグイ伸びてます) スタートアップの成長率は、ローンチ初月からの比較でたまに「1,200%成長!」とか見かけるのですが、上の図のようにローンチ1年目は無視した数字です。 ここでは特に、マーケットプレイス型のプロダクトで起業しようとするエンジニアさんの参考になればと思い、書いていきます。 前提 プロダクトや開発環境について [フロントエンド] ・言語:Nuxt.js/Vue.js ・OS:Linux ・DB:FirebaseのFireStore ・デプロイ先:Firebase [バックエンド] ・言語:Ruby on Rails,AWS ・OS:Linux ・DB:PostgreSQL(AWSのRDS) ・デプロイ先:AWSのELB [その他] タスク管理:JIRA コード・バージョン管理:Github 環境構築:Docker その他ツール:Slack 市場について 歯科医療業界 ユーザーについて サプライ:歯科医療従事者(主に歯科衛生士。2022年2月以降は複数職種展開) デマンド:歯科医院経営者 (例えると、歯科業界版タ◯ミー) 外部資金調達について 初回:ガゼルキャピタル lead 2回目:ANOBAKA lead (ぁぁ...テッククランチ、行かないで...) ローンチ1~6ヶ月目 Biz:2名 エンジニア:業務委託1名 そもそもこの記事は、一度noteで書いたこちらの エンジニアさんに向けた焼き直し版です。 なんでリメイクしようと思ったかというと、うちを今手伝ってくれてるISSUEの寒河江さんとランチで話していた時、 「初期の顧客ってどうやって集めたんですか?」 とやたらと聞かれました。 経営において作ることと、売ることは両輪ですよね。 それにしてもやたら聞くのでエンジニアさん的には初期のプロダクトのトラクションを生む活動や、GoToMarket的な話はそんなに興味深いものなんだなと思った次第です。 そこ行くと僕の場合は答えの一つは、スタートアップを始める前に結構ちゃんと受託やコンサルをやってたことが大きいと思います。歯科医院の採用コンサルとして、求人広告の作成代行をしこしことやってました。本当に初期の歯科医院さんはかつての僕の支援先や、無料で使えるプレスリリースツールでLPにリンクを貼って、事前予約を募っていました。 受託やコンサルをしっかりやってたのも、スタートアップを始めるに際して大きいのですが、もう一つはしっかりペインを捉えることだと思います。 うちの場合、痛みっつうのはこんな感じでしょうか。 20件の歯医者が、まだ新卒の何の訓練もされていない新卒歯科衛生士1人を奪い合う為に、血みどろの戦いを繰り広げるのです。猛烈な痛みです。なんとなくフワッとプロダクト作る前に、強烈な痛みを先に探すのがいいと思います。 ↑とか、なんだか言ってますが、最初の半年はパッとしませんでした。 とにかく... プロダクトのバケツの穴がデカ過ぎ ました。 (初期のUI。この頃はUIUXなんて言葉も知らず、馴染みのwebデザイナーさんに自分で下書き送ってワイヤー書いてもらって作った。) 後に明確にターニングポイントになった施策を先に書くと、 『ユーザーの半径15km圏内でカレンダー更新があったら、一斉通知メールを送る』 という機能でした。 2020年を通して、上がったり下がったり、 再現性を維持することが出来なかったのは、 物理的距離の概念を舐め過ぎてたのが大きな理由です。 上記の 『半径15km圏内でカレンダー更新があったら、一斉通知メールを送る』 施策は、マッチングした人同士のやり取りや、メッセを送り合った人同士のやり取りではなく、まだ接点を持たない人同士のやり取りの増加に寄与しました。 こうやってマッチングやメッセ交換の手前のやり取りをする母集団が増加することで、メッセージの件数増加、マッチングの件数増加に繋がっていきました。 あと、2021年の結構なタイミングまで、僕自身もシェアリングプラットフォームにおける「プロダクト」とは、エンジニアが書いたコードの集合体を表すものだと思っていました。 プロダクトがあって、ユーザーがある。 プロダクトとユーザーは別の概念のものだと思っていました。 これはシェアリングプラットフォームでは間違いで、ユーザーがプロダクトの中に包含されるのが、シェアリングプラットフォームの実態です。  ユーザーさえもプロダクトの一部です。 それに気づいた時、これまで展開しているエリアは「東京都23区と大阪市内」と言っていたことを大きく反省します。 絞ってるようでいて何も絞っていませんでした。 なんなら、名古屋とか、札幌とか、一部の噂を聞きつけた、イノベーターやアーリーアダプター院長の為に、リソースを割いて人材の集客もやってたんですよね。 今振り返ると、プロダクトのバケツの穴が塞がらなかった理由は ①matchable(match+able)な地理関係でユーザーを集め切れていない ②仮に集まってても、近隣のフレッシュな情報が主体的にログインしなければ分からない こんな状態で、 「なんでGMV(流通人件費)増えていかないんだろうねー?」 と毎週ボードメンバーで集まって うんうんうんうん言う日々でしたね。 (アホです) ローンチ7~12ヶ月目 Biz:2名 エンジニア:業務委託1名+正社員1名(2ヶ月で戦線離脱) クリエイター:業務委託2名 そうとは知らずに、GMVを増やす本質的なメトリクスが見えず、とりあえず一番分かりやすい 「登録者数」 を増加させるしかねぇ。 と言うことでよく分からんことをやり始めます。 ボードの2人で、東京と大阪の歯科系メーカーとディーラーに飛び込み訪問をして、チラシを配って、ルートセールスに行く際に 「配ってください!オネシャス!」 って結構本気でやってました。 ジョイントベンチャーですね。 それやって貰えるとHANOWAはありがたいけど、向こうにやるインセンティブもない。 レベニューシェアと言っても、1マッチングあたり日当の33%の手数料なので、せいぜい3,000円程度しか儲かりません。 仮に 「1件新規登録してくれたら5万円謝礼金をお支払いします」 なんて言ったところで、端た金過ぎて動く動機にもならない。 また僕とかはクソ暑い夏の中、マスクしながらチャリンコで夜中にポスティングをこのタイミングでやるという暴挙に出ます。なんかやってる自分に酔ってましたね。 何にもなりませんでした。 また英会話マッチングのフラミンゴと言うサービスを見てて 「うちに足りないのは、シズル感かも知れない!」 と思い、サインアップ&ログインして初めて中が覗ける仕組みだったUX(閉鎖系)を、サインアップしてなくてもこんな人たちがいることをオープンに(開放系)解放してみようと夏頃開発しました。  (社内では「フラミンゴ化」と呼ばれてた) エンジニアは頑張ってくれたけど、そんなに大した成果には繋がりませんでした。 値付けも迷走してて、一番初めは初期登録料5万円を徴収していました。でもコロナがヤバくなってきた2020年4月から初期登録料を10万円に値上げして、早々に夏前には5万円にしれっと戻しました。 コロナもあるけど、露骨に登録件数が落ちましたね。 (そもそもやろうとした動機が、ちょっとでも売上が欲しかったし、みんな勢いよくポンポン5万円を払うからもっとイケんじゃね?と思った) 10月から初期登録料5万円を廃止して、なんとかして月額課金モデルに転換したかったので掲載料月額5,000円のスタンダードプランと、月額30,000円のプレミアムプランを作りました。 で、アホほど当たり前のことですが、登録しても近隣にユーザーがいなければ秒速で退会します。 何しにわざわざエンジニア工数稼働して貰って作ったんでしょうね? 秋には本当にお金ないぞとなって、結構本気で歯科医院向けのBPOサービスを立ち上げる準備もしたけど、やっぱなんとかしてエクイティ調達するべ!と、ペンディングになりました。 もっとくだらないこと一杯やった気がするのですが、人の脳は黒歴史を忘れたがる性質があるんだと思います。 あとこの年はずっと「SaaSはいいよな〜」と思い続けてた記憶があります。 バリュエーション付きやすそうに見えていたし、追いかけるべきメトリクスが明確で羨ましかったなぁ。 ローンチ13~18ヶ月目 Biz:3名 エンジニア:業務委託3名 クリエイター:業務委託2名 UXデザイナー:業務委託1名 1月。 ここで何を思ったか、軽い気持ちで 「お互い、近隣のユーザーがカレンダー登録したら知りたいよね?」 「更新あった時、通知メール送った方が良くない?」 ぐらいの感じで実装したのが、先述の 『ユーザーの半径15km圏内でカレンダー更新があったら、一斉通知メールを送る』 です。 これを機に、見る見る双方のメッセージのやりとりが増え、それに伴い時間差でマッチング件数にも反映されて来ました。 「なんかこれ当たってんじゃね?」 って感じです。 カレコレもう4,000文字くらい1年前の自分がどれだけアホなんかを書いてますが、初回起業家と、シリアル起業家が歴然として違うのはこーゆーことかと思ったりもします。 芽が出始めたタイミングで、なんとか死ぬ気で投資家を集めて、春にはなんとか調達を完了できました。 あとこの時期の最後の6月頃に、1人業務委託のUXデザイナーに入ってもらい、さらに細々としたプロダクトのバケツの穴を塞いで行って貰ったのも良かったです。 僕がPMをやりながらファイナンスとマーケと、あと投資契約を10何人分自力でやるというなんの修行か分からん時期の終わりが見えたタイミングでした。 ローンチ19月目~ Biz:3名 エンジニア:業務委託3名+ISSUE クリエイター:業務委託2名 UXUIデザイナー:業務委託2名 財務担当:1名 調達後またゆるく広告費をかけはじめて、6月から7月にかけて単月で24%成長したのは、もう少し自信を持っていいのかなと思い、将来のCFO候補を採用します。 このタイミングで入って貰ったのが僕のコンサル時代からの友人である、財務コンサルタントKです。 Kと共に今年を「n-4」と見立てて、ゆるく上場準備を始めていければと思っていたものの、彼が放った 「今の売上を”区”単位で因数分解しよう」 と言う一言はHANOWAにさらなる加速をもたらします。 “区”単位で過去のマッチング傾向を分析し、都内東部の足立区、葛飾区、江東区に偏りを見つけ、東部の6区を戦略的な優先順位第一位と定め、そこにFAXDMと郵送DMのリソースの集中を8月から始めました。 そこからこんな感じ↑で、全国の6万8,000件の地域ごとの歯科医院の母数を、 厚労省の資料から見つけ出し、優先順位をエリア毎に4位まで定め、上場後の「n+1」までのカバレッジシートを作成しました。 今後も微調整はするでしょうが、これに従ってドミナントで一時期にLTVやCPAはある程度無視して広告予算をぶっ込み、一定レベルでアクティベートすれば、また次の地域に… これを繰り返すのが自分たちの必勝パターンだと見えて来ました。 これによって2021年の12月はなんと単月で40%以上の成長を。 2022年2月も現在単月で25%以上の成長を続けています。 タイトルの580%成長とは、2021年1月から2021年12月までのこと伸び率です。 2021年1月から直近の2022年2月なら、もう1,000%くらいには伸びてます。 結び エンジニアさんが自分でプロダクトを作って、スタートアップを始めたいと思った時、 1番の注意点は安易に作れてしまうことだと思います。 作れることはもちろん武器なのですが、ついついユーザーの痛みや欲求を通り越して 「無かったから作った」 「面白そうだったから作った」 という方によくご相談を頂きます。 それよりも、市場の痛みが明確で強ければ、多少使い勝手は悪くてもユーザーは我慢してくださるものかと思います。 また、 『ユーザーの半径15km圏内でカレンダー更新があったら、一斉通知メールを送る』 なんて、技術的には多分そこまで困難なものでは無いと思います。 ただ、そこに至るまでの紆余曲折や、思考と実行の軌跡から、 何か開発者さんがプロダクトを立ち上げる上で学べるものがあればと思い、 今回の記事をシェアさせてもらいました。 ご笑覧頂けましたら幸いです。 HANOWA あらい 追伸:人材募集中!! そんなHANOWAでは、現在事業の急激な成長に伴い、エンジニアを大募集しています。 #フルリモート #出社無し #信託型SO プロダクトや開発環境について [フロントエンド] ・言語:Nuxt.js/Vue.js ・OS:Linux ・DB:FirebaseのFireStore ・デプロイ先:Firebase [バックエンド] ・言語:Ruby on Rails,AWS ・OS:Linux ・DB:PostgreSQL(AWSのRDS) ・デプロイ先:AWSのELB [その他] タスク管理:JIRA コード・バージョン管理:Github 環境構築:Docker その他ツール:Slack ご興味お持ち頂けましたらDMお待ちしておりますm(_ _ )m ご覧頂き有難うございました!
- 投稿日:2022-02-24T11:38:14+09:00
【Ruby On Rails】radio_button:数値として扱いparamsを使って合計点数など計算する
環境 Mac(12.2.1) MacBook Pro (13-inch, 2020) 2 GHz クアッドコアIntel Core i5 16 GB 3733 MHz LPDDR4X ruby (3.0.0p0) rails (7.0.1) mysql2 (0.5.3) 実現したい内容 上記の質問内容に対してラジオボタンを押したときに はい=>2点 いいえ=>0点 どちらでもない=>1点 と、それぞれのラジオボタンを点数として取り扱う 更に、それらの点数を合計し、結果をDBに保存する 使用するメソッド ・ [form_with] ・ [radio_button] ・ [label] ・ [submit] ・ [params] viewには何を書く? viewではform_withを使用し、フォームを作成する form_withのネスト(入れ子)に「radio_button」、「label」を使用 ⇨「はい」、「いいえ」、「どちらでもないの」フォームの内容とそれらにあてはめたい点数(数値など)を入力 「submit」を使用し、DBに保存 controllerには何を書く? controllerではviewの入力情報を受け取り、その入力情報をもとに計算を行う 受け取りは「params」を使用 view名に該当するアクションを作成し、アクション内でparamsで受け取った情報を元に計算とDB保存を行う とある質問フォームを作った時の例 Sampleテーブル id result 1 4 2 2 viewの設定 app/views/sample/test.html.erb <%=form_with modele: @test,url: test_path, local: true,method: :post do |f|%> <li>ここに質問の文言を記述</li> <!--ラジオボタンの記述方法 f.radio_button :オブジェクト名(保存したいカラム名など) ,:ここに点数(数値)を設定--> #設問1つ目 <%= f.radio_button :total_1 , 2, checked: true %> <!--ラベルの記述方法:f.label :ラジオボタンで書いたオブジェクト名(保存したいカラム名など) ,"画面上で表示したいタイトル",value:ラジオボタンで書いた値 --> <%=f.label :total_1 ,"はい",value: 2 %> <%=f.radio_button :total_1 ,0%> <%=f.label :total_1 ,"いいえ",value: 0 %> <%=f.radio_button :total_1 ,1%> <%=f.label :total_1 ,"どちらでもない",value: 1 %> #設問2つ目 <%=f.radio_button :total_2 , 2, checked: true%> <%=f.label :total_2 ,"はい",value: 2%> <%=f.radio_button :total_2 ,0%> <%=f.label :total_2 ,"いいえ",value: 0%> <%=f.radio_button :total_2 ,1%> <%=f.label :total_2 ,"どちらでもない",value: 1%> <%=f.submit%> controllerの設定 controller側でラジオボタンの入力情報をparamsで受け取り(今回の場合はradio_buttonのvalueで設定した値)、計算する app/controllers/sample_controller.rb class SampleController < ApplicationController def create @total=params[:total_1].to_i + params[:total_2].to_i @total_result = Sample.create(result: @total) end ・ ・ ・ web画面上でsubmit(送信ボタン)を押すと、form_withのmethod: :postでcontrollerのcreateアクションが実行 結果 @tatalでフォーム内容をparamsで計算 @total_lesultでSampleテーブルのresultカラムに@totalの内容を保存 おまけ:web画面に結果を出したい場合 web画面に結果を出したい場合、 viewの記述の追加 viewのform_withのmethod: :postからmethod::getに変更 controllerの記述の追加 app/views/sample/test.html.erb <%=form_with modele: @test,url: test_path, local: true,method: :get do |f|%> <li><%= p @toatl%></li> app/controllers/sample_controller.rb class SampleController < ApplicationController def test @total=params[:total_1].to_i + params[:total_2].to_i end ・ ・ ・ おわりに 初学者で試し試し作っているので、間違っているところがあったらごめんなさい 参考 投稿データを保存しよう 【Rails】入門説明書 paramsについて解説 【Rails】form_withの使い方を徹底解説! 【Rails】form_with/form_forについて【入門】 【Rails】paramsがなんなのかやっとわかった! 【Rails】ラジオボタンを表示する方法 【JavaScript】ラジオボタンを利用してレコード内集計する 【JavaScript】ラジオボタンの診断テストで合計点数に応じてコメントを変える方法 【Rails】テーブルにデータを保存しよう 【Rails】 createメソッドの使い方とは?new・saveメソッドとの違い
- 投稿日:2022-02-24T11:38:14+09:00
【Ruby On Rails】radio_buttonを数値として扱いparamsを使って合計点数など計算する
環境 Mac(12.2.1) MacBook Pro (13-inch, 2020) 2 GHz クアッドコアIntel Core i5 16 GB 3733 MHz LPDDR4X ruby (3.0.0p0) rails (7.0.1) mysql2 (0.5.3) 実現したい内容 上記の質問内容に対してラジオボタンを押したときに はい=>2点 いいえ=>0点 どちらでもない=>1点 と、それぞれのラジオボタンを点数として取り扱う 更に、それらの点数を合計し、結果をDBに保存する 主に使用したメソッド ・ [form_with] ・ [radio_button] ・ [label] ・ [submit] ・ [params] viewには何を書く? viewではform_withを使用し、フォームを作成する form_withのネスト(入れ子)に「radio_button」、「label」を使用 ⇨「はい」、「いいえ」、「どちらでもないの」など、フォームに表示したい内容とそれらの値(今回は点数)を記述 「submit」を使用し、DBに保存 controllerには何を書く? controllerではviewの入力情報を受け取り、その入力情報をもとに計算を行う 受け取りは「params」を使用 view名(今回はapp/views/sample/test.html.erb)と同名のアクション(今回はtestアクション)を作成し、そのアクション内でparamsで受け取った情報を受け取り、合計計算とDBへの保存を行う とある質問フォームを作った時の例 Sampleテーブル id result 1 4 2 2 viewの設定 ラジオボタンの記述方法 f.radio_button :「オブジェクト名(保存したいカラム名など)」,:「ここに点数(数値)を設定」 ラベルの記述方法 f.label :「ラジオボタンで書いたオブジェクト名(保存したいカラム名など) 」, "「画面上で表示したいタイトル」",value:「ラジオボタンで書いた値」 app/views/sample/test.html.erb <%= form_with modele: @test,url: test_path, local: true,method: :post do |f| %> <li>ここに質問の文言を記述</li> #設問1つ目 <%= f.radio_button :total_1 , 2, checked: true %> <%= f.label :total_1 ,"はい",value: 2 %> <%= f.radio_button :total_1 ,0 %> <%= f.label :total_1 ,"いいえ",value: 0 %> <%= f.radio_button :total_1 ,1%> <%= f.label :total_1 ,"どちらでもない",value: 1 %> #設問2つ目 <%= f.radio_button :total_2 , 2, checked: true%> <%= f.label :total_2 ,"はい",value: 2%> <%= f.radio_button :total_2 ,0%> <%= f.label :total_2 ,"いいえ",value: 0%> <%= f.radio_button :total_2 ,1%> <%= f.label :total_2 ,"どちらでもない",value: 1%> <%= f.submit%> controllerの設定 controller側でラジオボタンの入力情報をparamsで受け取り(今回の場合はradio_buttonのvalueで設定した値)、計算する app/controllers/sample_controller.rb class SampleController < ApplicationController def create @total=params[:total_1].to_i + params[:total_2].to_i @total_result = Sample.create(result: @total) end ・ ・ ・ web画面上でsubmit(送信ボタン)を押すと、form_withのmethod: :postでcontrollerのcreateアクションが実行 結果 @tatalでフォーム内容をparamsで計算 @total_lesultでSampleテーブルのresultカラムに@totalの内容を保存 おまけ:web画面に結果を出したい場合 web画面に結果を出したい場合、 viewの追記 viewのform_withのmethod: :postをmethod: :getに変更 controllerの追記 app/views/sample/test.html.erb <!-- "viewのform_withのmethod: :postをmethod: :getに変更"--> <%=form_with modele: @test,url: test_path, local: true,method: :get do |f|%> ・ ・ ・ <!--viewの追記--> <li><%= p @toatl %></li> app/controllers/sample_controller.rb #controllerの追記 class SampleController < ApplicationController def test @total=params[:total_1].to_i + params[:total_2].to_i end ・ ・ ・ おわりに 初学者で試し試し作っているので、間違っているところがあったらごめんなさい 参考 【Rails】createメソッドの使い方とは?new・saveメソッドとの違い 【Rails】入門説明書 paramsについて解説 【Rails】paramsがなんなのかやっとわかった! 【Rails】form_withの使い方を徹底解説! 【Rails】form_with/form_forについて【入門】 【Rails】ラジオボタンを表示する方法 【Rails】テーブルにデータを保存しよう 【Rails】投稿データを保存しよう 【JavaScript】ラジオボタンを利用してレコード内集計する 【JavaScript】ラジオボタンの診断テストで合計点数に応じてコメントを変える方法
- 投稿日:2022-02-24T11:11:31+09:00
「Herokuの起動が重すぎる」を解決する方法
#はじめに 現在オリジナルアプリで朝散歩習慣化アプリを作っています。 Herokuにデプロイしているのですが、立ち上がりがとにかく遅くて使い勝手が悪い。 今回はHerokuの立ち上がりを早くする方法について学んだのでまとめます。 #なぜ起動が遅くなるのか Herokuの立ち上がりが遅い原因は、「30分アクセスがないとスリープモードになる」という仕様があるためらしいです。 そのため、スリープにしないために設定を変える必要があります。 #Herokuにスケジューラを追加 まずはターミナルで下記のコマンドを打ちます。 % heroku addons:create scheduler:standard すると以下のような表示が出ます。 Creating scheduler:standard on ⬢ morning-walk... free To manage scheduled jobs run: heroku addons:open scheduler Created scheduler-flexible-28737 Use heroku addons:docs scheduler to view documentation #ジョブを作成する 続いてターミナルに以下のコマンドを打ちます。 % heroku addons:open scheduler すると以下のような画面が出ます。 「Create job」ボタンを押すと、次に以下のような画面が出ます。 この入力欄に ・Every 10 minutes ・curl https://morning-walk.herokuapp.com/ (curlの後は、自分のアプリのURLを記載してください) ・free と入力すると完了です。 ここまでで、 設定したHerokuアプリを10分毎に起動させる処理にできました。 #まとめ Herokuでアプリを作ると、とにかく起動が重いですが、 このような方法があると知れてよかったです。 今後は、Herokuではない別の環境でアプリを実行させたいと思います。 (これから情報収集するのですが、Heroku以外でおすすめの環境があれば教えていただけると嬉しいです?) 参考文献↓ https://yukitoku-sw.hatenablog.com/entry/2020/02/04/225151
- 投稿日:2022-02-24T10:47:32+09:00
[py2rb] 多重継承の5
はじめに 移植やってます。 ( from python 3.7 to ruby 2.7 ) 多重継承 (Python) 前回の記事の コメント にて、@Nabetani さんよりメソッドのまとめ方を教えていただきましたので、それを踏まえて再びやってみました。 一番目の行は、ファイル名(Rubyですとmodule名) C:class, SC:継承するsuperclass D:メソッド定義, DS:メソッド内でsuperを使用しているメソッド定義 継承テスト(Python) class C5: s1 = None s3 = None s5 = 's5' def __init__(self): print('C5') class C4(C5): s4 = 's4' def __init__(self): print('C4') class C2(C4): s2 = 's2' def __init__(self): print('C2') super().__init__() class C3: s3 = 's3' def __init__(self): print('C3') class C1(C2, C3): s1 = 's1' def __init__(self): print('C1') super().__init__() print(self.s1) print(self.s2) print(self.s3) print(self.s4) print(self.s5) C1() # output C1 C2 C4 s1 s2 None s4 s5 ふむふむ。 クラス変数も受け継がれてますね。 継承テスト(Ruby) module C5 @@s1 = 'None' @@s3 = 'None' @@s5 = 's5' def __init__ puts 'C5' end end module C4 include C5 @@s4 = 's4' def __init__ puts 'C4' end end module C2 include C4 @@s2 = 's2' def __init__ puts 'C2' super end end module C3 @@s3 = 's3' def __init__ puts 'C3' end end class C1 include C2, C3 @@s1 = 's1' def initialize __init__ end def __init__ puts 'C1' super puts @@s1 puts @@s2 puts @@s3 puts @@s4 puts @@s5 end end c = C1.new p C1.ancestors # output C1 C2 C4 s1 s2 s3 s4 s5 [C1, C2, C4, C5, C3, Object, Kernel, BasicObject] s3が惜しかったですが、継承チェーンは正しいようです。 メモ Python の 多重継承の5 を学習した 百里を行く者は九十里を半ばとす