ラベル Ruby on Rails の投稿を表示しています。 すべての投稿を表示
ラベル Ruby on Rails の投稿を表示しています。 すべての投稿を表示

2020年12月12日土曜日

RubyOnRails: 入れ子のパラメータをストロングパラメータする。

purchase(購入)に複数のitem(=items 商品)がネストしているパラメータを受け取って、 permitして返すメソッドを作る場合、
purchaseは普通にpermitすればいいが、itemsをどうpermitするか。

(メソッドの呼び出し元では permited_parameter.items.first.product_name のように、 itemの属性にもメソッドチェーンしてアクセスしたい。)

以下な感じでする。
def permited_parameters
  params.require(:purchase).map do |purchase|
    visit.permit(
      :customer_name,
      :customer_address
    ).merge(
      items: visit[:items].map do |item|
        item.permit(
          :product_name,
          :count,
          :price
        )
      end
    )
  end
end

2020年9月19日土曜日

VueのページからVueのページに遷移するときにコンポーネントが更新されない。

 Rails+Vueで開発中に、 Vueの入力フォームを日付を変えて遷移するリンクを押しても、 Vueのデータが更新されない。。 turbolinksの影響のようだ。。 turbolinksとVueの共存方法はググると出ていたが、 https://qiita.com/midnightSuyama/items/efc5441a577f3d3abe74 うまくいかない。。 こんなにコード書かなきゃいけないのか。。

 今回はVueへの遷移時だけturbolinksをoffにすることで対応。
<a class="navi-item nav-link " data-turbolinks="false" href="/hoge">Vueのページへ</a>

Vueが使われているページが多い場合はだめだな。

2020年8月28日金曜日

ActiveRecordでjoinして同じカラム名の値があると後勝ちになる。

やっちゃいがちなはまりポイント。
irb(main):033:0> Visit.all.joins(:items).select('visits.id, items.id').first.attributes
  Visit Load (2.0ms)  SELECT  visits.id, items.id FROM `visits` INNER JOIN `items` ON `items`.`visit_id` = `visits`.`id` ORDER BY `visits`.`id` ASC LIMIT 1
=> {"id"=>2}
irb(main):032:0> Visit.all.joins(:items).select('visits.id as visit_id, items.id as item_id').first.attributes
  Visit Load (2.6ms)  SELECT  visits.id as visit_id, items.id as item_id FROM `visits` INNER JOIN `items` ON `items`.`visit_id` = `visits`.`id` ORDER BY `visits`.`id` ASC LIMIT 1
=> {"id"=>nil, "visit_id"=>101, "item_id"=>2}
visits, itemsともにid があるが、 結果から .id として取得するとitemsのidになる。 SQLだと
  > select id from visits join items on visits.id = items.visit_id where visits.id = 101 \G;
ERROR 1052 (23000): Column 'id' in field list is ambiguous
となってそもそも実行できないがActiveRecordだとエラーにはならない。 ActiveRecordでselect句を使うのは結構注意。.* で指定するのやめた方がよさそう。

2015年5月21日木曜日

【Ruby On Rails】Stringなど既存のクラスにメソッドを追加する方法

いつも忘れるのでメモ。

このアプリケーションだけで頻繁に使う処理をStringなど既存のrubyクラスに足したいときがある。
たとえばStringに 文字列を大文字・小文字が相互に続くように変換する dekoboko というメソッドを足すとする。

core_ext/string.rb というファイルを作ってそこでStringクラスを追記する。

X
# coding:utf-8

class String

def dekoboko
ret = ""
self.length.times do | idx |
if idx % 2 == 0
ret += self[ idx ].upcase
else
ret += self[ idx ].downcase
end
end
ret
end

end



application.rbでstring.rbをloadすればStringクラスを拡張できる。


module MyApp
class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. # Custom directories with classes and modules you want to be autoloadable. # config.autoload_paths += %W(#{config.root}/extras ) # 基本クラスの拡張を読み込む Dir[ config.root.join( "core_ext/*.rb" ) ].each do | f | load f end


