
é¢æ°åããã°ã©ãã³ã°èšèªElixirã人æ°ãéããŠãããåäžããŒãžã¢ããªã±ãŒã·ã§ã³ãäœæããããã®ææ°ã®ãã¬ãŒã ã¯ãŒã¯ã®1ã€ã§ããAngular 2ãæè¿ãªãªãŒã¹ãããŸããã ããããAngular 2ããŒã¹ã®ããã³ããšã³ãã¯ã©ã€ã¢ã³ãã¢ããªã±ãŒã·ã§ã³ã«ããŒã¿ãæäŸããElixirããã³Phoenix Frameworkã®å®å
šãªããã¯ãšã³ããæåããäœæããããã€ãã®èšäºã§ããããç解ããŸãããã
Hello, world
ã¯ç§ãã¡ã®ãªãã·ã§ã³ã§ã¯ãªãã®ã§ãå¿
èŠã«å¿ããŠå®éã®ãããžã§ã¯ãã«ããªãããã£ãããšãé©çšã§ããŸãïŒæ瀺ããããã¹ãŠã®ã³ãŒãã¯MITã©ã€ã»ã³ã¹ã®äžã§ã¬ã€ã¢ãŠããããŸãã
èšäºã®ããªã¥ãŒã 倧ãã ã§ããïŒ åæ§ã«èšå€§ãªæ°ã®ã³ã¡ã³ããããã°ããã®ã§ããã ç§ã¯ãããªããã³ã¡ã³ãããããããŠã¡ã€ã³èšäºããããå€ããåŸãããšã«äœåºŠãæ°ã¥ããŸããã
æåã®èšäºã«ã¯ããã€ãã®çŽ¹ä»çãªèšèããããããã¯ãšã³ãã§åäœããŸãã è¡ããïŒ
ã¯ããã«
æ°ã¶æåãç§ã¯éåžžã«çãæéã§ãããã¿ã€ãã®Webã¢ããªã±ãŒã·ã§ã³ãå®è£
ããããã«äžè«ãæ¥è
ãšããŠç³ãåºãããŸããã èŠä»¶ã®ãã¡ãæ©èœã®ã¿ãçµäºæ¥ãããã³ãªãŒãã³ãœãŒã¹ããŒã«ã®ã¿ã䜿çšããå¿
èŠæ§ããããŸããã ããã°ãããããšæã£ãã®ã¯ãããããElixir / Phoenix FrameworkãšAngular 2ãã³ãã«ãå®è·µãã倧ããªçç±ã ããšãåŸè
ã¯å°ãåã«ãªãªãŒã¹ãããŸããã ãã®çµæããããžã§ã¯ãã¯æééãã«å®äºãã顧客ã¯æºè¶³ããæ°ããã¿ã¹ã¯ã®å®è£
ã§çµéšãè£å
ãããŸããã
ãããã®ã¿ã¹ã¯ã®1ã€ã¯ãè€æ°ã®å€ãéžæããæ©èœãåããGRNTIããã³OECD FOSãã£ã¬ã¯ããªã衚瀺ããå¿
èŠæ§ã§ããã éåžžã®æºåãæŽã£ãããªãŒã®ãããªåèæžã衚瀺ããããã®æ¢è£œã®ãœãªã¥ãŒã·ã§ã³ããªãã£ããããèªè»¢è»ãäœãçŽãå¿
èŠããããŸããã ããã«ã Elixir / Phoenix FrameworkãšAngular 2ã®äž¡æ¹ãåæã«æ¢çŽ¢ããããã®ããã®äžé£ã®ããã¬ãŒãã³ã°ãèšäºã®ããŒããæäŸããŸããã
ãããã£ãŠããã®ãµã€ã¯ã«ã®çµããã«ãElixirããã³Phoenixãã¬ãŒã ã¯ãŒã¯ã®äœæ¥ããã¯ãšã³ãããããAPIã䜿çšããŠSRSTIããã³OECD FOSãã£ã¬ã¯ããªã®ã³ã³ãã³ããAngular 2ã®ç¬ç«ããããã³ããšã³ãã«éä¿¡ããŸããã»ã¯ã·ã§ã³\ãµãã»ã¯ã·ã§ã³ãä¿åæã«éžæãŠã£ã³ããŠã®å€ã«ç§»åããéžæãããã®ãéããŠåŸ©å
ããŸãã å€èŠ³ã¯Twitter BootstrapãæäŸããŸãã ããã³ããšã³ãã§ã®ãã£ã¬ã¯ããªã®å®è£
ãåå¥ã®ã¢ãžã¥ãŒã«ãšããŠé
眮ããŸããããã¯ãå°æ¥ã©ã®ãããžã§ã¯ãã§ã䜿çšã§ããŸãã
ããã€ãã®å®è£
ããŒã
SRSTIãã£ã¬ã¯ããªã¯3ã¬ãã«ïŒæ倧ïŒæ§é ã§ãåãšã³ããªã¯10é²åé¡ã®ã³ãŒããæã¡ã00ãã99ãŸã§ã®æ°åã®3ã€ã®ã°ã«ãŒãã§æ§æãããããããšååã§åºåãããŠããŸãã ãªãã¡ã¬ã³ã¹ããã¯ã«ã¯çŸåšãçŽ8,000ã®ã»ã¯ã·ã§ã³ãšãµãã»ã¯ã·ã§ã³ã®ã¬ã³ãŒããå«ãŸããŠããããã©ããããã¹ã圢åŒã®ããªã¥ãŒã ã¯400 kb以äžã§ãã ããã¥ã¢ã«ã®å
容ã¯grnti.ruã«ãããŸã ïŒãã®ãªãœãŒã¹ãšã¯é¢ä¿ãããŸããïŒã
OECD FOSã¯ããããã§åºåãããéå±€ã³ãŒããæã€3ã¬ãã«ã®æ§é ãæã£ãŠããŸããã以åã®ããŒãžã§ã³ãšã¯ç°ãªãããã®å Žåãã³ãŒãã®æåŸã®ã°ã«ãŒãã¯2ã€ã®ã©ãã³æåã®çµã¿åããã§ãã ãã£ã¬ã¯ããªå
ã®ãšã³ããªã¯å€§å¹
ã«å°ãªãã300ãå°ãäžåããåèšããªã¥ãŒã ã¯çŽ8kbã§ãã æ®å¿µãªããããã®ã¬ã€ãã®é¢é£ããŒãžã§ã³ããªã³ã©ã€ã³ã§èŠã€ããããšãã§ããªãã£ããããä»ã®ãã£ãã«ã§èŠã€ãã£ããã®ã䜿çšããŸãã
SRSTIãã£ã¬ã¯ããªãŒã®ããªã¥ãŒã ã®ãããããã䜿çšããå ŽåãçŸæç¹ã§å¿
èŠãªã»ã¯ã·ã§ã³ã®ã¿ãããã¯ãšã³ãããèŠæ±ããŸãããOECD FOSå
šäœãæå®ã§ããæ§é ã¯ã¯ã©ã€ã¢ã³ãäžã§ãã§ã«åŠçã§ããŸãã
ããã«æ確ã«ãããïŒã¿ã¹ã¯ã¯ããéåããŠããŠãããã¯ããåºãæ©èœã®äžéšã«ãããŸããã§ããã åœç¶ã®ããšãªãããã¿ã¹ã¯ããã£ã¬ã¯ããªã®åºåã®ã¿ãç®çãšããå ŽåïŒæ
å ±æäŸãªã©ïŒãããã¯ãšã³ããSPAãå¿
èŠãããŸããã
ãŸããç§ã¯ç¬¬äžäººè
ã®ãµããããããšã¯äžåãããŸãããããããªããäœããã®éšåã®ããå¹æçãªå®è£
ãææ¡ãããªããç§åŠã«æè¬ããŸããç§ã¯åŠç¿ã倧奜ãã§ãã
泚æïŒã³ãŒãããŠãŒãã£ãªãã£ã®åºåãããã³ããã€ãã®é«åºŠãªèª¬æã¯ãå°ãªããšãäœããã®æ¹æ³ã§èªã¿åããšäœ¿çšã®å©äŸ¿æ§ãé«ããããã«ãã¹ãã€ã©ãŒã®äžã«é ãããŠããŸãã
æè¡ã¹ã¿ãã¯ã®éžæ
çŸåšãåçŽæ¹åã®é»åå¢å ã¯ããé«äŸ¡ã«ãªã£ãŠãããããã©ãŒãã³ã¹ã¯åšæ³¢æ°ãäžããããšã§ã¯ãªããæ°Žå¹³ã«ãæ°ããã³ã³ãã¥ãŒãã£ã³ã°ã³ã¢ãè¿œå ããããšã§éæãããŠããŸãã ãã®ããã競äºåã®ããã³ã³ãã¥ãŒãã£ã³ã°ïŒäžŠåå®è¡ïŒã«ç¹åããèšèªã®é¢å¿ãé«ãŸã£ãŠããŸãã åæã«ãå
±æããŒã¿ãžã®å
±æã¢ã¯ã»ã¹ã¯æ·±å»ãªé çã®çš®ã«ãªãã é¢æ°åããã°ã©ãã³ã°èšèªã§ã¯å€§å¹
ã«åæžã§ããŸãã
Elixirã¯ãErlangã§èšè¿°ããã Beamä»®æ³ãã·ã³ã§å®è¡ããããããªãè¥ããã€ãã³ãŒãã³ã³ãã€ã«ãããé¢æ°åèšèªã§ãã ãã®èšèªã¯ã Erlangã®ãã¹ãŠã®å©ç¹ãç¶æ¿ããŠããŸãã
- æ©èœæ§
- å
ç«
- ããã¹ãŠãããã»ã¹ã
- ããã»ã¹ã¯äºãã«åé¢ãããŠããã
- ããã»ã¹ã®äœæãšç Žå£ã®éåžžã«äœãã³ã¹ãã
- åããã»ã¹ã«ã¯äžæã®èå¥åïŒPIDïŒãããããªãã·ã§ã³ã§äžæã®ååãå²ãåœãŠãããšãã§ããŸãã
- ããã»ã¹éã§ãªãœãŒã¹ãå
±æããããšã¯ãããŸããã
- ååãšèå¥åãããã£ãŠããå Žåã¯ãä»»æã®ããã»ã¹ããä»»æã®ããã»ã¹ã«ã¡ãã»ãŒãžãéä¿¡ã§ããŸãã
- ã¡ãã»ãŒãžã³ã°ã¯ââããã»ã¹ééä¿¡ã®å¯äžã®æ¹æ³ã§ããã
- ãã¿ãŒã³ãããã³ã°
- ãå¿
èŠãªããšãè¡ãããŸãã¯åã«æ»ã¬ããšããã€ããªãã®ãŒ
- åæ£ã·ã¹ãã ã®äœæã®å®¹æãã
åæã«ãRubyã«ãã䌌ãã·ã³ãã«ãªæ§æããããã³ã«ã¡ã«ããºã ã«ããããªã¢ãŒãã£ãºã ãéåžžã«è±å¯ãªã¡ã¿ããã°ã©ãã³ã°æ©èœãMarkdownããŒã¯ã¢ããã䜿çšããŠããã¥ã¡ã³ããç°¡åã«äœæããæ©èœã ããã³ã¢ãžã¥ãŒã«ã³ãŒãã§ã®å®éã®ãã¹ãïŒ!!!ïŒ ã Erlangã®ãã¹ãŠã®æ©èœãšãErlangåãã«äœæãããã©ã€ãã©ãªã¯ãããã©ãŒãã³ã¹ãæãªãããšãªãElixirã³ãŒãããçŽæ¥åŒã³åºãããšãã§ããããšãéèŠã§ãã
ãã®èšèªã¯ãã¡ãã»ãŒãžã³ã°ãåããæ¬æ Œçãªããã»ã¹ã¢ãžã¥ãŒã«ãæžãã®ã«æåéãæ°åãããããããã«ãããã»ãã·ã³ã°ãšã¡ãã»ãŒãžã³ã°ã®äœ¿çšãåŒãèµ·ãããŸãïŒãã®ãµã€ã¯ã«ãžã®åå¿ãè¯å®çãªå Žåã以äžã®åºçç©ã§ããã«æ»ãããšãæã¿ãŸãïŒã
èšèªã®äœè
ã§ããJoséValimãã³ãã¥ããã£ã®ç掻ã«ç©æ¥µçã«åå ããããšã倧ããªæå³ããããŸãã 圌ã¯åãã§è©³çŽ°ã«è³ªåã«çããå¿
èŠã«å¿ããŠãäžè¶³ããŠããæ©èœãèšèª/ã©ã€ãã©ãªã«å°å
¥ããŸãïŒããšãã°ãåŸã§èª¬æããEctoã§-å人çãªååããªçµéšããããŸãïŒã
Phoenix Frameworkã¯Elixirã§æã人æ°ã®ããWebãã¬ãŒã ã¯ãŒã¯ã§ãããMVCãã¿ãŒã³ãå®è£
ãã Webã¢ããªã±ãŒã·ã§ã³ã®éçºã倧å¹
ã«ç°¡çŽ åããŸãã ããã«ãPhoenixã«ã¯ãã£ãã«ããããŸããããã¯ãWebãœã±ãããä»ããã¢ããªã±ãŒã·ã§ã³ãšã®ãªã¢ã«ã¿ã€ã éä¿¡ã®å¯èœæ§ã§ãããããã¯æ¬åœã«çŽ æŽãããæ©èœã§ãã ãã©ãŠã¶ã§äœ¿çšããããã®JavaScriptã³ã³ããŒãã³ããšãAndroidåãã®Javaãªã©ã®ä»ã®èšèªã®å®è£
ããããŸãã
Angular 2ã¯ãäž»ã«GoogleããµããŒãããåäžããŒãžWebã¢ããªã±ãŒã·ã§ã³ã®ã¯ã©ã€ã¢ã³ãåŽãéçºããããã®ãã¬ãŒã ã¯ãŒã¯ã§ãã ããŒãžã§ã³2ã¯ãAngularJSã®éçºããã³éçšäžã«åŸãããçµéšã«åºã¥ããŠå®å
šã«æžãçŽãããŸããã ãã®ãªãªãŒã¹ã¯2016幎9æã«ãªãªãŒã¹ãããŸããã
Elixirããã³Phoenixãã¬ãŒã ã¯ãŒã¯ã®ããã¯ãšã³ã
ãšãªã¯ã·ãŒã«ã«ã€ããŠã®ããã€ãã®èšèElixirã«åºäŒã£ãããšããªãå Žåã¯ãèšèªã®åŠç¿ããå§ããããšã匷ããå§ãããŸãã 䜿çšããã¢ãããŒããšæ©èœã«ã€ããŠè©³çŽ°ã«èª¬æããäºå®ã§ãããå°ããªã·ãªãŒãºã®èšäºã®æ çµã¿å
ã§ãã¹ãŠã網çŸ
ããããšã¯äžå¯èœã§ãïŒããã¯ãç§èªèº«ãåžžã«æ°ãããã®ãèŠã€ããŠãããšããäºå®ãæ°ããŠããããã§ã¯ãããŸããïŒã åŠç¿ã®ããã«ããããžã§ã¯ãWebãµã€ãã®åºæ¬çãªçŽ¹ä»ãšãã€ã³ã¿ãŒãããäžã®ä»ã®å€ãã®ãªãœãŒã¹ããããŸãã èšèªã®äœæè
ã倧ããªåã³ã§çãããã©ãŒã©ã ã¯éåžžã«äŸ¿å©ã§ãã ã¡ãªã¿ã«ãRedditã®ãã®ã¹ã¬ããã«ã¯ãããšãªã¯ã·ãŒã«ãæåã«å匷ããããããã«å®¹å§¿ã«çªå
¥ãã䟡å€ãããã ããšãã質åã«å¯ŸããåªããçãããããŸãã èŠçŽãããšããã®ãããã¯ã®äœè
ã¯ãRubyãšãon Elixirãã§æžããããã¹ãã±ãŒã¹ã®ããã©ãŒãã³ã¹ã®ããããªéãã«å€±æãããšèšããŸãã Rubyã®ã³ãŒãã¯ã4.221ç§ãElixirã§ã¯5.923ç§ã§å®è¡ãããŸããã èšèªã®æ©èœã䜿çšããŠïŒRubyãš1察1ã§ç§»æ€ããã ãã§ãªãïŒã³ãŒããæžãçŽããåŸã3åïŒ!!!ïŒéãåäœãå§ããŸããã
æ£ããããšãèšã£ãŠãç§ã¯å·éã«èšããŸããç§ã¯èªåã§ãããããããšã¯ãã£ãã«ãªããéåžžã¯ããã«æŠéã«çªå
¥ããŸãã
ããŒã«ãããã®ã€ã³ã¹ããŒã«ãšãããžã§ã¯ãã®éå§
ErlangãElixirãããã³ããŒââãã®ããŒãžã§ã³ïŒåŸã§ããã³ããšã³ããæäœããããã«å¿
èŠã«ãªããŸãïŒãå¶åŸ¡ããã«ã¯ã asdfããã±ãŒãžãããŒãžã£ãŒã䜿çšããŸãã FedoraãšUbuntuãasdfãErlangãElixirã®äŸåé¢ä¿ã®ã€ã³ã¹ããŒã«ã«ã€ããŠè©³ãã説æããåªããèŠç¹ãããã®ã§ãç¹°ãè¿ããŸããã 圌ã¯è±èªã§ãããååãªã³ããŒã¢ã³ãããŒã¹ãããããŸãã å·çæç¹ã®ææ°ããŒãžã§ã³ïŒErlang-19.2ãElixir-1.4.1ã
ãŸããPostgreSQLã®ææ°ããŒãžã§ã³ïŒçŸæç¹ã§ã¯9.6ã䜿çšïŒãå¿
èŠã§ããããã¯ããã£ã¹ããªãã¥ãŒã·ã§ã³\ OSã®æšæºããã±ãŒãžãããŒãžã£ãŒã䜿çšããŠã€ã³ã¹ããŒã«ã§ããŸãã
ErlangãšElixirãã€ã³ã¹ããŒã«ãããã Phoenix Frameworkãã€ã³ã¹ããŒã«ããå¿
èŠããããŸãã
ãããžã§ã¯ãã®äœæãã³ã³ãã€ã«ããã¹ããããã³äŸåé¢ä¿ã®ç®¡çã®ããã®Elixirã«ã¯ãç¹å¥ãªèªååãŠãŒãã£ãªãã£-Mix ïŒããã¥ã¡ã³ãã®ãã®éšåã«ã泚æãæãå¿
èŠããããŸãïŒããããŸãã mix
ãŠãŒãã£ãªãã£-makeã«äŒŒmake
ããããã䟿å©ã§ãã
ãŸãããã®å©ããåããŠã次ã®ã³ãã³ãã§ïŒæ¬¡ã®ïŒ Hexããã±ãŒãžãããŒãžã£ãŒãã€ã³ã¹ããŒã«ããŸãã
$ mix local.hex
次ã«ãPhoenix Frameworkã¢ãŒã«ã€ãïŒ
$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez
ããã¥ã¡ã³ãã«ã¯node.jsã®å¿
èŠæ§ãèšèŒãããŠããŸããããã®å ŽåãPhoenixã¯APIã®ã¿ãæäŸãããããAngular 2ã«é²ããšãã«ããŒããå¿
èŠã«ãªããŸãã
ãã®èšäºã®å·çæç¹ã§ã¯ãPhoenix FrameworkããŒãžã§ã³1.2.1ãé¢é£ããŠããŸããã
Hexã«ã€ããŠäžèšè©±ã䟡å€ã¯ãããŸãã Elixir / Erlangã®ã©ã€ãã©ãªãå
¬éããåäžã®ãªããžããªhttps://hex.pmããããŸãã ããã©ã«ãã§ã¯ãElixirã®ãã¹ãŠã®ãããžã§ã¯ãäŸåé¢ä¿ãããã§æ€çŽ¢ãããŸãã
å¿
èŠãªãœãããŠã§ã¢ãã€ã³ã¹ããŒã«ããããå¿
èŠãªãã£ã¬ã¯ããªã«ç§»åãã次ãå®è¡ããŠæ°ããPhoenixãããžã§ã¯ããäœæããŸãã
mix phoenix.new atv_api --no-brunch --no-html $ mix phoenix.new atv_api --no-brunch --no-html * creating atv_api/config/config.exs * creating atv_api/config/dev.exs * creating atv_api/config/prod.exs ... * creating atv_api/priv/static/images/phoenix.png * creating atv_api/priv/static/favicon.ico Fetch and install dependencies? [Yn] y * running mix deps.get We are all set! Run your Phoenix application: $ cd atv_api $ mix phoenix.server You can also run your app inside IEx (Interactive Elixir) as: $ iex -S mix phoenix.server Before moving on, configure your database in config/dev.exs and run: $ mix ecto.create $
ã¯ã©ã€ã¢ã³ãéšåã¯å¥ã®ãããžã§ã¯ãã«ãªããããhtmlãã³ãã¬ãŒããšãã©ã³ããµããŒããªãã§æ°ãããããžã§ã¯ããäœæããŠããŸãã
æ§æãã¡ã€ã«config/prod.exs
ã config/dev.exs
ãããã³config/test.exs
ã®ããŒã¿ããŒã¹æ¥ç¶èšå®ããããããæ¬çªã¢ãŒããéçºã¢ãŒããããã³ãã¹ãã¢ãŒãã«å€æŽããããšããå§ãããŸãã
ããã«ãElixirããŒãžã§ã³1.4以éã䜿çšããŠããŠãçŸåšã®ããŒãžã§ã³ã®PhoenixããŸã 1.2.1ã§ããå Žåã mix.exs
ãã¡ã€ã«ãmix.exs
å€æŽããããšããå§ãããŸãã Elixir 1.4ã¯ããã€ãã®æ°æ©èœããããããŸãããç¹ã«ããããžã§ã¯ãã®éå§æã«éå§ããå¿
èŠãããç¬èªã®ããã»ã¹ããªãŒãæã€äŸåé¢ä¿ã®è¿œå ãç°¡çŽ åããŸããã 以åã«ãã®ãããªäŸåé¢ä¿ïŒããã³ãããã®ã»ãšãã©ïŒãäŸåé¢ä¿ã®ãªã¹ãïŒ deps
ïŒãšå®è¡ããã¢ããªã±ãŒã·ã§ã³ã®ãªã¹ãïŒ deps
ïŒã®äž¡æ¹ã«è¿œå ããå¿
èŠããã£ãå Žåãæåã¯ããã ãã§ååã§ãïŒ mix
ã¯äŸåé¢ä¿ãã¢ããªã±ãŒã·ã§ã³ã§ãããã©ãããå€å¥ããŠéå§ããŸãã äŸåé¢ä¿ã«ãªã¹ããããŠããªãã¢ããªã±ãŒã·ã§ã³ã®ã¿ãæå®ããå¿
èŠããããŸãã ã¢ããªã±ãŒã·ã§ã³ã®èª¬æãè¿ãã¡ãœããã次ã®åœ¢åŒã«ããŸãããã
# mix.exs ... # Configuration for the OTP application. # # Type `mix help compile.app` for more information. def application do [mod: {AtvApi, []}, extra_applications: [:logger]] end ...
以åã®ãã®ãšæ¯èŒãããšãããŒ:applications
ãªããªã£ããªã¹ããšæ°ãããªã¹ããè¿œå ãããããšãããããŸã:applications
:extra_applications
ãããã§ã¯:logger
ã®ã¿ãæ®ããäŸåé¢ä¿ã«ãªã¹ãããããã¹ãŠãé€å€ãããŸããã
ãããmix ecto.create
ã mix ecto.create
ã䜿çšããŠããŒã¿ããŒã¹ã®äœæãéå§ããŸãã ããã©ã«ãã®ç°å¢ã¯ããããdev
atv_api_dev
ããã®ç°å¢çšã®ããŒã¿ããŒã¹ãäœæããatv_api_dev
ãšatv_api_dev
ã
mix ecto.drop
ã¿ã¹ã¯ãå®è¡ããããšã§ããã€ã§ãããŒã¿ããŒã¹ãåé€ã§ããŸãã ãã®å Žåã mix ecto.reset
ã¯ããŒã¿ããŒã¹ãåé€ããæ°ããããŒã¿ããŒã¹ãäœæãã移è¡ãéå§ããåæããŒã¿å
¥åã®ããã«seeds.exs
ã®å
容ãå®è¡ããŸãïŒåŸè
ã«ã€ããŠã¯ä»¥äžã§è©³ãã説æããŸãïŒã
mix
åã«ãç®çã®å€ã§åæåããmix
å€æ°MIX_ENV=prod
ã MIX_ENV=dev
ïŒããã©ã«ãïŒãŸãã¯MIX_ENV=test
ããããšã«ãããé©åãªç°å¢ã§å¿
èŠãªã¿ã¹ã¯ãå®è¡ã§ããŸãã
OECD FOSãªãã¡ã¬ã³ã¹
OECD FOSã®ãªãã¡ã¬ã³ã¹ã¯ç°¡åãªã®ã§ãããããå§ããŸãããã
Phoenixã¯Ectoã©ã€ãã©ãªã䜿çšããŠããŒã¿ãåŠçããŸãã Ectoã¯ããã¥ãŒïŒã¢ãã«ïŒãä»ããŠããŒã¿ããŒã¹ããŒãã«ãæäœããããŒã¿ããŒã¹ã¯ãšãªãäœæããããã®DSLã§ãã Ectoã¯ãRails ActiveRecordsãšã¯ç°ãªããéåžžã«ã·ã³ãã«ã§ãïŒæäœéå¿
èŠïŒããåæã«åŒ·åãªããŒã«ã§ãã
ã³ãŒããžã§ãã¬ãŒã¿ãŒ
Phoenix Frameworkã«ã¯ã移è¡ã®å®å
šãªã»ãããã¢ãã«ã CRUDãå®è£
ããã³ã³ãããŒã©ãŒãjsonãçæãããã¥ãŒã¢ãžã¥ãŒã«ãããã³åºæ¬çãªãã¹ããäœæã§ããããŸããŸãªã¿ã€ãã®ã³ãŒããžã§ãã¬ãŒã¿ãŒããããŸãã äž¡æ¹ã®ãã£ã¬ã¯ããªã®å Žåãå®å
šãªCRUDã¯å¿
èŠãããŸããããOECD FOSã®å Žåã¯ãçæãããã³ãŒãããå§ããŠäœåãªéšåãåé€ã§ããŸãã
OECD FOSãã£ã¬ã¯ããªããŒãã«ã«ã¯ã id
ãštitle
2ã€ã®ãã£ãŒã«ãããããäž¡æ¹ãšãtext
ã¿ã€ãtext
ã ïŒãªãtext
ã§ããïŒ éãããªãå Žå ããªãã¹ãã¬ãŒããã®ã§ããïŒïŒ
ãžã§ãã¬ãŒã¿ãŒãšãã©ã€ãã䜿çšããŸãã
mix phoenix.gen.json Fos fos titleïŒtext $ mix phoenix.gen.json Fos fos title:text * creating web/controllers/fos_controller.ex * creating web/views/fos_view.ex * creating test/controllers/fos_controller_test.exs * creating web/views/changeset_view.ex * creating web/models/fos.ex * creating test/models/fos_test.exs * creating priv/repo/migrations/20170215194144_create_fos.exs Add the resource to your api scope in web/router.ex: resources "/fos", FosController, except: [:new, :edit] Remember to update your repository by running migrations: $ mix ecto.migrate
ããã§ã phoenix.gen.json
ã¯ãæ··åïŒæ··åã¿ã¹ã¯ïŒãŠãŒãã£ãªãã£ã®ã¿ã¹ã¯ã Fos
ã¯åæ°åœ¢ã®ã¢ãã«ã®ååã fos
ã¯ããŒãã«ã®ååã§ããæ
£äŸã«ãããè€æ°åœ¢ã®å°æåãšãã£ãŒã«ãã®èª¬æãå«ãã¢ãã«ã®ååãå¿
èŠã§ãã ãã®å Žåãã¢ãã«ã«title
ãšã¿ã€ãtext
ïŒããã¯PostgreSQLããŒã¿ã¿ã€ãïŒãšããååã®ãã£ãŒã«ããèŠãããšæãtext
ã id
ãã£ãŒã«ãã«ã€ããŠã¯åŸã»ã©èª¬æããŸãã mix help
ã³ãã³ããå®è¡ãããšãããã¯ã¹ã¿ã¹ã¯ã®ãªã¹ããååŸã§ããŸã ããã§ããã¯ã¹ã¿ã¹ã¯ã®è©³çŽ°ã«ã€ããŠã¯ã ããã¥ã¡ã³ããåç
§ããŠãã ãã ã
ã³ãã³ããå®äºãããšã resources "/fos", FosController, except: [:new, :edit]
web/router.ex
resources "/fos", FosController, except: [:new, :edit]
ãresources "/fos", FosController, except: [:new, :edit]
è¡resources "/fos", FosController, except: [:new, :edit]
web/router.ex
ã«è¿œå ããããã«æ±ãããweb/router.ex
ã ãšãããããã£ãŠã¿ãŸãããïŒããã¯åŸã§å€æŽããŸãïŒïŒ
web / router.ex defmodule AtvApi.Router do use AtvApi.Web, :router pipeline :api do plug :accepts, ["json"] end scope "/api", AtvApi do pipe_through :api resources "/fos", FosController, except: [:new, :edit] end end
ãŸãã移è¡ããã»ã¹ãéå§ããããæ±ããããŸãããæ¥ããªãã§ãã ããã ããã©ã«ãã§ã¯ãEctoã¯integer
åã®èªåã€ã³ã¯ãªã¡ã³ãid
ãã£ãŒã«ããäž»ããŒãšããŠã¢ãã«ãšç§»è¡ïŒããŒã¿ããŒã¹ã«ããŒãã«ãäœæããã¹ã¯ãªããïŒãçæããŸãããããŒãã£ã·ã§ã³ã³ãŒããããŒãšããŠäœ¿çšããããããã®ã¿ã€ãã®ãã£ãŒã«ãã¯å¿
èŠãããŸããã ã¢ãã«ã®ãã®åäœãå€æŽããŸãã
移è¡ãã¡ã€ã«ããå§ããŸãããã ã¢ãã«ãžã§ãã¬ãŒã¿ãŒã¯ã priv/repo/migrations
ãã£ã¬ã¯ããªã«ç§»è¡ãäœæããŸãã _create_fos.exs
ã§çµãããã¡ã€ã«ãéãã次ã®åœ¢åŒã«_create_fos.exs
ããŸãã
priv / repo / migrations / 20170215194144_create_fos.exs defmodule AtvApi.Repo.Migrations.CreateFos do use Ecto.Migration def change do create table(:fos, primary_key: false) do add :id, :text, null: false, primary_key: true add :title, :text timestamps() end end end
Elixirã³ãŒãã¯ã¢ãžã¥ãŒã«ãšé¢æ°ã«ç·šæãããŠããŸã ã åã¢ãžã¥ãŒã«ã¯defmodule
ãã¯ãã«ãã£ãŠå®çŸ©ãããé¢æ°ã®èª¬æã¯def
ãŸãã¯defp
ã«ãã£ãŠå®çŸ©ãããŸãã use
ã«æ³šæãæããŸã§ãåŸã§ããã«æ»ããŸãã ãã®ç§»è¡ã¢ãžã¥ãŒã«ã¯AtvApi.Repo.Migrations.CreateFos
ãšåŒã°ããèŠçŽã«åºã¥ããŠäŸ¿å®äžäœæãããŠããŸãã ãã®èšèªã§ã¯ããã®ãããªååã ãã匷å¶ããããšã¯ãããŸããããŸããèšèªã¯ã AtvApi.Repo.Migrations
ãAtvApi.Repo
ãªã©ã®ã芪ãã¢ãžã¥ãŒã«ãAtvApi.Repo.Migrations
ãã§ãŒã³å
šäœãæã€ããšã匷å¶ããŸããã
create/2
ããŒãã«äœæãã¯ãã«primary_key: false
ãªãã·ã§ã³ãè¿œå ããŸããã ããã«ãããæšæºã®id
ãã£ãŒã«ãã®äœæããã£ã³ã»ã«ããåãååã®ãã£ãŒã«ããæåã§è¿œå ããŸãããã¿ã€ãã¯text
ããããäž»ããŒã«ãªããŸãã
web/models
ãã£ã¬ã¯ããªã«ããã¢ãã«ã®èª¬æãä¿®æ£ããŸãããïŒ
ãŠã§ã/ã¢ãã«/ fos.ex defmodule AtvApi.Fos do use AtvApi.Web, :model @primary_key {:id, :string, autogenerate: false} schema "fos" do field :title, :string timestamps() end @doc """ Builds a changeset based on the `struct` and `params`. """ def changeset(struct, params \\ %{}) do struct |> cast(params, [:id, :title]) |> validate_required([:id, :title]) end end
äž»ããŒã®èª¬æã«@primary_key
å®æ°ãè¿œå ããããšã«æ³šæããŠãã ããã ãŸããèš±å¯ãããå€æŽã®ãªã¹ãã«ãã£ãŒã«ãå:id
ã¢ãã ãè¿œå ããŸãã ïŒ cast/3
é¢æ°ã®èª¬æãåç
§ãæåŸã®ãã©ã¡ãŒã¿ãŒãallowed
ïŒ-ããã§ãªãå Žåãå€æŽã»ããã«èšå®ãããã³ãŒãã®ãã£ãŒã«ããè¿œå ã§ããŸããã åãã¢ãã ãvalidate_required/2
ããªããŒã¿é¢æ°ã®ãªã¹ãã«è¿œå ãããŸã ãããã¯ãååã瀺ãããã«ããã§ã³ãžã»ããå
ã®å¯Ÿå¿ãããã£ãŒã«ãã®ååšããã§ãã¯ããååšããªãå Žåã¯ã»ããããšã©ãŒãšããŠããŒã¯ããŸãã
timestamp
ã¿ã€ãã®updated_at
ããã³updated_at
ãã£ãŒã«ããã¢ãã«ã®åç·ã«è¿œå ãããã¯ãåŒã³åºãtimestamps/1
ã«æ³šç®ãã䟡å€ããããŸãã æåã®ãã£ãŒã«ãã¯äœææã«çŸåšã®æå»ã§åæåããã2çªç®ã®ãã£ãŒã«ãã¯ã¬ã³ãŒããEcto
é¢æ°ã«ãã£ãŠå€æŽããããã³ã«åæåãããŸãã
ãã³ãããã®äžã®ã¢ãã«ãšã¯ããã§ã¯ãã¢ãã«ãäœã§ãããã«ã€ããŠããã€ãã®èšèãèšãå¿
èŠããããŸãã
Elixirã«ã¯ã struct
ããšããæŠå¿µããããŸãã æ§é äœã¯é£æ³é
åã®æ¡åŒµã§ãïŒã€ãŸããããŒãšå€ã®ãã¢ã®ã¹ãã¢ã§ãããéåžžã¯%{ key => value, ...}
ãšåŒã°ã%{ key => value, ...}
ããŒãã¢ãã ã®å Žåã %{ key: value, ...}
ïŒ; æ§é ã«ã¯è¿œå ã®ããŒ__struct__
ããã®å€ã«ã¯ååãå«ãŸãã ã³ã³ãã€ã«æã«ã³ãŒãã§æå®ããããã£ãŒã«ãã«ãã£ãŠã®ã¿å¶éãããŸãã , , . defstruct
, :
iex> defmodule User do ...> defstruct title: "John", age: 27 ...> end
, defstruct
, , , . %User{}
.
, â , Map
. Enumerable
, Enum
.
, â , - ( ) , do ... end
scheme
. , AtvApi.Fos
, %Fos{}
- :id
( ) :title
( ).
.
, :
mix test test/models/fos_test.exs $ mix test test/models/fos_test.exs Compiling 7 files (.ex) Generated atv_api app 1) test changeset with valid attributes (AtvApi.FosTest) test/models/fos_test.exs:9 Expected truthy, got false code: changeset.valid?() stacktrace: test/models/fos_test.exs:11: (test) . Finished in 0.05 seconds 2 tests, 1 failure Randomized with seed 166025
test/models/fos_test.exs
, , @valid_attrs
, id
. , - id
, . â . :
@valid_attrs %{title: "Humanities, multidisciplinary", id: "0605BQ"}
:
mix test test/models/fos_test.exs $ mix test test/models/fos_test.exs .. Finished in 0.04 seconds 2 tests, 0 failures Randomized with seed 892257
, , , :
$ mix ecto.migrate 17:54:26.080 [info] == Running AtvApi.Repo.Migrations.CreateFos.change/0 forward 17:54:26.080 [info] create table fos 17:54:26.097 [info] == Migrated in 0.0s
, mix ecto.rollback
.
. , .
priv/repo/seeds.exs
. oecd_fos.txt
grnti.txt
( ) priv/repo
. . :
priv/repo/seeds.exs require Logger alias AtvApi.Repo import Ecto.Query ### OECD FOS dictionary ### alias AtvApi.Fos unless Repo.one!(from f in Fos, select: count(f.id)) > 0 do multi = File.read!("priv/repo/oecd_fos.txt") |> String.split("\n") |> Enum.reject(fn(row) -> byte_size(row) < 1 end) |> Enum.sort |> Enum.dedup |> Enum.reduce(Ecto.Multi.new, fn(row, multi) -> [id, title] = row |> String.trim |> String.split(";") changeset = Fos.changeset(%Fos{}, %{id: id, title: title}) Ecto.Multi.insert(multi, id, changeset) end) Repo.transaction(multi) Logger.info "OECD FOS load complete" end ### OECD FOS dictionary ###
. ( require ) Logger .
requireElixir - (.. , ). â , (.. ) . , , , . require
.
alias
, Repo
AtvApi.Repo
. â import
â ( â Ecto.Query, ). (, , ) , only: [function_title: arity]
, , : import Ecto.Query, only: [from: 2]
(arity â ). â - , . â () , , , , . .
seeds.exs
, . Repo.one!/2 , SQL SELECT COUNT(f.id) FROM fos AS f
, , .
"" "" ", [] , ( tuple ) {:ok, result}
{:error, description}
, , , , .
äŸïŒ
iex> File.read("file.txt") {:ok, "file contents"} iex> File.read("no_such_file.txt") {:error, :enoent} iex> File.read!("file.txt") "file contents" iex> File.read!("no_such_file.txt") ** (File.Error) could not read file no_such_file.txt: no such file or directory
, ( pipe operator ). . , (2) (3) , (4) (5), :
iex(1)> some_map = %{one: 1} %{one: 1} iex(2)> Enum.count(some_map) 1 iex(3)> some_map |> Enum.count() 1 iex(4)> Enum.count(Map.put(some_map, :two, 2)) 2 iex(5)> some_map |> Map.put(:two, 2) |> Enum.count() 2
, :
- (
File.read!/1
), , - ( List ) -
String.split/3
, \n
, - , ,
Enum.reject/2
, , false
, Enum.sort/1
,Enum.dedup/1
,- , ,
Enum.reduce/3
, .
Enum.reduce(enumerable, acc, fun)
, Enum , , Enumerable (), , , . , . Enum.reduce/3
.
Ecto.Multi
.
String.trim/1
, white-space ; String.split/3
, . oecd_fos.txt
, . String.split/3
, . (pattern matching) id
, â title
.
( pattern matching ) â Elixir. , , =
( ) â , (match operator). , :
iex> x = 1 1 iex> x 1
, :
iex> 1 = x 1 iex> 2 = x ** (MatchError) no match of right hand side value: 1
x
1, .
, .
:
iex> {a, b, c} = {:hello, "world", 42} {:hello, "world", 42} iex> a :hello iex> b "world"
:
iex> {a, b, {d, e} = c} = {:hello, "world", {:grey, "hole"}} {:hello, "world", {:grey, "hole"}} iex> a :hello iex> b "world" iex> c {:grey, "hole"} iex> d :grey iex> e "hole"
, . , :
iex> {a, b, c} = {:hello, "world"} ** (MatchError) no match of right hand side value: {:hello, "world"}
, :
iex> {a, b, c} = [:hello, "world", 42] ** (MatchError) no match of right hand side value: [:hello, "world", 42]
, . , :ok
:
iex> {:ok, result} = {:ok, 13} {:ok, 13} iex> result 13 iex> {:ok, result} = {:error, :oops} ** (MatchError) no match of right hand side value: {:error, :oops}
, .
- (pin operator). , , , , Elixir , . , ? pin operator:
iex> x = 1 1 iex> ^x = 2 ** (MatchError) no match of right hand side value: 2 iex> {y, ^x} = {2, 1} {2, 1} iex> y 2 iex> {y, ^x} = {2, 2} ** (MatchError) no match of right hand side value: {2, 2}
x 1, :
iex> {y, 1} = {2, 2} ** (MatchError) no match of right hand side value: {2, 2}
( changeset
) AtvApi.Fos.changeset/2
, OECD FOS (, , , :id
). Ecto.Changeset
.
, ' ' `changeset`, Ecto.Changeset
, , () () (constraints) ( , â ). Ecto.Changeset
" ", .. changeset
. changeset
cast/3
change/2
. , , , , API, .., â . , , ( ) .
AtvApi.Fos.changeset/2
, Ecto.Changeset
, â cast/3
â ( struct
), ( params
) , , , ( [:id, :title]
). , , ( , ). , , :id
:
|> validate_length(:id, min: 6)
, , valid?
false
. .
äŸïŒ
iex> valid = AtvApi.Fos.changeset(%AtvApi.Fos{}, %{id: "123", title: "Some title"}) #Ecto.Changeset<action: nil, changes: %{id: "123", title: "Some title"}, errors: [], data: #AtvApi.Fos<>, valid?: true> iex> invalid = valid |> Ecto.Changeset.validate_length(:id, min: 6) #Ecto.Changeset<action: nil, changes: %{id: "123", title: "Some title"}, errors: [id: {"should be at least %{count} character(s)", [count: 6, validation: :length, min: 6]}], data: #AtvApi.Fos<>, valid?: false> iex> AtvApi.Repo.insert!(invalid) # "" , ** (Ecto.InvalidChangesetError) could not perform insert because changeset is invalid. Applied changes %{id: "123", title: "Some title"} Params %{"id" => "123", "title" => "Some title"} Errors %{id: [{"should be at least %{count} character(s)", [count: 6, validation: :length, min: 6]}]} Changeset #Ecto.Changeset<action: :insert, changes: %{id: "123", title: "Some title"}, errors: [id: {"should be at least %{count} character(s)", [count: 6, validation: :length, min: 6]}], data: #AtvApi.Fos<>, valid?: false> (ecto) lib/ecto/repo/schema.ex:134: Ecto.Repo.Schema.insert!/4 iex> AtvApi.Repo.insert(invalid) # "" , {:error, description} {:error, #Ecto.Changeset<action: :insert, changes: %{id: "123", title: "Some title"}, errors: [id: {"should be at least %{count} character(s)", [count: 6, validation: :length, min: 6]}], data: #AtvApi.Fos<>, valid?: false>}
, , , .
insert* Ecto.Repo
. , ( use
) AtvApi.Repo
, AtvApi.Repo.insert ...
, . , Enum.reduce/3
( â Enum.each/2
), , - ? () . , . Ecto.Repo.transaction/2
, , , Ecto.Multi
, . , Ecto.Multi
, , Enum.reduce/3
. Elixir () , Ecto.Multi
, [ ] , Enum.reduce/3
multi
.
Repo.transaction/2
, (, , , , arity â â , ? , , .. ). OECD FOS.
:
mix run priv/repo/seeds.exs $ mix run priv/repo/seeds.exs [debug] QUERY OK source="fos" db=0.7ms SELECT count(f0."id") FROM "fos" AS f0 [] [debug] QUERY OK db=0.1ms begin [] [debug] QUERY OK db=1.4ms INSERT INTO "fos" ("id","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4) ["010000", "Natural Sciences", {{2017, 2, 21}, {11, 50, 38, 799789}}, {{2017, 2, 21}, {11, 50, 38, 804086}}] [debug] QUERY OK db=0.3ms ... INSERT INTO "fos" ("id","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4) ["0605BQ", "Humanities, multidisciplinary", {{2017, 2, 21}, {11, 50, 38, 973021}}, {{2017, 2, 21}, {11, 50, 38, 973025}}] [debug] QUERY OK db=5.8ms commit [] [info] OECD FOS load complete
FosController
back-end. ? â ( , )!
CRUD-, (View), , , . OECD FOS , â index
, . ( , ), . , ( , ).
ExMachina . , mix test.watch â , .
mix.exs
:
mix.exs # mix.exs # ... # Specifies your project dependencies. # # Type `mix help deps` for examples and options. defp deps do [{:phoenix, "~> 1.2.1"}, {:phoenix_pubsub, "~> 1.0"}, {:phoenix_ecto, "~> 3.0"}, {:postgrex, ">= 0.0.0"}, {:gettext, "~> 0.11"}, {:cowboy, "~> 1.0"}, # - {:ex_machina, "~> 1.0", only: :test}, {:mix_test_watch, "~> 0.3", only: :dev, runtime: false}] end # ...
:
mix deps.get $ mix deps.get Running dependency resolution... Dependency resolution completed: ex_machina 1.0.2 fs 2.12.0 mix_test_watch 0.3.3 * Getting ex_machina (Hex package) Checking package (https://repo.hex.pm/tarballs/ex_machina-1.0.2.tar) Using locally cached package * Getting mix_test_watch (Hex package) Checking package (https://repo.hex.pm/tarballs/mix_test_watch-0.3.3.tar) Fetched package * Getting fs (Hex package) Checking package (https://repo.hex.pm/tarballs/fs-2.12.0.tar) Fetched package
!
. , test/support
. , :id
:title
, AtvApi.FactoryFosList.fos_list/0
, . :
test/support/factory_fos_list.ex defmodule AtvApi.FactoryFosList do @fos_list [ %{id: "010000", title: "Natural Sciences"}, %{id: "020000", title: "Engineering and Technology"}, %{id: "030000", title: "Medical and Health Sciences"}, # ... %{id: "0604YG", title: "Theater"}, %{id: "0605BQ", title: "Humanities, multidisciplinary"}, ] def fos_list, do: @fos_list end
AtvApi.Factory
, . :
test/support/factory.ex defmodule AtvApi.Factory do use ExMachina.Ecto, repo: AtvApi.Repo import AtvApi.FactoryFosList, only: [fos_list: 0] def fos_factory do %AtvApi.Fos{ id: "0", title: "Some science-technology name", } end def build_all(factory_name, insert? \\ false) do get_list(factory_name) |> Enum.map(fn(rec) -> case insert? do true -> insert(factory_name, rec) false -> build(factory_name, rec) end end) end def insert_all(factory_name) do build_all(factory_name, true) end defp get_list(:fos) do fos_list() end defp get_list(_) do [] end end
use
, use ExMachina.Ecto, repo: IasipApi.Repo
:
require ExMachina.Ecto ExMachina.Ecto.__using__(repo: AtvApi.Repo)
require
, __using__/1
, , use
, ( ) , .
ExMachina.Ecto, - â , . "Quote and unquote" , "" "Domain Specific Languages" Elixir.
, , , , ...
⊠â , , use ExMachina.Ecto, ...
. ExMachina.Ecto
GitHub . , , .
, , .. ExMachina.Ecto
( require ExMachina.Ecto
) ExMachina.Ecto.__using__/1
, - ( â repo: AtvApi.Repo
; , Elixir []
). :repo
, quote do ... end
, (.. AtvApi.FactoryFos
), quote
unquote
(. ). , params_for/2
, string_params_for/2
. use ExMachina
use ExMachine.EctoStrategy, ...
, â .. ( ).
, build/2
build_list/3
, . ExMachina.Ecto
insert/2
insert_list/3
, , . , ExMachina.Strategy
, ExMachina.EctoStrategy
use ExMachine.Strategy, function_title: :insert
. , insert/2
insert_list/3
â __using__/1
ExMachina.Strategy
:function_name
.
, , .
AtvApi.FactoryFosList.fos_list/0
, , fos_factory/0
.
ExMachine.Ecto
, : build/2
/ build_list/3
insert/2
/ insert_list/3
. ( , ) , . . build/2
build(factory_name, attrs)
, build_list/3
build_list(number_of_factories, factory_name, attrs)
. , build/2
, <factory_name>_factory/0
. ã€ãŸã build(:fos, %{})
, fos_factory/0
, .
AtvApi.Factory.build/2
Elixir:
$ MIX_ENV=test iex -S mix Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] Interactive Elixir (1.4.1) - press Ctrl+C to exit (type h() ENTER for help) iex> AtvApi.Factory.build(:fos, %{id: "0103SY", title: "Optics"}) %AtvApi.Fos{__meta__: #Ecto.Schema.Metadata<:built, "fos">, id: "0103SY", inserted_at: nil, title: "Optics", updated_at: nil}
build_all/2
, .
: mix.exs
Phoenix Framework mix
, test/support
. MIX_ENV=test
.
, ( build_all/2
), ( insert_all/1
). , build_all/2
, insert_all/1
â .
get_list/1
:
get_list/1 defp get_list(:fos) do fos_list() end defp get_list(_) do [] end
, defp
def
, . , ( , -) .
, . get_list/1
:fos
, , AtvApi.FactoryFosList.fos_list/0
; , . , . .
build_all/2
. get_list/1
, . Enum.map/2
, , . insert?
ExMachina.build/2
, ExMachina.Ecto.insert/2
(, - ). AtvApi.FactoryFos.build_all/2
%Fos{}
.
, mix test
. , mix-test.watch
. mix test.watch
:
mix test.watch $ mix test.watch Running tests... ..... Finished in 0.05 seconds 5 tests, 0 failures Randomized with seed 806690
. , Ctrl+C.
Phoenix Framework test/controllers
. , â exs
, Elixir Script
. , .
() ElixirElixir ExUnit
. , â , .
.
ExUnit
:
# File: assertion_test.exs # 1) ExUnit. ExUnit.start # 2) ( , test case) # (use) "ExUnit.Case". defmodule AssertionTest do # 3) : "async: true", # . # , # . use ExUnit.Case, async: true # 4) "test" "def" test "the truth" do assert true end end
:
$ elixir assertion_test.exs warning: this check/guard will always yield the same result assertion_test.exs:17 . Finished in 0.03 seconds (0.03s on load, 0.00s on tests) 1 test, 0 failures Randomized with seed 598489
Mix
test/test_helper.exs
. .
ExUnit.Case
. async
.
. setup_all
setup
( ):
defmodule ExampleTest do use ExUnit.Case setup do {:ok, [hello: :world]} end test "context contains key-value pairs", context do assert context[:hello] == :world end end
, , :
test "context is a map and pattern matching", %{hello: hello} do assert hello == :world end
setup
, â , .
, ExUnit.Case
(callbacks) ExUnit.Callbacks
. setup_all
setup
, on_exit/2
.
. , (. ).
setup_all
. setup
. , setup_all
setup
.
on_exit/2
, , setup
.
setup_all
{:ok, keywords}
, - keywords
setup_all
, setup
.
setup
setup
.
:ok
.
setup_all
- , , setup
.
ããã¥ã¡ã³ãã®äŸïŒ
defmodule AssertionTest do use ExUnit.Case, async: true # "setup_all" setup_all do IO.puts " AssertionTest" # No metadata :ok end # "setup" setup do IO.puts " 'setup'" on_exit fn -> IO.puts " " end # [hello: "world"] end # , # setup context do IO.puts " : #{context[:test]}" :ok end # setup :invoke_local_or_imported_function test "always pass" do assert true end test "another one", context do assert context[:hello] == "world" end defp invoke_local_or_imported_function(context) do [from_named_setup: true] end end
ExUnit.Assertions
. , ExUnit.Case
, .
assert/1
refute/1
.
, , , .
Phoenix Framework , - , .. , , .
, , .
fos_controller_test.exs
:
test/controllers/fos_controller_test.exs defmodule AtvApi.FosControllerTest do use AtvApi.ConnCase import AtvApi.Factory import AtvApi.FactoryFosList, only: [fos_list: 0] setup %{conn: conn} do insert_all(:fos) fos = fos_list() |> Enum.sort |> Poison.encode! |> Poison.decode! {:ok, conn: put_req_header(conn, "accept", "application/json"), fos: fos} end test "lists all entries on index", %{conn: conn, fos: fos} do conn = get conn, fos_path(conn, :index) assert json_response(conn, 200)["data"] == fos end end
(use) AtvApi.ConnCase
, Phoenix Framework. Phoenix.ConnTest
, , , ; , setup
, conn
Plug.Conn
, .
AtvApi.Factory
AtvApi.FactoryFosList
.
setup
, conn
Plug.Conn.put_req_header/3
. AtvApi.Factory.insert_all/1
. AtvApi.FactoryFosList.fos_list/0
, Enum.sort/1
, JSON Poison
( JSON, â ). conn
, .
, FosController
fos
, setup
. conn
fos
, .
. , HTTP GET- . Phoenix.ConnTest.get/3
, conn
, â URL, . â "/api/fos"
â , Phoenix.Router
, web/router.ex
. - fos_path/2
.
Phoenix.ConnTest.get/3
conn
, .
JSON- :
{"data": [ {"id": "010000", "title": "Natural Sciences"}, {"id": "020000", "title": "Engineering and Technology"}, ... ] }
Phoenix.ConnTest.json_response/2
, â 200 (.. HTTP_OK), JSON- . , , "data"
â .. â fos
.
web/router.ex
resources "/fos", FosController, except: [:new, :edit]
. CRUD-, . , :
mix phoenix.routes $ mix phoenix.routes fos_path GET /api/fos AtvApi.FosController :index fos_path GET /api/fos/:id AtvApi.FosController :show fos_path POST /api/fos AtvApi.FosController :create fos_path PATCH /api/fos/:id AtvApi.FosController :update PUT /api/fos/:id AtvApi.FosController :update fos_path DELETE /api/fos/:id AtvApi.FosController :delete
â â . â . resources "/fos", FosController, except: [:new, :edit]
get "/fos", FosController, :index
mix phoenix.routes
:
mix phoenix.routes $ mix phoenix.routes Compiling 6 files (.ex) fos_path GET /api/fos AtvApi.FosController :index
, â HTTP GET- http://:/api/fos/
:index
AtvApi.FosController
.
. , , . ã©ã£ã¡ïŒ :
mix test test/controllers/fos_controller_test.exs $ mix test test/controllers/fos_controller_test.exs Compiling 6 files (.ex) 1) test lists all entries on index (AtvApi.FosControllerTest) test/controllers/fos_controller_test.exs:19 Assertion with == failed code: json_response(conn, 200)["data"] == fos left: [%{"id" => "010000", "title" => "Natural Sciences"}, %{"id" => "020000", "title" => "Engineering and Technology"}, %{"id" => "030000", "title" => "Medical and Health Sciences"}, ... %{"id" => "0101PO", "title" => "Mathematics, interdisciplinary applications"}, %{"id" => "0101PQ", "title" => "Mathematics"}, %{"id" => "0101UR", ...}, %{...}, ...] right: [%{"id" => "010000", "title" => "Natural Sciences"}, %{"id" => "010100", "title" => "Mathematics"}, %{"id" => "0101PN", "title" => "Mathematics, applied"}, ... %{"id" => "010600", "title" => "Biological sciences"}, %{"id" => "0106BD", "title" => "Biodiversity conservation"}, %{"id" => "0106CO", ...}, %{...}, ...] stacktrace: test/controllers/fos_controller_test.exs:21: (test) Finished in 0.1 seconds 1 test, 1 failure Randomized with seed 415134
, , . , :index
AtvApi.FosController
, , ( Enum.sort/1
, setup
). , AtvApi.FosController.index/2
. :
web/controllers/fos_controller.ex defmodule AtvApi.FosController do use AtvApi.Web, :controller alias AtvApi.Fos import Ecto.Query def index(conn, _params) do fos = Repo.all(from f in Fos, order_by: f.id) render(conn, "index.json", fos: fos) end end
(use) AtvApi.Web
, __using__/1
controller
. â web/web.ex
, .
, index/2
. Ecto.Repo.all/2
, Ecto.Queryable
, , . , , : Repo.all(Fos)
. , , - :
SELECT f0."id", f0."title", f0."inserted_at", f0."updated_at" FROM "fos" AS f0
ã€ãŸã , . , DSL Ecto.Query
, Repo.all(from f in Fos, order_by: f.id)
, :
SELECT f0."id", f0."title", f0."inserted_at", f0."updated_at" FROM "fos" AS f0 ORDER BY f0."id"
, fos
fos
, id
.
Phoenix.Controller.render/3
, (View) conn
( ), ( ) ( ). , , , , ; , Phoenix Framework ( ) AtvApi.FosView
, render/2
, "index.json", â , fos
. , view
â , . web/views/fos_view.ex
â .
:
mix test test/controllers/fos_controller_test.exs $ mix test test/controllers/fos_controller_test.exs Compiling 1 file (.ex) . Finished in 0.2 seconds 1 test, 0 failures Randomized with seed 347227
, .
(, , , mix ecto.create
, mix ecto.migrate
mix run priv/repo/seeds.exs
):
$ mix phoenix.server [info] Running AtvApi.Endpoint with Cowboy using http://localhost:4000
http://localhost:4000/api/fos/
:
, JSON- . !
, â . , , front-end' . has_children
.
, , :
$ mix phoenix.gen.model Grnti2 grnti2 title:text has_children:boolean
. , .
(integer) . . , id
Ecto
, integer.
:
priv/repo/migrations/20170211194248_create_grnti.exs defmodule AtvApi.Repo.Migrations.CreateGrnti do use Ecto.Migration def change do create table(:grnti, primary_key: false) do add :id, :integer, null: false, primary_key: true add :title, :text add :has_children, :boolean, default: false, null: false timestamps() end end end
, :has_children
-.
:
web/models/grnti.ex defmodule AtvApi.Grnti do use AtvApi.Web, :model schema "grnti" do field :title, :string field :has_children, :boolean, default: false timestamps() end @doc """ Builds a changeset based on the `struct` and `params`. """ def changeset(struct, params \\ %{}) do struct |> cast(params, [:id, :title, :has_children]) |> validate_required([:id, :title, :has_children]) end end
, . , id
, . , changeset/2
.
, :
mix test test/models/grnti_test.exs $ mix test test/models/grnti_test.exs . 1) test changeset with valid attributes (AtvApi.GrntiTest) test/models/grnti_test.exs:9 Expected truthy, got false code: changeset.valid?() stacktrace: test/models/grnti_test.exs:11: (test) Finished in 0.05 seconds 2 tests, 1 failure Randomized with seed 788882
, , , id
, :
@valid_attrs %{title: "some content", has_children: true, id: 100001}
:
mix test test/models/grnti_test.exs $ mix test test/models/grnti_test.exs .. Finished in 0.04 seconds 2 tests, 0 failures Randomized with seed 692361
â :
mix ecto.migrate $ mix ecto.migrate 17:54:26.080 [info] == Running AtvApi.Repo.Migrations.CreateGrnti.change/0 forward 17:54:26.080 [info] create table grnti 17:54:26.097 [info] == Migrated in 0.0s
. priv/repo/seeds.exs
:
priv/repo/seeds.exs ### Grnti dictionary ### alias AtvApi.Grnti unless Repo.one!(from g in Grnti, select: count(g.id)) > 0 do multi = File.read!("priv/repo/grnti.txt") |> String.split("\n") |> Enum.reject(fn(row) -> byte_size(row) < 2 end) |> Enum.reduce(%{}, fn(row, acc) -> {id, parent_id, title} = case <<String.trim(row)::binary>> do <<a::binary-size(2), ".", b::binary-size(2), ".", c::binary-size(2), " ", title::binary>> -> { String.to_integer("#{a}#{b}#{c}"), String.to_integer("#{a}#{b}00"), title } <<a::binary-size(2), ".", b::binary-size(2), " ", title::binary>> -> { String.to_integer("#{a}#{b}00"), String.to_integer("#{a}0000"), title } <<a::binary-size(2), " ", title::binary>> -> { String.to_integer("#{a}0000"), -1, title } end parent = case Map.get(acc, parent_id) do nil -> {"", true} {p_title, _} -> {p_title, true} end current = case Map.get(acc, id) do nil -> {title, false} {_, has_children} -> {title, has_children} end acc |> Map.put(id, current) |> Map.put(parent_id, parent) end) |> Enum.reduce(Ecto.Multi.new, fn({id, {title, has_children}}, multi) -> if id > -1 do changeset = Grnti.changeset(%Grnti{}, %{id: id, title: String.trim(title), has_children: has_children}) Ecto.Multi.insert(multi, "#{id}", changeset) else multi end end) Repo.transaction(multi) Logger.info "GRNTI load complete" end ### Grnti dictionary ###
:
- (
File.read!/1
), , - ( List ) -
String.split/3
, \n
, - , ,
Enum.reject/2
, Enum.reduce/3
,Enum.reduce/3
.
Enum.reduce/3
. %{}
. row
acc
. ?
, , :
- " 00 "
- " 00.21 - "
- " 02.01.39 "
id
title
. , , . , id
"", â ( ), â , ⊠, . .
binaryElixir <<>>
. .
. , : Erlang , â , , Elixir .
â , â , :
defmodule ImageTyper @png_signature <<137::size(8), 80::size(8), 78::size(8), 71::size(8), 13::size(8), 10::size(8), 26::size(8), 10::size(8)>> @jpg_signature <<255::size(8), 216::size(8)>> def type(<<@png_signature, rest::binary>>), do: :png def type(<<@jpg_signature, rest::binary>>), do: :jpg def type(_), do :unknown end
ImageTyper.type/1
, , : :png
| :jpg
| :unknown
.
, -, id
, â title
has_children
: %{id => {title, has_children}}
.
, .
{id, parent_id, title}
case
, , String.trim/1
. - .
a
, b
c
, , title
, . case
: , "#{a}#{b}#{c}"
( #{}
â ( )), , , , . c
, â b
. â â -1. id
, parent_id
title
.
. Map.get/3
. - nil
. , ( , , ), , â has_children
true
, parent
.
id
: â , â , has_children
.
Map.put/3
\ .
.
Enum.reduce/3
, Ecto.Multi
, , OECD FOS. multi
, Repo.transaction/2
.
:
mix run priv/repo/seeds.exs $ mix run priv/repo/seeds.exs [debug] QUERY OK source="fos" db=0.9ms queue=0.1ms SELECT count(f0."id") FROM "fos" AS f0 [] [debug] QUERY OK source="grnti" db=3.6ms SELECT count(g0."id") FROM "grnti" AS g0 [] [debug] QUERY OK db=0.1ms begin [] [debug] QUERY OK db=2.1ms INSERT INTO "grnti" ("has_children","id","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) [false, 443135, " ", {{2017, 2, 22}, {16, 51, 9, 581608}}, {{2017, 2, 22}, {16, 51, 9, 585864}}] [debug] QUERY OK db=0.3ms INSERT INTO "grnti" ("has_children","id","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) [false, 722335, " ", {{2017, 2, 22}, {16, 51, 9, 593526}}, {{2017, 2, 22}, {16, 51, 9, 593531}}] [debug] QUERY OK db=0.1ms INSERT INTO "grnti" ("has_children","id","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) [true, 761300, " ", {{2017, 2, 22}, {16, 51, 9, 593995}}, {{2017, 2, 22}, {16, 51, 9, 594000}}] ... [debug] QUERY OK db=0.4ms INSERT INTO "grnti" ("has_children","id","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) [false, 107161, "", {{2017, 2, 22}, {16, 51, 56, 376371}}, {{2017, 2, 22}, {16, 51, 56, 376375}}] [debug] QUERY OK db=0.3ms INSERT INTO "grnti" ("has_children","id","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) [false, 292931, " ", {{2017, 2, 22}, {16, 51, 56, 376969}}, {{2017, 2, 22}, {16, 51, 56, 376972}}] [debug] QUERY OK db=5.0ms commit [] [info] GRNTI load complete
, fos
( , ) grnti
. .
GrntiController
.
, test/support
. , , :id
, :title
:has_children
, AtvApi.FactoryGrntiList.grnti_list/0
, . :
test/support/factory_grnti_list.ex defmodule AtvApi.FactoryGrntiList do @grnti_list [ %{id: 000000, has_children: true, title: " "}, %{id: 000800, has_children: false, title: " "}, %{id: 000900, has_children: false, title: " "}, %{id: 001100, has_children: false, title: " "}, # ... %{id: 032323, has_children: false, title: " ( XII .)"}, %{id: 032325, has_children: false, title: " ( XII . XVI .)"}, ] def grnti_list, do: @grnti_list end
AtvApi.Factory
:
test/support/factory.ex defmodule AtvApi.Factory do use ExMachina.Ecto, repo: AtvApi.Repo import AtvApi.FactoryFosList, only: [fos_list: 0] import AtvApi.FactoryGrntiList, only: [grnti_list: 0] def fos_factory do %AtvApi.Fos{ id: "0", title: "Some science-technology name", } end def grnti_factory do %AtvApi.Grnti{ id: 0, title: "Some grnti chapter name", has_children: false, } end def build_all(factory_name, insert? \\ false) do get_list(factory_name) |> Enum.map(fn(rec) -> case insert? do true -> insert(factory_name, rec) false -> build(factory_name, rec) end end) end def insert_all(factory_name) do build_all(factory_name, true) end defp get_list(:fos) do fos_list() end defp get_list(:grnti) do grnti_list() end defp get_list(_) do [] end end
, , grnti_factory/0
build/2
, build_list/3
, insert/2
insert_list/3
, get_list/1
. build_all/1
insert_all/1
grnti
, ! , : - (.. , :fos
:grnti
) . , get_list(_)
, . , , .
get_list/1, , . , , :
defp get_list(factory) do case factory do :fos -> fos_list() :grnti -> grnti_list() _ -> [] end end
, - , . , , API:
# ################################################### # # proceed API request results # # ################################################### # # check for task defp proceed_response(task_uuid, response, state) do # could be rewriten inline, but this is for better code readability task = Map.get(state, task_uuid) proceed_response(task, task_uuid, response, state) end # no task with such uuid - do nothing defp proceed_response(task, _task_uuid, _response, state) when is_nil(task) do state end # Got a normal HTTP response defp proceed_response(task, task_uuid, {:ok, %HTTPoison.Response{body: body, status_code: 200}} = _response, state) do json_decode_result = Poison.decode(body) proceed_response(task, task_uuid, json_decode_result, state) end # API task ID defp proceed_response(task, task_uuid, {:ok, %{"errorId" => 0, "taskId" => api_task_id} = _json_body}, state) do Process.send_after(self(), {:api_get_task_result, task_uuid}, task.result_request_interval) put_in(state, [task_uuid, :api_task_id], api_task_id) end # Set a timer to try again if the task is still processing defp proceed_response(task, task_uuid, {:ok, %{"errorId" => 0, "status" => "processing"} = _json_body}, state) do Process.send_after(self(), {:api_get_task_result, task_uuid}, task.result_retry_interval) state end # Deal with result if the task is done and task type is Image # in case of push: true defp proceed_response( %{type: "ImageToTextTask"} = task, task_uuid, {:ok, %{"errorId" => 0, "status" => "ready", "solution" => %{"text" => text}} = _json_body}, state) do state |> put_in([task_uuid, :result], %{text: text}) |> put_in([task_uuid, :status], :ready) |> push_data(task, task_uuid, {:ready, task_uuid, %{text: text}}) end # Any other - probably an error defp proceed_response(_task, task_uuid, error, state) do parse_error(task_uuid, error, state) end
if\else
.
. , {"errorId" => 0, "taskId" => 12345}
, API , {"errorId" => 0, "status" => "processing"}
, , , {"errorId" => 0, "status" => "ready", "solution" => {"text" => "some_text"}}
. , , , , , JSON {"errorId" => 0, "status" => "ready", "solution" => %{"image" => image_string}}
(, , JSON, ). , API â - proceed_response/3
.
â . , :
$ iex Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] Interactive Elixir (1.4.1) - press Ctrl+C to exit (type h() ENTER for help) iex> defmodule ListSum do ...> def list_sum(list), do: list_sum(list, 0) ...> def list_sum([head | tail], acc), do: list_sum(tail, acc + head) ...> def list_sum([], acc), do: acc ...> end {:module, ListSum, <<70, 79, 82, 49, 0, 0, 5, 180, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 223, 131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115, 95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:list_sum, 2}} iex> ListSum.list_sum([1, 5, 10, 20]) 36
. , .. , , , , , â , . mix test.watch
, .
AtvApi.GrntiController
"/api/grnti/<id>"
<id>
, , , <id>
-1.
get "/grnti/:id", GrntiController, :show
:
web/router.ex defmodule AtvApi.Router do use AtvApi.Web, :router pipeline :api do plug :accepts, ["json"] end scope "/api", AtvApi do pipe_through :api get "/fos", FosController, :index get "/grnti/:id", GrntiController, :show end end
:
mix phoenix.routes $ mix phoenix.routes Compiling 6 files (.ex) fos_path GET /api/fos AtvApi.FosController :index grnti_path GET /api/grnti/:id AtvApi.GrntiController :show
! .
-, :
test/support/factory.ex # ... def get_descendants(:grnti, -1) do grnti_list() |> Enum.filter(fn(%{id: id}) -> rem(id, 10000) == 0 end) end # ...
, â ? , ? ! iex
:
MIX_ENV=test iex -S mix $ MIX_ENV=test iex -S mix Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] Interactive Elixir (1.4.1) - press Ctrl+C to exit (type h() ENTER for help) iex> AtvApi.Factory.get_descendants(:grnti, -1) [%{has_children: true, id: 0, title: " "}, %{has_children: true, id: 20000, title: ""}, %{has_children: true, id: 30000, title: ". "}]
.
test/controllers/grnti_controller_test.exs
:
test/controllers/grnti_controller_test.exs defmodule AtvApi.GrntiControllerTest do use AtvApi.ConnCase import AtvApi.Factory setup %{conn: conn} do insert_all(:grnti) {:ok, conn: put_req_header(conn, "accept", "application/json")} end test "the root level descendants", %{conn: conn} do id = -1 grnti_subtree = get_descendants(:grnti, id) conn = get conn, grnti_path(conn, :show, id) assert json_response(conn, 200)["data"] == grnti_subtree |> Poison.encode! |> Poison.decode! end end
mix test.watch
, , ** (UndefinedFunctionError) function AtvApi.GrntiController.init/1 is undefined (module AtvApi.GrntiController is not available)
. , . :
web/controllers/grnti_controller.ex defmodule AtvApi.GrntiController do use AtvApi.Web, :controller alias AtvApi.Grnti def show(conn, %{"id" => id}) do conn |> put_resp_content_type("text/plain") |> send_resp(200, "request_ok") end end
show/2
, conn
, "id"
. content-type 200 . , , , . .
Elixir, Ecto Phoenix Framework "thin model, fat controller" â , .
. Grnti, :
web/models/grnti.ex defmodule AtvApi.Grnti do # ... def descendants(parent_id) when parent_id == -1 do from g in AtvApi.Grnti, where: fragment("mod(?, ?)", g.id, 10000) == 0, order_by: g.id end end
when
. Erlang Elixir guards
: , , ( , guards case
, ). guards
, . id
-1. , , , .
â DSL Ecto.Query
. â where
fragment/1
Ecto.Query.API
. , Ecto.Query
. , â SQL-, ( ?
) , , . id
10000 â .
show/2
:
web/controllers/grnti_controller.ex defmodule AtvApi.GrntiController do # ... def show(conn, %{"id" => parent_id}) do grnti = parent_id |> Grnti.descendants |> Repo.all render(conn, "index.json", grnti: grnti) end end
** (FunctionClauseError) no function clause matching in AtvApi.Grnti.descendants/1
. , , , AtvApi.Grnti.descendants/1
"-1" -1. â "id"
URL GET-, . AtvApi.Grnti.descendants/1
:
web/models/grnti.ex defmodule AtvApi.Grnti do # ... def descendants(parent_id) when is_binary(parent_id) do parent_id |> String.to_integer |> descendants end def descendants(parent_id) when parent_id == -1 do from g in AtvApi.Grnti, where: fragment("mod(?, ?)", g.id, 10000) == 0, order_by: g.id end end
â ( is_binary/1
, â ), descendants/1
, .
, : ** (UndefinedFunctionError) function AtvApi.GrntiView.render/2 is undefined (module AtvApi.GrntiView is not available)
. , , , (view). :
web/views/grnti_view.exdefmodule AtvApi.GrntiView do
use AtvApi.Web, :view
def render("index.json", %{grnti: grnti}) do
%{data: render_many(grnti, AtvApi.GrntiView, "grnti.json")}
çµãã
def render("grnti.json", %{grnti: grnti}) do
%{id: grnti.id,
title: grnti.title,
has_children: grnti.has_children}
çµãã
çµãã
, â , . , Phoenix.Controller.render/3
, , , Phoenix.View.render/2
, , ( ) () . . , , def render("index.json", %{grnti: grnti})
. :data
, , render_many/3
. , , , â (. ). Phoenix.View.render/2
, , , render("index.json", %{grnti: grnti})
, â .
, , , â !
, !
. , , - . AtvApi.Factory
get_descendants/2
:
test/support/factory.ex defmodule AtvApi.Factory do # ... def get_descendants(:grnti, -1) do grnti_list() |> Enum.filter(fn(%{id: id}) -> rem(id, 10000) == 0 end) end def get_descendants(:grnti, parent_id) when rem(parent_id, 10000) == 0 do grnti_list() |> Enum.filter(fn(%{id: id}) -> rem(id, 100) == 0 and id > parent_id and id < parent_id + 10000 end) end defp get_list(:fos) do # .. end
guard, 10000, (.. xx0000). , id
xxxx00 id
id
.
, , .
. , , , , , . , DRY , , â . , setup
:
test/controllers/grnti_controller_test.exs defmodule AtvApi.GrntiControllerTest do use AtvApi.ConnCase import AtvApi.Factory setup %{conn: conn, id: id} do insert_all(:grnti) conn = put_req_header(conn, "accept", "application/json") descendants = :grnti |> get_descendants(id) |> Poison.encode! |> Poison.decode! conn = get conn, grnti_path(conn, :show, id) {:ok, conn: conn, descendants: descendants} end @tag id: -1 test "shows chosen root level subtree", %{conn: conn, descendants: descendants} do assert json_response(conn, 200)["data"] == descendants end end
setup
, , , . . @tag id: -1
, id: -1
, setup
. - .
, . :
test/controllers/grnti_controller_test.exs # ... @tag id: 000000 test "shows chosen second level subtree - id: 000000", %{conn: conn, descendants: descendants} do assert json_response(conn, 200)["data"] == descendants end @tag id: 020000 test "shows chosen second level subtree - id: 020000", %{conn: conn, descendants: descendants} do assert json_response(conn, 200)["data"] == descendants end @tag id: 030000 test "shows chosen second level subtree - id: 030000", %{conn: conn, descendants: descendants} do assert json_response(conn, 200)["data"] == descendants end #...
, ** (FunctionClauseError) no function clause matching in AtvApi.Grnti.descendants/1
. â , -1.
, , AtvApi.Grnti/descendants/1
:
web/models/grnti.ex # ... def descendants(parent_id) when parent_id == -1 do from g in AtvApi.Grnti, where: fragment("mod(?, ?)", g.id, 10000) == 0, order_by: g.id end def descendants(parent_id) when rem(parent_id, 10000) == 0 do from g in AtvApi.Grnti, where: g.id > ^parent_id, where: g.id < ^(parent_id + 10000), where: fragment("mod(?, ?)", g.id, 100) == 0, order_by: g.id end # ...
, Ecto
, SQL-:
SELECT g0."id", g0."title", g0."has_children", g0."inserted_at", g0."updated_at" FROM "grnti" AS g0 WHERE (g0."id" > $1) AND (g0."id" < $2) AND (mod(g0."id", 100) = 0) ORDER BY g0."id"
$1
â , , $2
â .
, , .
.
:
test/support/factory.ex defmodule AtvApi.Factory do # ... def get_descendants(:grnti, parent_id) when rem(parent_id, 10000) == 0 do grnti_list() |> Enum.filter(fn(%{id: id}) -> rem(id, 100) == 0 and id > parent_id and id < parent_id + 10000 end) end def get_descendants(:grnti, parent_id) when rem(parent_id, 100) == 0 do grnti_list() |> Enum.filter(fn(%{id: id}) -> id > parent_id and id < parent_id + 100 end) end defp get_list(:fos) do # .. end
ãã¹ãïŒ
test/controllers/grnti_controller_test.exs # ... @tag id: 000900 test "shows chosen second level subtree - id: 000900", %{conn: conn, descendants: descendants} do assert json_response(conn, 200)["data"] == descendants end @tag id: 021500 test "shows chosen second level subtree - id: 021500", %{conn: conn, descendants: descendants} do assert json_response(conn, 200)["data"] == descendants end @tag id: 032300 test "shows chosen second level subtree - id: 032300", %{conn: conn, descendants: descendants} do assert json_response(conn, 200)["data"] == descendants end #...
3 .
å®è£
ïŒ
web/models/grnti.ex # ... def descendants(parent_id) when rem(parent_id, 10000) == 0 do from g in AtvApi.Grnti, where: g.id > ^parent_id, where: g.id < ^(parent_id + 10000), where: fragment("mod(?, ?)", g.id, 100) == 0, order_by: g.id end def descendants(parent_id) when rem(parent_id, 100) == 0 do from g in AtvApi.Grnti, where: g.id > ^parent_id, where: g.id < ^(parent_id + 100), order_by: g.id end # ...
!
, . , , , id
, , .
, . , id
â setup
, AtvApi.Factory.descendants/2
, . , ?
ExUnit.Case
describe/2
. setup
. setup
, describe do ... end
, . describe
setup
:
test/controllers/grnti_controller_test.exs defmodule AtvApi.GrntiControllerTest do use AtvApi.ConnCase import AtvApi.Factory setup %{conn: conn, id: id} do insert_all(:grnti) conn = conn |> put_req_header("accept", "application/json") |> get(grnti_path(conn, :show, id)) {:ok, conn: conn} end describe "Controller must return descendants of" do setup %{id: id} do descendants = :grnti |> get_descendants(id) |> Poison.encode! |> Poison.decode! {:ok, descendants: descendants} end @tag id: -1 test "the root level", %{conn: conn, descendants: descendants} do assert json_response(conn, 200)["data"] == descendants end @tag id: 000000 test "the chapter with id: 000000", %{conn: conn, descendants: descendants} do assert json_response(conn, 200)["data"] == descendants end @tag id: 020000 test "the chapter with id: 020000", %{conn: conn, descendants: descendants} do assert json_response(conn, 200)["data"] == descendants end @tag id: 030000 test "the chapter with id: 030000", %{conn: conn, descendants: descendants} do assert json_response(conn, 200)["data"] == descendants end @tag id: 000900 test "the chapter with id: 000900", %{conn: conn, descendants: descendants} do assert json_response(conn, 200)["data"] == descendants end @tag id: 021500 test "the chapter with id: 021500", %{conn: conn, descendants: descendants} do assert json_response(conn, 200)["data"] == descendants end @tag id: 032300 test "the chapter with id: 032300", %{conn: conn, descendants: descendants} do assert json_response(conn, 200)["data"] == descendants end end end
.
id
. :
describe/2
:
test/controllers/grnti_controller_test.exs defmodule AtvApi.GrntiControllerTest do use AtvApi.ConnCase # ... describe "Request must be declined with status code 422 and appropriate JSON error message in case of" do @tag id: "somestring" test "id as a non-digit symbol string", %{conn: conn} do assert json_response(conn, 422)["error"] == %{message: "Unprocessable Entity"} end @tag id: -2 test "id is less than -1 and equal -2", %{conn: conn} do assert json_response(conn, 422)["error"] == %{message: "Unprocessable Entity"} end @tag id: -100 test "id is less than -1 and equal -100", %{conn: conn} do assert json_response(conn, 422)["error"] == %{message: "Unprocessable Entity"} end @tag id: 1000000 test "id is greater than 999999 and equal 1000000", %{conn: conn} do assert json_response(conn, 422)["error"] == %{message: "Unprocessable Entity"} end @tag id: 90000000 test "id is greater than 999999 and equal 90000000", %{conn: conn} do assert json_response(conn, 422)["error"] == %{message: "Unprocessable Entity"} end @tag id: 030955 test "id is a third level section code and equal 030955", %{conn: conn} do assert json_response(conn, 422)["error"] == %{message: "Unprocessable Entity"} end @tag id: 020129 test "id is a third level section code and equal 020129", %{conn: conn} do assert json_response(conn, 422)["error"] == %{message: "Unprocessable Entity"} end end end
. id
. String.to_integer/1
, . Integer.parse/2
. â , â 10, .. , . {integer, reminder_of_binary}
:error
.
:
web/controllers/grnti_controller.ex defmodule AtvApi.GrntiController do use AtvApi.Web, :controller alias AtvApi.Grnti def show(conn, %{"id" => parent_id}) do case Integer.parse(parent_id) do :error -> show(conn, :error) {int, _} -> show(conn, int) end end def show(conn, parent_id) when is_integer(parent_id) and parent_id > -2 and parent_id < 1000000 and ( rem(parent_id, 100) == 0 or parent_id == -1 ) do grnti = parent_id |> Grnti.descendants() |> Repo.all() render(conn, "index.json", grnti: grnti) end def show(conn, _parent_id) do conn |> put_resp_content_type("application/json") |> send_resp(422, ~S({"error":{"message":"Unprocessable Entity"}})) end end
, ,
show/2
, , .. URL. , show/2
. . , , guard
, . , , Content-Type: application/json
, (sigil) .
back-end. , ( , GrntiController - DRY ). , , - , .
mix phoenix.server
:
, front-end , , . , , .
back-end, , GitHub .
, . è¯ãäžæ¥ãïŒ