base64ã¨ã³ã³ã¼ãå½¢å¼ã®æååãã¼ã¿ããã¼ã¸ã«åãè¾¼ã
æ¦è¦
S3ã®ãã©ã¤ãã¼ããã±ããã«ä¿ç®¡ããã¦ããç»åã表示ãããã
S3ãããªãã¸ã§ã¯ããåå¾ãããããbase64ã¨ã³ã³ã¼ããããã®ãData URLã使ç¨ãã¦ãã©ã¦ã¶ã«åãè¾¼ã¿ãç»åã表示ãããã¨ãè¡ãªã£ãã®ã§
ãããã¡ã¢ã¨ãã¦æ®ãã¾ãã
Data URL ã¨ã¯
Data URL 㯠data: ã¹ãã¼ã ãå
é ã«ã¤ããURLã§ããã¼ã¿ãã¤ã³ã©ã¤ã³ã§ææ¸ã«åãè¾¼ããã¨ãã§ãã¾ãã
ã¹ãã¼ã (data:)ãMIMEã¿ã¤ããbase64ãã¼ã¯ã³ããã¼ã¿èªä½ã®4ã¤ã®é¨åã§æ§æããã¾ãã
data:[<mediatype>][;base64],<data>
ãã¼ã¿ãæåã®å ´åã¯ããã®ã¾ã¾æå®ãããã¨ãã§ãã¾ãã
æå以å¤ã§ããã°ãbase64
ãæå®ãã base64ã¨ã³ã³ã¼ããããã¤ããªã¼ãã¼ã¿ãæå®ãã¾ãã
Data URIã¹ãã¼ã ã«å¯¾å¿ãããã©ã¦ã¶ããbase64ã¨ã³ã³ã¼ãããããã¼ã¿ããã³ã¼ããããããå±éãã¦ãããããã
ä¾ã¨ãã¦PNGã®ç»åãbase64ã¨ã³ã³ã¼ãããData URIã¯ä»¥ä¸ã®ããã«ãªãã¾ãã
image_tag "data:image/png;base64, #{image_data}"
image_data
ã®é¨åã¯ãbase64ã¨ã³ã³ã¼ãããããã¼ã¿ãã®ãã®ãå
¥ãã¾ãã
base64ã¨ã³ã³ã¼ãã¨ã¯
base64ã¯ããã¼ã¿ã64種é¡ã®å°åå¯è½ãªè±æ°åã®ã¿ãç¨ãã¦ããã«ããã¤ãæåããã¤ããªãã¼ã¿ãæ±ãããã®ã¨ã³ã³ã¼ãæ¹å¼ã§ãã
å
·ä½çã«ã¯ãAãâ¦ãZãaãâ¦ãzã0ãâ¦ã9ã+ã/ ã§ãã¼ã¿ãå¤æããã¦ãã¾ãã
base64ã¨ã³ã³ã¼ãæ¹å¼ã¯ãASCIIããã¹ãããæ±ããªãã¡ãã£ã¢ä¸ã§ä¿åãéä¿¡ãè¡ãéã«ããã¼ã¿ãå¤æããããã«ä½¿ç¨ããã¦ã¾ãã
base64ãå®ããããçµç·¯ã¨ãã¦ããã¤ã¦é»åã¡ã¼ã«ãéä¿¡ããéã«SMTPãããã³ã«ã§ã¯ASCIIã§è¡¨ç¾ãããè±æ°åããéä¿¡ãããã¨ãã§ããªãã£ãããç»åãªã©ã®ããã¹ã以å¤ã®ãã¼ã¿ãæ±ãããã«ASCIIã¸ã®å¤ææ¹æ³ãå®ãããã¾ããã
Data URL ã§ç»åã表示ãããã¨ã«ã¤ãã¦ã®æ³¨æç¹
- ãã©ã¦ã¶ã«ãã£ã¦URLã®æåæ°ã«å¶éãè¨ãããã¦ãã¾ãã
データ URL - URI | MDN - base64ã¨ã³ã³ã¼ããã¦åãè¾¼ãã¨ãå½ç¶ã§ãã imgã¿ã°ã§srcã«ç»åãã¹ãæå®ããããããã½ã¼ã¹ã³ã¼ãèªä½ãé·ããªãã¾ãããã®ããåãè¾¼ãç»åã®åã ããã¼ã¸ãµã¤ãºã大ãããªãã¾ãããã¼ã¿è»¢ééãå¢å ãããããç»åãåãè¾¼ãã ãã¡ã¤ã«ã®ãã¦ã³ãã¼ãã«æéããããå¯è½æ§ãããã¾ãã
å®éã®ã³ã¼ã
s3 = Aws::S3::Resource.new(client: s3_client) object = s3.bucket(Settings.s3.bucket_name).object(prefix: "uploads/image") image_data = Base64.encode64(object.get.body.read)
object.get.body.read
ã®é¨åã«ã¤ãã¦ã
getã¡ã½ããã§ãªãã¸ã§ã¯ããåå¾ããbodyã¡ã½ããã§ãªãã¸ã§ã¯ãã®ä¸èº«ãåç
§ãã¾ãã以ä¸ã®ããã«ãã¤ãåãã¼ã¿ãæ±ãã¹ããªã¼ã ã¨ãªã£ã¦ãã¾ãã
pry > object.get.body.class
=> StringIO
æååã¨ãã¦æ±ãããã«ã¯ã¹ããªã¼ã ãã read ãã¦æåååã«å¤æããå¿ è¦ãããã¾ãã
pry > object.get.body.read.class => String
ãã¥ã¼å´ã§ã®è¨è¿°ã§ãã
image_tag "data:image/jpeg;base64, #{image_data}"
ã¡ãªã¿ã«ãobject.get.body.read ã§æååã«ããå¾ãã®ã¾ã¾dataURLã«åãè¾¼ããã¨ãã§ããã®ãã¨æãã
base64ã¨ã³ã³ã¼ãããã«ãã¥ã¼å´ã«è¿ã㨠Invalid byte sequence in UTF-8
ã¨ã¨ã©ã¼ã«ãªã£ã¦ãã¾ãã¾ããã
ãããããdataURLã解éã§ããªãæååãå«ã¾ãã¦ãããããå¥é追å å¦çï¼ã¨ã¹ã±ã¼ãï¼ï¼ãå¿
è¦ãããããªãã¨æãã¾ãã
åèã«ããURL
データ URL - URI | MDN
Base64 - Wikipedia
Base64についてまとめてみた - iimon TECH BLOG
æµ®åå°æ°ç¹ã®æ¼ç®ã«ã¤ãã¦
èæ¯
ååä¾¡æ ¼ã«å¯¾ãå²å¼çãããã¦æçµä¾¡æ ¼ãåºãéã«è¨ç®çµæã«ãããçãã¦ãããããããã«ã¤ãã¦èª¿ã¹ãå
容ãã¾ã¨ãã¾ãã
ãã¨ãã°ä»¥ä¸ã®ãããªãæ°å¤ãªãã©ã«ï¼Floatã¯ã©ã¹ï¼ã§è¨ç®ããå ´åãè¨ç®çµæãå¾®å¦ã«ããã¦ãã¾ã£ã¦ãã¾ãã
pry(main)> 0.3 - 0.2 => 0.09999999999999998
ãããã丸ã誤差ã¨ãããã®ã
ããã°ã©ãã³ã°ã§ã®æ¼ç®
PCã¯10é²æ°ã§ã¯ãªãã2é²æ°ã§è¨ç®ãè¡ãã¾ãã
ãã¨ãã°ã10é²æ°ã§0.2ã¯2é²æ°ã§è¡¨ãã¨0.00110011...ã®ãããªå¾ªç°å°æ°ã«ãªãã¾ãã
æ¼ç®ãè¡ãéã循ç°å°æ°ã¯æ¡ã®éä¸ã§åãæ¨ã¦ããããã¨ã«ãªãã¾ãã
ãã®ãããå°æ°ç¹ã®æ¼ç®ã§ã¯ä¸è¨ã®ãããªèª¤å·®ãçºçãããã¨ãããã¾ãã
ãã¡ãã¯IEEEï¼ç±³å½é»åé»æ°æè¡è
åä¼ï¼ã«ããè¦æ ¼åããããIEEE754ã¨ããæµ®åå°æ°ç¹æ°å½¢å¼ã«æºæ ãã¦ãããã¨ã«ããæåã¨ã®ãã¨ã
ãã¾ãã¾ãªããã°ã©ãã³ã°è¨èªãIEEE 754ã®è¦æ ¼ã«æºæ ãã¦ããããã
Rubyã ãã§ãªãä»ã®è¨èªã«ããã¦ãä¸è¨ã®å½±é¿ãåãããã¨ã«æ³¨æã
Rubyã§ã©ãããã
ãã®ããæ£ç¢ºãªå°æ°ç¹ã®è¨ç®ãããã«ã¯ãã»ãã®æ°å¤ã¯ã©ã¹ã使ãå¿ è¦ãããã¾ãã
Rationalã¯ã©ã¹
æçæ°ãæ±ãã¯ã©ã¹ã§ãã
å°æ°ç¹ã2é²æ°ã«è¡¨ãéã«ç¡éå°æ°ã¨ãªããããä¸å®ã®æ¡æ°ã§ä¸¸ãå¦çãè¡ãããã誤差ãçºçãã¦ãã¾ã£ã¦ãã¾ããã
æ´æ°/æ´æ° ã¨ããå½¢ã§æ´æ°ã®è¨ç®ãããå¾ã§å°æ°ã«æ»ããã¨ããã¦ããã¾ããï¼æ´æ°ã¯2é²æ°ã§è¡¨ããã¨ãã§ããããï¼
pry(main)> 0.3r - 0.2r => (1/10) pry(main)> (0.3r - 0.2r).to_f => 0.1 pry(main)> Rational("0.3") - Rational("0.2") => (1/10) pry(main)> (Rational("0.3") - Rational("0.2")).to_f.to_s => "0.1"
BigDecimalã¯ã©ã¹
å¯å¤é·æµ®åå°æ°ç¹è¨ç®ã¯ã©ã¹ã§ãã
Rationalã«æ¯ã¹ã¦è¨ç®é度ãé¡èã«ä½ä¸ãã¾ããããã¡ããæ£ç¢ºãªå°æ°ã®è¨ç®ãå¯è½ã§ãã
pry(main)> BigDecimal("0.3") - BigDecimal("0.2") => 0.1e0 # to_sã使ãã¨10é²æ°ã®æååã«å¤æãã¦ããã pry(main)> (BigDecimal("0.3") - BigDecimal("0.2")).to_s("F") => "0.1"
åèURL
研鑽Rubyプログラミング ― 実践的なコードのための原則とトレードオフ – 技術書出版と販売のラムダノート
IEEE 754 - Wikipedia
class Float (Ruby 3.3 リファレンスマニュアル)
浮動小数点って何? #Ruby - Qiita
ActiveRecordã§é¢é£ã¢ãã«ããã¼ããã
èæ¯
ããã¢ãã«ã®ãã£ãã·ã¥ãä½æãããããã®éã«é¢é£ã¢ãã«ãåæã«ãã£ãã·ã¥ãä½æãã¦ããããã é¢é£ã¢ãã«ãèªã¿è¾¼ãããã« preloadãincludesãeager_loadãjoins ã®ã©ã®ã¡ã½ããã使ç¨ãããè¯ããè¿·ã£ãããã¾ã¨ãã¨ãã¦èªåç¨ã«ããã
å®è¡ç°å¢
åã¡ã½ããã®æåã確èª
railsã³ã³ã½ã¼ã«ä¸ã§ã®ã¯ã¨ãªã®å®è¡ç¢ºèªæ¹æ³ã¨ãã¦ãto_sql
ã¡ã½ããã¯ãã¹ã¦ã®ã¯ã¨ãªãåºåãããªããããexplain
ã¡ã½ããã使ãã
preload
é¢é£ãã¼ã¿ãå¥ã®ã¯ã¨ãªã§åå¾ãã¦ãã£ãã·ã¥ããã
pry(main)> User.preload(:tasks).explain; nil User Load (45.8ms) SELECT `users`.* FROM `users` Task Load (115.9ms) SELECT `tasks`.* FROM `tasks` WHERE `tasks`.`user_id` IN (1, 2, 3) => nil
ãªããusersãã¼ãã«ã®ã¬ã³ã¼ãã大éã«ããå ´åãINå¥ã大ãããªããã注æãå¿ è¦ã
ç¹å®ã®æ¡ä»¶ã«åè´ãããã¼ã¿ã ããåå¾ãã¦ãã£ãã·ã¥ãããå ´åã ä¸è¨ã®ã¨ãã常ã«2ã¤ã®ã¯ã¨ãªãçºè¡ãããããwhereæ¡ä»¶ã§postsãçµããã¨ã¯ã§ããªãã
pry(main)> User.preload(:tasks).where(tasks: {id: 1}).explain; nil User Load (2.4ms) SELECT `users`.* FROM `users` WHERE `tasks`.`id` = 1 ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'tasks.id' in 'where clause': SELECT `users`.* FROM `users` WHERE `tasks`.`id` = 1 from /root/.rbenv/versions/2.3.4/lib/ruby/gems/2.3.0/gems/mysql2-0.5.5/lib/mysql2/client.rb:151:in `_query'
Userãã¼ã¿ãªãçµããã¨ãã§ããã
pry(main)> User.preload(:tasks).where(users: {id: 1}).explain; nil User Load (1.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 Task Load (1.1ms) SELECT `tasks`.* FROM `tasks` WHERE `tasks`.`user_id` = 1 => nil
includes
includesã¯ãpreloadã¨åæ§ã«é¢é£ãã¼ã¿ãå¥ã®ã¯ã¨ãªã§ãã¼ãããã
pry(main)> User.includes(:tasks).explain; nil User Load (29.5ms) SELECT `users`.* FROM `users` Task Load (100.3ms) SELECT `tasks`.* FROM `tasks` WHERE `tasks`.`user_id` IN (1, 2, 3) => nil
å ã»ã©ã®preloadã§ã¯é¢é£ã¢ãã«ãwhereã§çµããã¨ãã§ããªãã£ãã includesã§ã¯ã以ä¸ã®ã¨ããLEFT OUTER JOINã§æå®ãããæ¡ä»¶ãé©ç¨ãããçµæãåå¾ãããã¨ãã§ããã
pry(main)> User.includes(:tasks).where(tasks: {id: 1}).explain; nil SQL (5.7ms) SELECT `users`.`id` AS t0_r0, `users`.`created_at` AS t0_r1, `users`.`updated_at` AS t0_r2, `tasks`.`id` AS t1_r0, `tasks`.`user_id` AS t1_r1, `tasks`.`provider` AS t1_r2, `tasks`.`uid` AS t1_r3, `tasks`.`created_at` AS t1_r4, `tasks`.`updated_at` AS t1_r5 FROM `users` LEFT OUTER JOIN `tasks` ON `tasks`.`user_id` = `users`.`id` WHERE `tasks`.`id` = 1 => nil
ãã®éã«includesã¯2 ã¤ã®ã¯ã¨ãªãã1 ã¤ã®ã¯ã¨ãªã«å¤æ´ããã¦ããã
ãªãusersã®æ¡ä»¶çµãè¾¼ã¿ã§ã¯2ã¤ã®ã¯ã¨ãªãçæããã¦ããã
pry(main)> User.includes(:tasks).where(users: {id: 1}).explain; nil User Load (1.2ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 Task Load (0.8ms) SELECT `tasks`.* FROM `tasks` WHERE `tasks`.`user_id` = 1 => nil
referencesã使ç¨ãããã¨ã§whereå¥ã§æ¡ä»¶çµããã¿ãããªãã¦ããLEFT OUTER JOINãããã¨ãã§ããã
pry(main)> User.includes(:tasks).references(:tasks).explain; nil SQL (192.1ms) SELECT `users`.`id` AS t0_r0, `users`.`created_at` AS t0_r1, `users`.`updated_at` AS t0_r2, `tasks`.`id` AS t1_r0, `tasks`.`user_id` AS t1_r1, `tasks`.`provider` AS t1_r2, `tasks`.`uid` AS t1_r3, `tasks`.`created_at` AS t1_r4, `tasks`.`updated_at` AS t1_r5 FROM `users` LEFT OUTER JOIN `tasks` ON `tasks`.`user_id` = `users`.`id` => nil
eager_load
eager_loadã¯ãLEFT OUTER JOINã使ç¨ãã¦åä¸ã®ã¯ã¨ãªã§ãã¹ã¦ã®é¢é£ã¢ãã«ããã¼ãããã
pry(main)> User.eager_load(:tasks).explain; nil SQL (200.1ms) SELECT `users`.`id` AS t0_r0, `users`.`created_at` AS t0_r1, `users`.`updated_at` AS t0_r2, `tasks`.`id` AS t1_r0, `tasks`.`user_id` AS t1_r1, `tasks`.`provider` AS t1_r2, `tasks`.`uid` AS t1_r3, `tasks`.`created_at` AS t1_r4, `tasks`.`updated_at` AS t1_r5 FROM `users` LEFT OUTER JOIN `tasks` ON `tasks`.`user_id` = `users`.`id` => nil
ããã¯ãincludesãwhereå¥ã¾ãã¯orderå¥ã§tasksãã¼ãã«ã®ãã¼ã¿ãæ±ãéã«åä¸ã®ã¯ã¨ãªã«ãªãã®ã¨ãã¾ã£ããåãã
pry(main)> User.eager_load(:tasks).where(tasks: {id: 1}).explain; nil SQL (4.3ms) SELECT `users`.`id` AS t0_r0, `users`.`created_at` AS t0_r1, `users`.`updated_at` AS t0_r2, `tasks`.`id` AS t1_r0, `tasks`.`user_id` AS t1_r1, `tasks`.`provider` AS t1_r2, `tasks`.`uid` AS t1_r3, `tasks`.`created_at` AS t1_r4, `tasks`.`updated_at` AS t1_r5 FROM `users` LEFT OUTER JOIN `tasks` ON `tasks`.`user_id` = `users`.`id` WHERE `tasks`.`id` = 1 => nil
joins
joinsã¯ãå é¨çµå(INNER JOIN)ã使ç¨ãã¦é¢é£ã¢ãã«ã®ãã¼ã¿ãåå¾ããããã ãtasksãã¼ãã«ã®ãã¼ã¿ã¯SELECTããã¦ããªãã
pry(main)> User.joins(:tasks).explain; nil User Load (75.9ms) SELECT `users`.* FROM `users` INNER JOIN `tasks` ON `tasks`.`user_id` = `users`.`id` => nil
tasksã®ãã¼ã¿ãæ±ãããå ´åã¯ãselectã¡ã½ããã使ãå¿ è¦ãããã
pry(main)> User.joins(:tasks).select('users.*, tasks.id').explain; nil User Load (72.0ms) SELECT users.*, tasks.id FROM `users` INNER JOIN `tasks` ON `tasks`.`user_id` = `users`.`id` => nil
åèã«ããè¨äº
RSpec 㧠ActiveModel::Errors ã®ã¢ãã¯ãä½æãã
ActiveRecord::RecordInvalid ãçºçãããã©ããããã¹ãããããã« ActiveModel::Errors ã®ã¢ãã¯ãä½ãããã
å®è¡ç°å¢
ActiveRecord::RecordInvalid ãçºçãããã©ããã®ãã¹ã
subjectãå®è¡ãããé ActiveRecord::RecordInvalid ãçºçãããã©ããã®ãã¹ãã³ã¼ããå®è£
ãã¾ãã
ãªããsubjectã¯çç¥ãã¦ãã¾ãããsubjectã§å®ç¾©ãã¦ãããã®ã«ã¯ãuser.save! ã®å¦çãå«ã¾ãã¦ãã¾ãã
# Userã®ã¤ã³ã¹ã¿ã³ã¹ã«å¯¾ãã¦save!ã¡ã½ããå®è¡æã«ActiveRecord::RecordInvalidãraiseãããããã«ã¹ã¿ãå before { allow_any_instance_of(User).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) } it { expect { subject }.to raise_error(ActiveRecord::RecordInvalid) }
Model ã«errors ã class ãå®ç¾©ããã¹ã¿ããä½æ
ã¤ãã« user.save! ã«å¤±æããå ´åã以ä¸ã®ããã«ã¨ã©ã¼ã¡ãã»ã¼ã¸ããã°ã«åºåããã³ã¼ããæ¸ããã¦ããã¨ãã¾ãã
Rails.logger.error e.message
ãã®å ´åã«ãsubjectãå®è¡ã ActiveRecord::RecordInvalid ãçºçããæã«ãã¨ã©ã¼ãã°ãåºåããããã©ããã®ãã¹ãã³ã¼ããå®è£
ãã¾ãã
ã¾ããã¹ãã®åå¦çã¨ãã¦ã¹ã¿ãã®å®ç¾©ãè¡ãã¾ãã
let(:errored_model) do instance_double( User, errors: instance_double('errors', full_messages: ['nameãå ¥åãã¦ãã ãã'], messages: { name: ['ãå ¥åãã¦ãã ãã'] }), class: class_double(User, i18n_scope: :activerecord), ) end before do # Userã®ã¤ã³ã¹ã¿ã³ã¹ã«å¯¾ãã¦save!ã¡ã½ãããå®è¡ãããæã«ActiveRecord::RecordInvalidãraiseãããããã«ã¹ã¿ãå allow_any_instance_of(User).to receive(:save!).and_raise(ActiveRecord::RecordInvalid, errored_model) # Rails.loggerã®ãªãã¸ã§ã¯ãã«å¯¾ãã¦errorã¡ã½ãããå¼ã³åºããããã«ãã allow(Rails.logger).to receive(:error) end
and_raise ã«ã¤ãã¦
rspec-mocks/lib/rspec/mocks/message_expectation.rb at main · rspec/rspec-mocks · GitHub
ãã¡ãã«ããããã« and_raise å
ã§ãraise ã¡ã½ãããå¼ã³åºããã¦ãããããraise ActiveRecord::RecordInvalid, user
ã¨ããããã«ã
and_raise ã®å¼æ°ã« ActiveRecord::RecordInvalid, user
ã渡ãã¦ãããã¨ããããã§ãã
instance_double ã® errors ã class ã«ã¤ãã¦
User ã® InstanceDouble 㧠errors ãæå®ãã¦ããªãã¨ä»¥ä¸ã®ãããªã¨ã©ã¼ãçºçãã¾ãã
RSpec::Mocks::MockExpectationError: #<InstanceDouble(User) (anonymous)> received unexpected message :errors with (no args) from /app/.bundle/ruby/2.6.0/gems/rspec-support-3.10.2/lib/rspec/support.rb:102:in `block in <module:Support>'
ã»ãã«ã i18n_scope ã¡ã½ãããå®ç¾©ããã¦ããªãã¨ä»¥ä¸ã®ãããªã¨ã©ã¼ãçºçãã¾ãã
NoMethodError: undefined method `i18n_scope' for RSpec::Mocks::InstanceVerifyingDouble:Class from /app/.bundle/ruby/2.6.0/gems/activerecord-6.0.3.6/lib/active_record/validations.rb:22:in `initialize'
å
é¨çã« ActiveRecord ã«ãã㦠i18n_scope ã¡ã½ãããå¼ã³åºãã¦ããããã§ããã
rails/activerecord/lib/active_record/translation.rb at main · rails/rails · GitHub
æçµçãªãã¹ãã³ã¼ã
let(:errored_model) do instance_double( User, errors: instance_double('errors', full_messages: ['nameãå ¥åãã¦ãã ãã'], messages: { name: ['ãå ¥åãã¦ãã ãã'] }), class: class_double(User, i18n_scope: :activerecord), ) end before do allow_any_instance_of(User).to receive(:save!).and_raise(ActiveRecord::RecordInvalid, errored_model) allow(Rails.logger).to receive(:error) end # å®éã«Rails.logger.errorãå¼ã³åºãããã¨ã©ã¼ã¡ãã»ã¼ã¸ãå¼æ°ã¨ãã¦æ¸¡ããããã¨ãã¢ãã¯ã§ç¢ºèªãã it 'ã¨ã©ã¼ãã°ãåºåããããã¨' do subject expect(Rails.logger).to have_received(:error).with("ããªãã¼ã·ã§ã³ã«å¤±æãã¾ãã: nameãå ¥åãã¦ãã ãã") end
ã¢ãã¯/ã¹ã¿ãå®è£ ã®ããã®ã¡ã½ãã
ä¸è¨ã§ä½¿ç¨ããã¡ã½ãããæ´çãã¾ãã
ãããã RSpec ã«ãããã¹ã¿ãã¨ã¢ãã¯ã®éã
ã¹ã¿ãã¯ãä»ã®ã¡ã½ãããå®è¡ãããªãããã«ãã¦æ¬²ããå¤ãè¿ããããã®ã§ãã
ã¤ã¾ãããã¹ãã§ç¢ºèªãããå 容ã®ããã«ä½ãé½åã®ããå¤ãè¿ãããã«è¨å®ãããã®ãã¹ã¿ãã¨è¨ãã¾ãã
ã¢ãã¯ã¯ãæå®ã®ã¡ã½ãããå®è¡ããããã©ããããå¼æ°ãé »åº¦ãæå®ãã¦ãã§ãã¯ãããã®ã§ãã
instance_double
ç¹å®ã®ã¯ã©ã¹ã«å¯¾ã㦠doubleï¼èº«ä»£ããï¼ãä½æãã¾ãã
æå®ããã¯ã©ã¹ã§å®ç¾©ããã¦ããã¤ã³ã¹ã¿ã³ã¹ã¡ã½ããã®ã¿ããã¹ã¿ãã¨ãã¦è¨±å¯ããã¾ãã
ã»ã¨ãã©doubleã¨åæ§ã ããallowãªã©ã§å®ç¾©ããã¡ã½ããããå¼æ°ã§æå®ããã¯ã©ã¹ï¼ä»åã® Userï¼ã®ã¤ã³ã¹ã¿ã³ã¹ã¡ã½ããã¨ãã¦åå¨ãã¦ããªãã¨ãã¹ãã失æãã¾ãã
class_double
å®éã®ã¯ã©ã¹ã®æ
å ±ã¨ç´ä»ãã¦å®éã®ã¯ã©ã¹ã«å®è£
ããã¦ããªãã¡ã½ããã®ã¹ã¿ãåã¯ã§ããªãããã«ãªãã¾ãã
ã»ã¨ãã©instance_doubleã¨åæ§ã ãããã¡ãã¯ã¯ã©ã¹ã¡ã½ããã«å¯¾ãã¦é©ç¨ããã¾ãã
allow_any_instance_of
çæãããã¤ã³ã¹ã¿ã³ã¹ãã¹ã¦ã«å¯¾ãã¦ãã¹ã¿ããè¨å®ã§ãã¾ãã
åèã«ããè¨äº
ã³ã³ããééä¿¡ã§ãã¼ã¿ãã¼ã¹ãå ±æãã
ç°ãªããªãã¸ããªã®DBã³ã³ããã«ãã¼ã¿ãæå
¥ããæ¹æ³ãã¡ã¢ãã¾ãã
ä¾ãã°ããµã¼ãã¹ï¼è¨èªï¼ãã¨ã«ãªãã¸ããªãåãã¦ãã¦ãDBãå
±æãããã±ã¼ã¹ã«æ´»ç¨ã§ãããã¨æãã¾ãã
åæã¨ãã¦ãAãªãã¸ããªãã¡ã¤ã³ã¨ãã¦DBã³ã³ãããèµ·åãã¾ãããã¡ãã«Bãªãã¸ããªã§æ§ç¯ãããappã³ã³ããã®ãã¼ã¿ãä¿åã§ããããã«ãã¾ãã
ãããã¨ã¯ãã³ã³ããã®ãããã¯ã¼ã¯ãå©ç¨ãã¦ãã³ã³ããééä¿¡ãè¡ãã¾ãã
æé
ã¾ããAãªãã¸ããªã® docker-compose.yml ã®å
容ã以ä¸ã®ããã«ãã¾ããï¼insert-mysql
ã¯ä¾ã§ããï¼
db: (ä»ã®é ç®ã«ã¤ãã¦ã¯çç¥) networks: - default - insert-mysql networks: insert-mysql: driver: bridge
ä¸è¨ã®è¨å®å¤æ´ãåæ ãã¾ãã
$ docker-compose up -d Creating network "hogehoge_insert-mysql" with driver "bridge"
ããã§ãããã¯ã¼ã¯ä¸è¦§ã«æ°ãããããã¯ã¼ã¯(hogehoge_insert-mysql
)ã表示ããã¦ãããã¨ã確èªãã¾ãã
$ docker network list NETWORK ID NAME DRIVER SCOPE abcdef123456 hogehoge_default ãã bridge local 123456abcdef hogehoge_insert-mysql ãã bridge local aabbccddeeff bridge ãã bridge local
ã¾ããç¾æç¹ã§ã¯ä¸è¨ã®æ°ãããããã¯ã¼ã¯ã使ç¨ãã¦ããã³ã³ããããAãªãã¸ããªã®DBã³ã³ãããããªããã¨ã確èªãã¦ããã¾ãã
"Containers"
ã®é
ç®ã®é¨åã§ãã
$ docker network inspect hogehoge_insert-mysql
次ã«ãBãªãã¸ããªã® docker-compose.yml ã®ç·¨éãè¡ãã¾ãã
app: ï¼ä»ã®é ç®ã«ã¤ãã¦ã¯çç¥ï¼ networks: - default - hogehoge_insert-mysql networks: hogehoge_insert-mysql: external: true
å ã»ã©ã¨åæ§ã«è¨å®ã®å¤æ´ãåæ ãã¾ãã
$ docker-compose up -d
å度hogehoge_insert-mysql
ã®ãããã¯ã¼ã¯ã使ç¨ãã¦ããã³ã³ããã®ç¢ºèªããã¾ãã
"Containers"
ã®é
ç®ã«ãBãªãã¸ããªã®appã³ã³ããã追å ããã¦ãããã¨ã確èªã§ããã¨æãã¾ãã
$ docker network inspect hogehoge_insert-mysql
ããã§ãªãã¸ããªã«è·¨ã£ã¦DBã®å ±æãã§ããã¯ãã§ãã
ããã³ã³ããèµ·åã«å¤±æããå ´å
æ¢åã®ã³ã³ãããå½±é¿ãã¦ããå¯è½æ§ãããã®ã§ãä¸æ¦å
¨é¨ã®ãããã¯ã¼ã¯ãåé¤(docker network rm
)ã¨docker-compose down
ãããã¨è¯ããã¨æãã¾ãã
åè
ã¯ããdocker network create hoge_network
ã§æåã§æ°ãããããã¯ã¼ã¯ãä½æãããã¨ããã®ã§ãããdocker-compose.yml ã«è¨å®ãããã¨ã«ãããèªåä½æãããã£ãã®ã§ãä¸è¨ã®æ¹æ³ã«ãã¾ããã
https://tech-blog.rakus.co.jp/entry/20181211/docker/postgresql/go
Rails ã®ã³ãã¯ã·ã§ã³ãã¼ã«ã«ã¤ãã¦ï¼ Puma ã® Worker æ°ãå¤ãã¦ã¿ãï¼
Railsã®ã³ãã¯ã·ã§ã³ãã¼ã«çµç±ã§è¡ã£ã¦ããDBæ¥ç¶ã«ã¤ãã¦ãå°ã調ã¹ãã®ã§ã¡ã¢ãæ®ãã¾ãã
å®è¡ç°å¢
Rails 6.1.3.1
Connection Pool ã¨ã¯
ã³ãã¯ã·ã§ã³ãã¼ã«ã¨ã¯ãRailsã®å¦çããã¼ã¿ãã¼ã¹ã«ã¢ã¯ã»ã¹ãããã³ã«ã³ãã¯ã·ã§ã³æ¥ç¶ã¨åæãè¡ã£ã¦è² è·ãé«ããªã£ãããããã©ã¼ãã³ã¹ãä½ä¸ããã®ãé²ãããã«ãäºã決ããããä¸éæ°ãèæ ®ãã¦ãã¼ã¿ãã¼ã¹ã¨ã®éã«ä½ã£ã¦ããæ¥ç¶ã®ã°ã«ã¼ãã®ãã¨ã§ãã
ãã¼ã¿ãã¼ã¹ã«æ¥ç¶ããç¶æ
ããã¡ã¢ãªä¸ã«ããããã確ä¿(ãã£ãã·ã¥)ãã¦ããããã¼ã¿ãã¼ã¹ã«ã¢ã¯ã»ã¹ããã¿ã¤ãã³ã°ã§ãPoolããå©ç¨å¯è½ãªæ¥ç¶ãåå©ç¨ãã¾ãã
æ¯åæ¥ç¶ãã¦åæãããããDBæ¥ç¶ã¨ããã³ã¹ãã®é«ãå¦çããããããè¡ããã¨ã§ãDBæ¥ç¶ã«ãããæéãç縮ã§ããå¹çããå¦çãè¡ããã¨ãã§ããã¨ããã¡ãªãããããã¨ã®ãã¨ã
Connection pool ã®è¨å®ã«ã¤ãã¦
Connection pool ã¯ãdatabase.yml ã§è¨å®ãã¾ãã
default: &default adapter: mysql2 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
pool ã¯ã¢ããªããã¼ã¿ãã¼ã¹ã«å¯¾ãã¦ä¿æã§ããæ¥ç¶ã®æ大æ°ã§ãã
ä¸è¨ã§ããã©ã«ãã® 5 ãè¨å®ãã¦ãã¾ãã
ã¾ããActiveRecord ã¯ã¹ã¬ãããã¨ã«åå¥ã®ãã¼ã¿ãã¼ã¹æ¥ç¶ã使ç¨ãã¾ãã
ãã®ãããpuma.rb ã«ããã¦ãã¹ã¬ããã¯ããã©ã«ãã®ã¾ã¾ 5 ã¨ã㦠pool ã¨åãå¤ã«ãªãããã«ãã¦ãã¾ããï¼ã¹ã¬ããæ° = pool æ°ï¼
max_threads_count = ENV.fetch('RAILS_MAX_THREADS', 5) min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count }
ãã¦ãæ¥ç¶ã使ç¨ããå¾ãèªåçã«è§£æ¾ããã¾ããã使ç¨å¯è½ãªæ°ãè¶ ããæ¥ç¶ã使ç¨ãããã¨ããã¨ãActive Record ã«ãã£ã¦ãããã¯ãããæ®ãã®ãªã¯ã¨ã¹ãã¯ãã¼ã«ã®æ¥ç¶ã®å¾ ã¡ç¶æ ã«ãªã£ã¦ãã¾ãã¾ãã
åä½ç¢ºèª
æ¥ç¶å¾
ã¡ã®ç¶æ
ã«ãªããã¨ãå®éã«è¦ã¦ã¿ããã¨æãã¾ãã
Promise.all() ãç¨ã㦠10 ãªã¯ã¨ã¹ãã並åå®è¡ãããããã«ãã¦ã¿ã¾ãã
Promise.all( [...new Array(10)].map((_, i) => { api.get(`http://localhost:3000/home/${index + 1}`) }) )
Railså´ã§ã¯ããªã¯ã¨ã¹ããåãä»ããDBå¦çãè¡ãã¾ãã
ããããããããã«ãå¦çæéã5ç§ã»ã©ä¼¸ã°ãã¾ãã
class HomeController < ApplicationController def index @user = User.find params[:id] sleep 5 end end
å
ã»ã©ä¸¦åå®è¡æ°ï¼poolï¼ã¨ã㦠5 ãè¨å®ãã¦ããã®ã§ã10 ãªã¯ã¨ã¹ãã®ãã¡æ®ãã® 5 ã¤ã®ãªã¯ã¨ã¹ããå¾
æ©ãããã¯ãã§ãã
ç»åããæå¾ã® 5 ãªã¯ã¨ã¹ãã¯é
ãã¦è¿ã£ã¦ãã¦ãããã¨ããããã¾ãã
次ã«ãpool æ°ãå¤æ´ãã¦ã¿ã¾ããå¤æ´ãããã°åç´ã«å®è¡æéã«ãå½±é¿ãåºã¦ãã¾ãã
pool æ°ã 3 ã«ãã¦ã¿ã¾ãã
default: &default adapter: mysql2 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 3 } %>
ãªããå¦çæéãé·ããªã©ã§æ¥ç¶ãåå¾ã§ããªãå ´åã¯ãã¢ããªã±ã¼ã·ã§ã³ããã®æ¥ç¶ãã¿ã¤ã ã¢ã¦ãã«ãªããä¾å¤ãçºçããå¯è½æ§ãããã¾ãã
could not obtain a connection from the pool within 5.000 seconds (waited 5.001 seconds); all pooled connections were in use excluded from capture: No host specified, no public_key specified, no project_id specified Completed 500 Internal Server Error in 5017ms (Views: 5.4ms | ActiveRecord: 0.0ms | Allocations: 158211)
Puma ã® Worker æ°ãå¤æ´ãã
Worker æ° â (å)ããã»ã¹æ°ã¨æããã¹ã¬ããæ° = pool æ° ã¯ 3 ã§ãã®ã¾ã¾ã¨ãããã®ç¶æ
㧠Worker æ°ãå¤æ´ãã¦ã¿ã¾ãã
puma.rb ã§ã³ã¡ã³ãã¢ã¦ãããã¦ãã以ä¸ã®è¨è¿°ãã¢ã³ã³ã¡ã³ããæå¹åãã¾ãã
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
ãã㨠åæ並åæ°ã¯ 3(ã¹ã¬ããæ°) à 2(workeræ°) = 6 ã«å¢ãã¦ãããã¨ããããã¾ãï¼
åèURL
Railsã¨MySQLã¨ã§ã¿ã¤ã ã¾ã¼ã³ãç°ãªãå ´åã®æ¤ç´¢ã®ä»æ¹
Railsã¨MySQLãªã©ã®DBã¨ã§ã¿ã¤ã ã¾ã¼ã³ãç°ãªãå ´åã«ããã¼ã¿æ½åºãæ¤ç´¢ã®éã«ãã¹ãèµ·ãããããªã®ã§èªæã®æå³ãè¾¼ãã¦ã¡ã¢ãã¾ãã
ãªãRailsã³ã³ã½ã¼ã«ã§æ¤ç´¢ããæ¹æ³ã«ã¤ãã¦è¨è¼ãã¦ãã¾ãã
å®è¡ç°å¢
ruby 2.6.6 Rails 6.0.3.6
ã¾ãã¯ããããã«ãããã¿ã¤ã ã¾ã¼ã³ã確èªãã¾ãã
mysql ã§ã®ã¿ã¤ã ã¾ã¼ã³ç¢ºèª
UTCã¨ãªã£ã¦ãã¾ãã
mysql> show variables like "%time_zone"; +------------------+--------+ | Variable_name | Value | +------------------+--------+ | system_time_zone | UTC | | time_zone | SYSTEM | +------------------+--------+ 2 rows in set (0.00 sec) mysql> select NOW(); +---------------------+ | NOW() | +---------------------+ | 2023-12-17 14:52:01 | +---------------------+ 1 row in set (0.00 sec)
Rails ã§ã®ã¿ã¤ã ã¾ã¼ã³ç¢ºèª
æ¥æ¬æéã§æ¤ç´¢ãã¦ãã¾ãã
pry(main)> Time.zone => #<ActiveSupport::TimeZone:0x000055555c991608 @name="Tokyo", @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>, @utc_offset=nil>
Rubyã³ã³ãããµã¼ãã¼ã®ã¿ã¤ã ã¾ã¼ã³
UTCã¨ãªã£ã¦ãã¾ãã
pry(main)> Time.now => "2023-12-17T15:00:03.228+00:00"
Railsã³ã³ã½ã¼ã«ã§ã®æ¤ç´¢æ¹æ³
ããã¤ãæ¹æ³ãããã®ã§ããããã®éããè¦ã¾ãã
ãªãç¾å¨æå»ã¯æ¥æ¬æéã§2023-12-18 00:00
ã§ããã¨ãã¾ãã
Time(Railsã¯TimeWithZone)ã¯ã©ã¹ã使ç¨ãã
Time.zone.now ã TIme.zone.now.ago ãªã© TimeWithZone ã¯ã©ã¹ã®ã¡ã½ããã使ã£ã¦æ¤ç´¢ãã¾ãã
pry(main)> User.where("updated_at > ?", Time.zone.now).to_sql => "SELECT `users`.* FROM `users` WHERE (updated_at > '2023-12-17 15:00:47.921823')"
æ¥æ¬æéãUTC(9æéå)ã«å¤æããã¦ãã¼ã¿ãã¼ã¹æ¤ç´¢ãè¡ãªã£ã¦ãããã¨ããããã¾ãã
æ¥æã®æååã§æå®ãã
ç´æ¥æ¥æã®æååãæå®ãã¦æ¤ç´¢ãã¾ãã
pry(main)> User.where("updated_at > ?", '2023-12-18 00:00').to_sql => "SELECT `users`.* FROM `users` WHERE (updated_at > '2023-12-18 00:00')"
æå®ããæéããUTCã«ã¯å¤æããã¦ããªããã¨ããããã¾ãã
ãªã®ã§ãUTCã«å¤æãã¦ããæå®ããªãã¨ãæ¥æ¬æéã«å¤æãããã¨åéããã¦ããããæå¾
ããæ¤ç´¢çµæãå¾ãããªãã®ã§æ³¨æãå¿
è¦ã§ãã
Integerã«å¤æãã
Timeã¯ã©ã¹(RailsãªãTimeWithZoneã¯ã©ã¹)ã®ãªãã¸ã§ã¯ãã to_i ã§Integerã«å¤æãããã®ãæå®ãã¾ãã
# Time.zone.now â integer pry(main)> User.where("updated_at > ?", Time.zone.now.to_i).to_sql => "SELECT `users`.* FROM `users` WHERE (updated_at > 1702825200)" # Time.zone.parse â integer pry(main)> User.where("updated_at > ?", Time.zone.parse("2023-12-18 00:00:00").to_i).to_sql => "SELECT `users`.* FROM `users` WHERE (updated_at > 1702825200)" # æ¥æ¬æé 2023-12-18 00:00 ã§ã pry(main)> Time.zone.at(1702825200) => Mon, 18 Dec 2023 00:00:00 JST +09:00
Integerã«å¤æãUnixTImeã¨ãã¦æ±ããã¨ãã§ããããã«ãªãã¾ãã
pry(main)> User.where("updated_at > ?", Time.parse("2023-12-17 15:00:00").to_i).to_sql => "SELECT `users`.* FROM `users` WHERE (updated_at > 1702825200)"
ä¸è¨ã®ããã«ãTime.parse ã§UTCæéã®ã¾ã¾Timeãªãã¸ã§ã¯ãã to_i ããæã¨åã1702825200
ã§æ¤ç´¢ããã¦ãã¾ãã
ãã®ãããã¿ã¤ã ã¾ã¼ã³ãæ°ã«ãããã¨ãªãæ¤ç´¢ãããã¨ãã§ãã¾ãã