コンソールから実行すると、
irb(main):001:0> "abcdefg".dekoboko
=> "AbCdEfG"

メソッドが追加できている!

2015年5月18日月曜日

【RubyOnRails】Parallelを使って並列処理をしているときにraiseした場合の動作

単純処理を繰り返し行う場合、高速化のためにParallelで並列処理する。
ふと思ったんだけど、処理を並列で進めているときに1つがraiseしたらそのほかの処理はどうなるんだろう。もちろんほかの処理も打ち切られてほしいんだけど、まさかraiseした以外のスレッドの処理は継続ってことはないよなぁ。。ということで確認してみた。

以下のサンプルプログラムで確認する。


# coding:utf-8

ary = [ *1..100 ]

Parallel.each( ary, :in_threads => 4 ) do | num |

p "#{num}: pid=#{Process.pid}"
raise if num == 50

end



さて、結果を予想すると、

50が割り当てられたthreadがraiseするが、その他のthreadも処理を打ち切るので50以降はプリントされないはず。

で実行してみると。。


$ bin/rails r script/multi_thread.rb

(省略)
"36: pid=28589"
"37: pid=28589"
"38: pid=28589"
"39: pid=28589"
"40: pid=28589"
"41: pid=28589"
"42: pid=28589"
"43: pid=28589"
"44: pid=28589"
"45: pid=28589"
"46: pid=28589"
"47: pid=28589"
"48: pid=28589"
"49: pid=28589"
"50: pid=28589"
script/multi_thread.rb:12:in `block in ': unhandled exception
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.3.3/lib/parallel.rb:430:in `call'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.3.3/lib/parallel.rb:430:in `call_with_index'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.3.3/lib/parallel.rb:240:in `block (3 levels) in work_in_threads'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.3.3/lib/parallel.rb:439:in `with_instrumentation'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.3.3/lib/parallel.rb:239:in `block (2 levels) in work_in_threads'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.3.3/lib/parallel.rb:233:in `loop'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.3.3/lib/parallel.rb:233:in `block in work_in_threads'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.3.3/lib/parallel.rb:136:in `block (2 levels) in in_threads'

予想通り50で止まった。マルチスレッドは並列処理といってもひとつのプロセス内で処理を分けているのでraiseすればプロセス自体が打ち切られ該当スレッド以外も止まる。
(プリントされているPIDもすべて同じ。)

ではマルチプロセスならどうなるだろう。


multi_thread.rbのin_threadsをin_processesに変えてプログラムは出来上がり。

$ bin/rails r script/multi_process.rb

