From 94da0367e51441f98686860153515c340c6add36 Mon Sep 17 00:00:00 2001 From: fumimowdan Date: Wed, 4 Oct 2023 15:35:58 +0100 Subject: [PATCH] Add ReportTemplate model Store future template in binary format in databse along with optional configuration for the code using that template. --- .rubocop.yml | 4 ++ app/models/report_template.rb | 22 +++++++++ config/locales/en.yml | 12 +++++ .../20231004134346_create_report_templates.rb | 14 ++++++ db/schema.rb | 12 ++++- spec/factories/report_templates.rb | 39 ++++++++++++++++ spec/fixtures/test_homeoffice_template.xlsx | Bin 0 -> 6202 bytes spec/models/report_template_spec.rb | 42 ++++++++++++++++++ 8 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 app/models/report_template.rb create mode 100644 db/migrate/20231004134346_create_report_templates.rb create mode 100644 spec/factories/report_templates.rb create mode 100644 spec/fixtures/test_homeoffice_template.xlsx create mode 100644 spec/models/report_template_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index 02fd138c..8d14a5d2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -106,3 +106,7 @@ RSpec/DescribeClass: RSpec/AnyInstance: Exclude: - 'spec/requests/submission_spec.rb' + +Rails/UniqueValidationWithoutIndex: + Exclude: + - 'app/models/report_template.rb' diff --git a/app/models/report_template.rb b/app/models/report_template.rb new file mode 100644 index 00000000..7a74fdcf --- /dev/null +++ b/app/models/report_template.rb @@ -0,0 +1,22 @@ +# == Schema Information +# +# Table name: report_templates +# +# id :bigint not null, primary key +# config :jsonb +# file :binary +# filename :string +# report_class :string +# created_at :datetime not null +# updated_at :datetime not null +# +class ReportTemplate < ApplicationRecord + validates(:file, presence: true) + validates(:filename, presence: true) + validates(:report_class, presence: true, uniqueness: true) + validates(:config, presence: true) + + validate do |record| + HomeOfficeReportConfigValidator.new(record).validate + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index b7f6cb3d..9911cdf7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -48,3 +48,15 @@ en: success: 'User was successfully removed.' omniauth_callbacks: no_account: 'You have not yet an account!' + + activerecord: + errors: + models: + report_template: + attributes: + file: + invalid: "File parsing error" + config: + missing_header_mappings: "config.header_mappings must be present" + missing_worksheet_name: "config.worksheet_name must be present" + invalid_worksheet_name: "config.worksheet_name not present in file" diff --git a/db/migrate/20231004134346_create_report_templates.rb b/db/migrate/20231004134346_create_report_templates.rb new file mode 100644 index 00000000..3da13834 --- /dev/null +++ b/db/migrate/20231004134346_create_report_templates.rb @@ -0,0 +1,14 @@ +class CreateReportTemplates < ActiveRecord::Migration[7.0] + def change + create_table :report_templates do |t| + t.string :report_class + t.string :filename + t.binary :file + t.jsonb :config + + t.timestamps + end + + add_index :report_templates, :report_class + end +end diff --git a/db/schema.rb b/db/schema.rb index 2a356c60..5565d1f1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_09_15_100841) do +ActiveRecord::Schema[7.0].define(version: 2023_10_04_134346) do # These are extensions that must be enabled in order to support this database enable_extension "citext" enable_extension "plpgsql" @@ -166,6 +166,16 @@ t.index ["application_id"], name: "index_qa_statuses_on_application_id" end + create_table "report_templates", force: :cascade do |t| + t.string "report_class" + t.string "filename" + t.binary "file" + t.jsonb "config" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["report_class"], name: "index_report_templates_on_report_class" + end + create_table "schools", force: :cascade do |t| t.string "name" t.string "headteacher_name" diff --git a/spec/factories/report_templates.rb b/spec/factories/report_templates.rb new file mode 100644 index 00000000..897eb4a8 --- /dev/null +++ b/spec/factories/report_templates.rb @@ -0,0 +1,39 @@ +# == Schema Information +# +# Table name: report_templates +# +# id :bigint not null, primary key +# config :jsonb +# file :binary +# filename :string +# report_class :string +# created_at :datetime not null +# updated_at :datetime not null +# +FactoryBot.define do + factory :report_template do + file { StringIO.new("") } + filename { "file.xlsx" } + report_class { "SomeClass" } + config do + { + "some" => true, + } + end + + factory :home_office_report_template do + file { Rails.root.join("spec/fixtures/test_homeoffice_template.xlsx").read } + filename { "test_homeoffice_template.xlsx" } + report_class { "Reports::HomeOffice" } + config do + { + "worksheet_name" => "Data", + "header_mappings" => { + "Column A" => %w[urn], + "bar" => %w[applicants.given_name applicants.family_name], + }, + } + end + end + end +end diff --git a/spec/fixtures/test_homeoffice_template.xlsx b/spec/fixtures/test_homeoffice_template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c99d75b4b8511c05ccb283a8cb065c27a9c5a30a GIT binary patch literal 6202 zcmbVQby!sGwja711O!21K)OS65CQ3sZh@g;=#-G|lA)wSR6t5%XbGjIQ|XrO<_>=6 z9{D`?e1F`vpFOkp^X#?ue%Je3@mnhLNXWzhbaZq;$1^D{z#YMYUmH1rZJjyTZ+}Y? zJ}b3xVu$Q_M=+o8{Q#loyscMwR!_rA)k1GiQ5Bxo9=Lz}l!y>Jx}q({(bwri~S8T<~X_8Q>b0o zuDwXJbD4ohw>u?g(&z($D)Hp3$QyGNWs$0ky+c~{kFi{NBpIdKGVVSEkCQ&N>=|=! z+PXii27a+6RH9pSXe5!U-W&A8ipiOxCpB3w)8~&tnxDVB;qtw zQ4%#GR){eS$OSu{<@fe11<@AbUP$2cmpQbeMQ%5As#w?E&`2?4@DSecA;XqwJlgV7e6Fn7<#H9syRKq!2xRk#3<)_w?^sc&6RbMd z7m^c`s`C5VyR-D^6yp{{`xzhY(ct(gLi#5R;rxXmXBSUfu=6c~wzLhDr#XpzW=qR1 z#S)U#jO7B!o)df+W(& z*^Al4QC`VnGKW1@144J{pX=gd<@6&xx0X{|d=`MV$eE5Bz!|>yjJ-o&JCLv{@t&;< z{(f5xamzX~9fx~?TG>8S?gPeG2}+b`5G7ddqbtJ%lA~GXAl>{!!a5gO5MIYnKk#|AqFRRn%qZ}k1(R@^uD^M20hI<3b%suC& zi@ICFwTqAybYt|USlo%U#d6YTYx^5}#vs%IYs}M|ygFzO57SS~H=t?$iNMm8?r`nK ze4JgF5;-V2eOYaQG<;lFCvD<r{;}skpy{wYJ)c-x~dKF^a*OQ&NrpGiZE${J??1ZvlHuh+@yeI^+QI|ciKENVReX7VB*!}Zxxx5@(csOe6j zDj-dZjWLhNuO!{d`G{4c6MNKg`Wc_Yd}I$lW;=-Ho7S^E>wK`g*QmHfk1{EyE}SR| z2n`q=^28*@RkRStUz93tNJ@C9dV$Xp$Gt?heEq`@<7UZ@Qj}J6I$c)H(kmgRe|-M@ zdC~ZOgsAZYGa87Tp*V(cyEkON_I;^JXRA$7YF1j36eU*$RW_K1BI`|2{MC%QDL-)! zQfyZJP_bmaN`@ZrSuiA^KEXqg@xar|i$9~!JL4U5R*Q|&_GHl8^Y{;gI=WZo1m~Ez z57)0imbQ85ioRXS$hu<5z2#u@T{2a+hM_7~Z9a}8nvm0iIZE{%%b9DOoR*LejH%U4 z;&n0Kd<2bjC~}9u@?&k>eF;chee1D;uIUsBQ|Qb5LblQG@|6l?sfE17U5=h(MQe^* z0PpXGb#uXTHIU8cmQLOiojJ=4^{!f&9|s`z3|y=)9d5eJ;I=#DCt+N8hzW0r=TG52 z=sILkCP%dgJi`C6mVQ{DGd2Rf->WI2W|R5aVGH@x9#=TeChvISM3K@nEE>a6kZRNb z!&-7utspAftc}XB{eCo$f=craea{XD@r@?tcxg^p^|s}P^Tsuarf-g(crE5jebWr; znxA!362r<>!AH*zhSl_~%fn_*pVr;SPNJ;xAE3{d${Q21Bz9CKR%cvByarYBKm>0* z$h=%rX;%Au^_onezRj4krA^~&GqZ!bRfBwXeR{mKk+LrV(%zg%&*eQqmso= z#GUaxEtMtCgdo~RLV=4S>k~KAhSzwv;e1cepe-C;o6gbxAK}dX_i%oA8_r;ut36bM zzTZplVM(Ci55x=U$V922g)(}uz}fi&HLdYTsWRWP4)TrOR36eoYCp|{O;|bN2(28$ zaF_!WiO9MptV#6ajN#LEp$}xx>{D8=75?*-t0bxxk;w#S8GLv+s|yvDE=;TdOW$Sq zuV9Lzq(lgbq#4*&%_jyYGn0RNJ6u_t`$P(-73ztDN*vsI#tdONj8uG(NHB5JsQja*6yL%<4tv!qfrqEy)s#Syz-yg!a#^v?6cksk6` z%6H9Y^O;CrAIT$~Hqr#g#k}EYDPu>0Cgmv)KbUnmQABI@4)W%8*%p*vJYHr6BLWY> zj3K0vYeFzxhHL5C-Q+nmVK0X2pf=32(S#{ncCs7ZfFBqggBuDz?o-c|A)jFcxAfl^ zzFv8!ZNX`xMcXE9SW7iDxD_IwDcm2^T5Q9})x83n3fLqdNu=SA?NU{mc}wwNKcCc|s?sb7%#+R59U;^lsqc0o=y532E)%6~YTVlS3cNH1z(#aNCP z0m=3dyH#QB&@X-O_8d8cFDv8LnLlDxG`Z@Ynx72ZDZ0pO$S1S*$1G^!g zb4G4fnA%2g0Vw_y8H>JONA3&jIad;JOw1%d(ifKUrET3_*DV)4ZX_kZE9a-d4Gi>R zh3T4BYunLrPL_QmjYnO?CK2S(lRYT3>1axBJ^C3ncT80hL&<1~0lhIT^F1AnudUH# z&2`VzE!;5flE9+sj+x{;D}6E+6rKxOhU#IMhN?7r_DxV<uUyX55;*=tPrW(_BMp*ud9c^3=x6sA#v zjfoPadMAJ{stW_{{0YZ}5b@Y(wqP~4N5bS?$0rvc>#?1Ke8YH;sLvpc#B9#miRc%c zyA9s8b%Cz+)>Mua30Y;-L`9d3We6Mz+g0YPj@A0@K{T_6@#ZYBdMpn)2*@eg;81;-f4d9+L*6CU9B5CR^!wZDlr01|)wmt=4 zf5kr%JK}#e<(4m;z-G@~oUH6Ee)Z- z*Ce^_AWdQ1#e2{JWazhiS+pFZVP!RCU+V{X_4)1g=Nj+r$D$`i>zneLpK+r|s=D#R zO^E#$Txrx(b5=)b8>C;kDv%U^AThJVyVtiLnP}5(gB1IA%s3rD;Xwyx8YiC;EM)qU z1OahVcn0q;)$u1h##0RETg)m(771TuVf|bl@Hj-TO(iyF%~)r5Pz|CuN={5Ei9(oI zner5wx(YYylLfU)Z@3y}&A5Z6P+Z=5PlW0Oui^-O#>k2C?$7=C_Y{1@9}Gy)5(`76 z9|^Qj62B72_JO_fl(pZ{#7b0NkR?nIj4EUy98MW9J5O3L+R2OdD zz(~S_*Ny2eQZf^6b~QWHEe7I4=r1>ein5jQ=Cf{6{E%)pXiv}eiwS&lrvDM7_;)wx zg@ePdiX5j1X}z7)T##tASIII_JWNEjOLKbyZ)~PYx(P|Sm*r(6x|@vvID+38FRG3v zeAm5r>jXhz-IJ(_j=`8^t+HTuoO9dRZXmPCf~M0JEAJXQLCGZ26?(tqGfb0s41Rvg ztOO$h!VW6zSiu^*?L&65Oeb=;_M=9^-HmqR76vt~(e##-7P+{BX2ikZTA}=#Cj~@> z7Yo&*=e?$%P!{==TOPp*O62uxx&cL%FWJ^APjoRdE9h+Vnvfc@wBk@Q=TSZ~Qh-Ct zFLe9s&rBT!29`;ELG;{33><+&6}{iRC&C*Vwfo*G*LFKnoen>O!Mc5a#WovzJl+}H zDQW+RYofbuGj(-#f!O`>Tz@%cD<^UA{EtHP$!T@8_@Pgcg2L)2aWmBj&X7*UNFQ&y zfbE!2YB8l@(JRSf{7C|97zYazfDH$ikqvTHASnB}YtEP|D0>-|wcc%9pAIFg3n7yA zq0Qq$V(Ws5{yCiF$ENIbQV_LFM>XD<{?^pZ1%%f^>Z7WY!9-`YFCU-nZSY;m|Mb2E=KB}4q#`4+jVtl2rIX7 z;?G>L(3U=On?|5X{Dc%sz^rO+wkdCNU)-#|;Ob-oL!BfDG>Xo^(sW@sww};eQ=!Cf zoH4*GLnI=N@#rl~1sp?_T(gdn_Dy=y$)W|AqXnbXn0X`rgt7>v7FvZs!W>+kaK39L zZ}LcQkg(H5M2t~^1+TkM%qd&P4F|5fwMb&BaSn55cs;TwocX(jJqI@EY z95>OH=!LBB1v~u?Ab!!0gkLgzN{!XeWI>%NtTXO(hS)BWX-`_dhptu!NLFNz&8m{$ z?|iQCWVgk$F0q==bJXpq_z3ev+&eUl9rLrlO?47NKt?$TRs?7$c62t84L+BcJu|?u zw>%0tB<-T`r|aV)pJ>m#c}6|#w?7#3V_xKP5WOx!cMfkRM3m>0p3>>$4E;2C^h+=f zc;17E8b&9?_X@q3^op)l3wwmIFr?O5aM!eIa>|oUISFFb6cNikbjfB8x!ozERV)@O zIQsNaZ+8j-kr?oMzIu10cspPHyWE+!{;7C(j&wV5{4I>|SN$=6{8RZZXTKda{T3T| zp8!|>J$U+4^)5fSCH22W1irj~z4t#T{-0Lvwj_VIf`$2iTKUB+{