(省略)
"41: pid=26378"
"44: pid=26381"
"43: pid=26375"
"45: pid=26378"
"46: pid=26375"
"47: pid=26378"
"48: pid=26375"
"50: pid=26378"
"49: pid=26381"
"52: pid=26381"
"53: pid=26381"
"54: pid=26381"
"55: pid=26381"
"56: pid=26381"
"57: pid=26381"
"58: pid=26381"
"59: pid=26381"
"51: pid=26375"
script/multi_thread.rb:8:in `block in ': unhandled exception
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.4.1/lib/parallel.rb:438:in `call'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.4.1/lib/parallel.rb:438:in `call_with_index'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.4.1/lib/parallel.rb:342:in `process_incoming_jobs'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.4.1/lib/parallel.rb:324:in `block in worker'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.4.1/lib/parallel.rb:317:in `fork'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.4.1/lib/parallel.rb:317:in `worker'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.4.1/lib/parallel.rb:305:in `block in create_workers'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.4.1/lib/parallel.rb:304:in `each'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.4.1/lib/parallel.rb:304:in `create_workers'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.4.1/lib/parallel.rb:263:in `work_in_processes'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.4.1/lib/parallel.rb:193:in `map'
        from /goy/rails_app/vendor/bundle/ruby/1.9.1/gems/parallel-1.4.1/lib/parallel.rb:154:in `each'

止まったがぴったり50ではない。プリントされる順も昇順ではなく若干ばらばら(PIDもみんな一緒ではない)。50を引き当てたプロセスがraiseしてもその他のプロセスはすぐには止まらない模様。raiseしたプロセスが親プロセスに伝え、親プロセスが各子プロセスを止めて。。。みたいになると思うのでタイムラグがあると思う。出力される順番もばらつくので順番どおりに処理されないといけないという時はそもそもマルチプロセスだとだめっぽい。


どちらにしても、並列処理でraiseしたときに、raiseした以外のプロセス・スレッドも処理を停止する(マルチプロセスの場合タイムラグあり)ということはわかった。

2015年1月18日日曜日

【RoR】オブジェクトがnewされたものか、DBからselectされたものか(保存済みのものか)チェックする方法。

初回保存のときと、それ以外とかで処理を変えたいことはよくある。原理的にはidでDBをselectして該当があるか見る っということなんだけど、Railsではそれようのメソッドが既にあるので便利。しかし名前をよく忘れる。その名は persisted?


item = Item.new
item.persisted? # DBに保存されてないのでfalseになる。selectしたオブジェクトはtrueに。

2015年1月7日水曜日

【Rails】Parallelで並列処理すると"DEPRECATION WARNING: Database connections will not be closed automatically, please close your database connection at the end of the thread by calling `close` on your connection. For example: ActiveRecord::Base.connection.close" と警告がでる

 ループ処理の高速化のためParallelでマルチスレッドにすると、処理後に、「DEPRECATION WARNING: Database connections will not be closed automatically, please close your database connection at the end of the thread by calling `close` on your connection. For example: ActiveRecord::Base.connection.close」という警告がでる。
コネクションを自分で閉じろとな、ふむふむ。言わんとしていることは分かるので、警告に従いclose処理をいれる。


Parallel.map_with_index( array, :in_threads=> 4 ) do | data, idx |
begin
# やりたい処理
rescue => ex
Rails.logger.error "エラーです。"
ensure
ActiveRecord::Base.connection.close
end
end

しかし警告は消えない。ホワイ? 結局解決方法はみつからず。しかし何回動かしてもプログラムの動作には
問題ないし、接続先のDBで接続すうを見てみると。

プログラム実行時


mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_connected | 24 |
+-------------------+-------+
1 row in set (0.01 sec)

プログラム終了後


mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_connected | 14 |
+-------------------+-------+

プログラム終了後に接続数はちゃんと減っているので大丈夫なんではないだろうか。

2015年1月6日火曜日

【RubyOnRails】オートコンプリート rails3-jquery-autocomplete を使うより jquery-ui のautocompleteを直接使う方がよかった。

 Railsでオートコンプリートというとrails3-jquery-autocompleteが主流なようだが、
  • マルチバイト文字を使うとき2文字以上入力しないと候補が表示されない
  • 該当する候補がないときにでる「no existing match」を消せない、且つ文字もかえれないっぽい。
という問題がある。
特に1つ目は今作ってるサービスはオートコンプリートで出したい候補に2・3文字のものが多いので、2文字手で打った段階でサジェストだとほぼ意味がない。
というわけでjquery-uiを使ってみる。
rails3-jquery-autocompleteも中ではjquery-uiのautocompleteを使っているようなので、同じことができるはず。

このブログの記事を参考にさせてもらいました。

ただ、同じページにオートコンプリートしたいテキストボックスが複数ある場合だとこのサンプルだとうまくいかないので、ちょっと修正。

■HTML










■Javascript





1文字入れただけで候補が表示されるオートコンプリートを実現できた。
次はフォーカスした段階で、候補(この時点では絞り込まれていないリスト全体)を表示できるようにしたい。

2014年12月20日土曜日

【RSpec】成功したテストでもit句に書いたラベルを結果に書き出す方法

 Rspecで書いたunitテストの内容を他の人(特にコードの読めない人)と共有する場合、
テスト内容の一覧表を別に作るのは手間すぎるので、
テストのラベル(name?)の一覧を渡すのが一番よさそう。
(もちろんそれで事足りるかはラベルをわかりやすく書いてないとだけど)

 失敗したテストのラベルは結果の標準出力にでてくるけど、
成功した場合に出す方法がわからなかったが下記でできた。
fオプションに dパラメータを渡す。oオプションは結果をファイルに保存するため。


$ bin/rspec -P "spec/models/card.rb" -f d -o /tmp/card_labels.txt

【サイコロの目が偶数のときだけカードがひけること】
【ジョーカーがでるとサイコロが2つになること】
【ハートのカードを引くともう一度カードがひけること】
【クラブのキングを引くと一回休みになること】
【コインが0になると、ゲームオーバーになること】


 見つけるまで結構時間がかかったけどヘルプをちゃんと読めってことだな。

2014年12月14日日曜日

【RubyOnRails】RspecでActionMailerでのメール送信のtestを書く

 会員登録や受注確定など特定の処理で提携のメールを送る という処理はよくある。Railsでメールを送る処理を書く場合、ActionMailerを継承したクラスにメールの内容を記載するが、そのtestの書き方。できないかと思ってたらできたのでよかった。

まず、config/environments/test.rb で下記のように追加する。


config.action_mailer.delivery_method = :test

# config.action_mailer.perform_deliveries = false


「config.action_mailer.delivery_method = :test」を設定すると、specの中でActionMailerインスタンスを Base::ActionMailer.deliveries.last で取得できる。testの下記方はこの Base::ActionMailer.deliveries.last の戻り値に対して、

mailer = Base::ActionMailer.deliveries.last
expect( mailer.subject ).to eq "本日は晴天なり"

のようにして書く。

添付ファイルがある場合のtest

添付ファイルがある場合、メールはマルチパートになるため添付なしとtestがちょっと変わる。


mailer = Base::ActionMailer.deliveries.last
expect( mailer.subject ).to eq "合格通知"
expect( mailer.from ).to eq "goukaku@test.com"

# 本文はマルチパートになるので下記のように書く
expect( mailer.parts[0].body ).to eq "サクラサク"

# 添付ファイルの件名チェック
expect( mailer.attachments[0].filename ).to eq "合格証明書.txt"



ちなみに、「config.action_mailer.perform_deliveries = false」をonにすると、メールが送信されなくなる。開発環境での誤送信などの防止にはいいがtestできなくなってしまうのでtest環境ではoffがよさげ。


# 余談だけどこのブログで使っている Syntax Hilighterはスマホで見るとうまく動かないらしい。スマホからご覧の場合jsのサンプルソースが見れないようなので(その他のソースもハイライトされてない)PCで見てくださいませ。(ん~どうしよう。)

2014年12月3日水曜日

Railsのログにタイムスタンプなどを載せて見やすくする方法

 Railsのログはデフォルトのままだと、タイムスタンプもログレベルも出ずに読みにくい。
本番環境のログの場合、config/environments/production.rb に下記のように書くと、


config.logger = Logger.new("log/production.log")
config.logger.formatter = Logger::FormatterWithTime.new


こんな風に見やすくなる。

[2014-12-03 00:22:10] [info] Rendered home/pie_chart.html.erb (2.6ms)
[2014-12-03 00:22:10] [info] Rendered home/index.html.erb within layouts/application (6565.4ms)
[2014-12-03 00:22:10] [info] Rendered part/_login_form.html.erb (1.3ms)
[2014-12-03 00:22:10] [info] Rendered part/_common_tag.html.erb (0.5ms)

2014年11月16日日曜日

Railsでseedファイルを複数持ちたいとき

Railsでseedファイルを複数持ちたいとき(後でデータを追加する必要ができてときなど)、
下記のようにrakeタスクとして追加するとできる。

あとはdb/seedsディレクトリを作って、seedファイルを追加するだけ。


lib/tasks/seed.rake

# coding:utf-8

Dir.glob( File.join( Rails.root, "db", "seeds", "*.rb" ) ).each do | file |
desc "seeding data by #{file}"
task "db:seed:#{ File.basename( file, ".rb" ) }" => :environment do
load( file )
end
end