Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
今こそッ、始めようGrailsブートキャンプ!!!! / Grails Bootcamp ...
Search
Kazuki YAMAMOTO
October 23, 2015
Programming
0
320
今こそッ、始めようGrailsブートキャンプ!!!! / Grails Bootcamp for JGGUG
今こそッ、始めようGrailsブートキャンプ!!!!の資料です。
https://jggug.doorkeeper.jp/events/32330
Kazuki YAMAMOTO
October 23, 2015
Tweet
Share
More Decks by Kazuki YAMAMOTO
See All by Kazuki YAMAMOTO
Grails 3で生まれ変わったGrailsの今 / Spring in Summer 2015
yamkazu
1
400
Grails 3でWeb APIを簡単に作ろう!
yamkazu
6
2.1k
Other Decks in Programming
See All in Programming
『ドメイン駆動設計をはじめよう』のモデリングアプローチ
masuda220
PRO
8
540
よくできたテンプレート言語として TypeScript + JSX を利用する試み / Using TypeScript + JSX outside of Web Frontend #TSKaigiKansai
izumin5210
6
1.7k
Click-free releases & the making of a CLI app
oheyadam
2
110
Jakarta Concurrencyによる並行処理プログラミングの始め方 (JJUG CCC 2024 Fall)
tnagao7
1
290
as(型アサーション)を書く前にできること
marokanatani
9
2.6k
Outline View in SwiftUI
1024jp
1
330
3rd party scriptでもReactを使いたい! Preact + Reactのハイブリッド開発
righttouch
PRO
1
600
Webの技術スタックで マルチプラットフォームアプリ開発を可能にするElixirDesktopの紹介
thehaigo
2
1k
詳細解説! ArrayListの仕組みと実装
yujisoftware
0
580
Make Impossible States Impossibleを 意識してReactのPropsを設計しよう
ikumatadokoro
0
170
Duckdb-Wasmでローカルダッシュボードを作ってみた
nkforwork
0
120
Remix on Hono on Cloudflare Workers
yusukebe
1
280
Featured
See All Featured
How STYLIGHT went responsive
nonsquared
95
5.2k
Rebuilding a faster, lazier Slack
samanthasiow
79
8.7k
Testing 201, or: Great Expectations
jmmastey
38
7.1k
Building Your Own Lightsaber
phodgson
103
6.1k
How To Stay Up To Date on Web Technology
chriscoyier
788
250k
Building Flexible Design Systems
yeseniaperezcruz
327
38k
KATA
mclloyd
29
14k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
31
2.7k
Building an army of robots
kneath
302
43k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
329
21k
Statistics for Hackers
jakevdp
796
220k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
47
2.1k
Transcript
ࠓͦ͜ο͡ΊΑ͏ GrailsϒʔτΩϟϯϓ!!! @yamkazu #jggug
ࢁຊ थ • @yamkazu • JGGUG
None
ຊͷ༰ • Grails֓ཁ • Grails 3֓ཁ • GrailsϋϯζΦϯ • Hallo
World • TODOΞϓϦΛ࡞ͬͯΈΑ͏
ࠓΒͳ͍͜ͱ • Groovyࣗମͷઆ໌ • αϯϓϧίʔυͰొ͢ΔGroovyͷίʔυʹ͍ͭͯՄೳͳ͔͗Γัଊ Λ͍Ε͍͖ͯ·͢ • Grailsͷཏతͳઆ໌ ࠓखΛಈ͔ͯ͠งғؾΛ ௫ΜͰΒ͏͜ͱΛ༏ઌ͠·͢ʂ
ࠓͷϋϯζΦϯʹඞཁͳڥ • Java • Grails • IDE or ςΩετΤσΟλ https://github.com/yamkazu/jggug-grails-bootcamp
Λࢀߟʹ४උΛ͓ئ͍͠·͢ʂ
Grailsͱ • Graeme Rocherࢯ͕։ൃ • ϑϧελοΫͷWebϑϨʔϜϫʔΫ • Groovyϕʔε • Ruby
on RailsɺDjangoͱ͍ͬͨϑϨʔϜϫʔΫʹӨڹΛड͚͍ͯΔ • DRYʢDon't Repeat Yourselfʣ= ಉ͡هड़Λ܁Γฦ͞ͳ͍ • CoCʢConvention over Configurationʣ= ઃఆΑΓن • εΩϟϑΥϧσΟϯά • Java EE্Ͱಈ࡞
Grailsͷྺ࢙ 4QSJOH4PVSDFԼ 7.XBSF͕ 4QSJOH4PVSDFΛങऩ
4QSJOH4PVSDF͕ 1JWPUBMԼ 1JWPUBMଔۀ
OCI͕৽͍͠ϗʔϜ http://www.ociweb.com/resources/news/2015/04/15/april-2015-grails-web-framework-finds-home-oci
Grails 3ͷ֓ཁ • Spring BootΛϕʔεʹ࠶ߏங • GradleͰϏϧυγεςϜ͕Ұ৽ • ΞϓϦέʔγϣϯϓϩϑΝΠϧͷՃ •
ίϯτϩʔϥɺυϝΠϯΫϥεͳͲͷ͍ํ΄ͱΜ Ͳมߋͳ͠ • etc
ͱΓ͋͑ͣHello World!
ϓϩδΣΫτͷ࡞ ίϚϯυϥΠϯ͔ΒҎԼΛ࣮ߦ͢Δɻ $ grails create-app sample $ cd sample ࡞ͨ͠ΒIDEAͰಡΈࠐΉɻ
https://github.com/yamkazu/jggug-grails-bootcamp/blob/master/README.md Ͱ࣮ࢪࡁΈͷਓෆཁʂ
ίϯτϩʔϥͷ࡞ $ grails grails> create-controller hello GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͠ίϯτϩʔϥΛੜ͢Δɻ
ΞΫγϣϯͷ࣮ package sample class HelloController { def index()
{ render 'Hello Grails!' } } IDEA͔Βgrails-app/controllers/sample/HelloController.groovyΛ։͍ͯ ҎԼͷindexΞΫγϣϯΛՃ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-hellocontroller-groovy
ΞϓϦέʔγϣϯͷىಈ grails> run-app GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͠ΞϓϦέʔγϣϯΛىಈ ͢Δɻ ϒϥβͰ http://localhost:8080 ʹΞΫηε͢Δɻ sample.HelloControllerͷϦϯΫΛΫϦοΫ͢Δɻ 'Hello
Grails!'ͱදࣔ͞ΕΔ͜ͱΛ֬ೝ͢Δɻ
ΞϓϦέʔγϣϯͷఀࢭ grails> stop-app GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͢Δɻ
GrailsΛߏ͢Δཁૉ
ΞʔςΟϑΝΫτ • GrailsʹΑͬͯಛผѻ͍͞ΕΔΦϒδΣΫτͷ͜ͱ • ։ൃऀ͕هड़ͨ͠··ͷίʔυҎ֎ʹɺGrailsʹΑͬͯੵۃతʹػೳ͕Ճ͞ΕΔ • ී௨ͷΫϥεͱ֨ೲσΟϨΫτϦ͕͔Ε͍ͯΔ • grails-appԼ: ΞʔςΟϑΝΫτ
• srcԼ: ී௨ͷΫϥε • ओͳΞʔςΟϑΝΫτͷछྨ υϝΠϯΫϥε • υϝΠϯΫϥε • ίϯτϩʔϥ • αʔϏε • Ϗϡʔ • λάϥΠϒϥϦ
GrailsͷσΟϨΫτϦߏͷ࡞ yourAppProject grails create-appでこの配下の構造が生成される ├── build.gradle Gradleのビルド設定ファイル ├── gradle Gradle
WrapperのJarファイルが格納される ├── gradle.properties Gradleのプロパティファイル ├── gradlew Gradle Wrapperのスクリプトファイル ├── gradlew.bat Gradle Wrapperのスクリプトファイル ├── grails-app Grailsとして特別扱いをするクラス群を格納する │ ├── assets 静的リソースを格納する │ │ ├── images 画像ファイル │ │ ├── javascripts JavaScriptファイル │ │ └── stylesheets CSSファイル │ ├── conf 設定系 │ ├── controllers コントローラ │ ├── domain ドメインクラス │ ├── i18n メッセージバンドル │ ├── init 起動時に必要なクラス │ ├── services サービス │ ├── taglib タグライブラリ │ ├── utils コーデックと呼ばれる特殊クラスを配置する(滅多に使用することはない) │ └── views ビュー(GSPファイル) └── src ├── integration-test Grailsアプリケーションを内部で起動して実行するテスト │ └── groovy ├── main Grailsの特別扱いを必要としない通常のクラスを格納する │ ├── groovy │ └── webapp └── test 純粋なGroovy/Javaプログラムとしてのテストを格納 └── groovy
εΩϟϑΥϧυΛ ମݧͯ͠ΈΑ͏
υϝΠϯΫϥεͷ࡞ grails> create-domain-class book GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͠υϝΠϯΫϥεΛੜ͢ Δɻ
υϝΠϯΫϥεΛఆٛ͢Δ IDEAͰgrails-app/domain/sample/Book.groovyΛ։͍ͯҎԼΛهड़͢Δɻ package sample class Book { String
title Integer price static constraints = { } } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-book-groovy
εΩϟϑΥϧυͷ࣮ߦ grails> generate-all sample.Book GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͠ը໘Λੜ͢Δɻ ͍͔ͭ͘ͷϑΝΠϧ͕ੜ͞ΕΔɻ
ΞϓϦέʔγϣϯͷىಈ grails> run-app GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͠ΞϓϦέʔγϣϯΛىಈ ͢Δɻ ϒϥβͰ http://localhost:8080 ʹΞΫηε͢Δɻ sample.BookControllerͷϦϯΫΛΫϦοΫ͢Δɻ
ΠϯλϥΫςΟϒϞʔυͷऴྃ grails> exit GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͠ΠϯλϥΫςΟϒϞʔυ Λऴྃ͢Δɻ ΞϓϦέʔγϣϯ͕ىಈதͷ߹ࣗಈతʹఀࢭ͢Δɻ
TODOΞϓϦΛ࡞ͬͯΈΑ͏
ࠓճ࡞ΔΞϓϦͷΠϝʔδ
ϓϩδΣΫτΛ࡞ ίϚϯυϥΠϯ͔ΒҎԼΛ࣮ߦ͢Δɻ $ grails create-app todo $ cd todo ࡞ͨ͠ΒIDEAͰಡΈࠐΉɻ
IDEAͷจࣈίʔυͷઃఆ WindowsͷํҎԼઃఆ͔ΒจࣈίʔυΛUTF-8ʹઃఆ͍ͯͩ͘͠͞ɻ [File]-[Settings…] [Editor]-[File Encodings]
·ͣυϝΠϯΫϥεΛ࡞Δ
υϝΠϯΫϥε • ͍ΘΏΔϞσϧΛఆٛ͢ΔΫϥε • υϝΠϯΫϥε㲈 Hibernate༻ޠʮΤϯςΟςΟʯ • GORM(Groovy Object Relational
Mappingɺΰʔ ϜɺΰϧϜ) ΛυϝΠϯΫϥεΛհͯ͠ར ༻͢Δ • ೖྗͷ੍ΛఆٛͰ͖Δ • υϝΠϯΫϥεͷఆ͕ٛσʔλϕʔεͷϚοϐϯάఆٛʹͳΔ • Ϋϥε໊ -> ςʔϒϧ໊ • ϓϩύςΟ໊ -> ΧϥϜ໊ • ੍ -> ΧϥϜͷ੍
υϝΠϯΫϥεͷྫ class Person { // プロパティ String name // 氏名
Integer age // 年齢 // 制約 static constraints = { name size: 10..30 // 10〜30文字でなければならない age min: 18 // 18歳以上でなければならない } }
υϝΠϯΫϥεΛੜ͢Δ GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͢Δɻ $ grails grails> create-domain-class todo ҎԼͷϑΝΠϧ͕ੜ͞ΕΔɻ grails-app/domain/todo/Todo.groovy src/test/groovy/todo/TodoSpec.groovy
υϝΠϯΫϥεΛఆٛ͢Δ IDEAͰgrails-app/domain/todo/Todo.groovyΛ։͍ͯҎԼΛهड़͢Δɻ package todo class Todo { String
content static constraints = { content blank: false, maxSize: 20 } } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-todo-groovy
ΞϓϦέʔγϣϯΛىಈͯ͠σʔ λϕʔεΛ֬ೝ͢Δ GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͢Δɻ grails> run-app ϒϥβͰ http://localhost:8080/dbconsole ʹΞΫηε͢Δɻ ҎԼͷઃఆͰʮConnectʯΛΫϦοΫɻ JDBC
URL: jdbc:h2:mem:devDb User Name: sa
υϝΠϯΫϥεΛͬͯΞϓ Ϧέʔγϣϯىಈ࣌ʹσʔλ ϕʔεʹσʔλΛอଘ͢Δ
υϝΠϯϥΫεΛͬͨCURD ૢ࡞ͷجຊ • υϝΠϯΫϥεʹࣗಈతʹՃ͞ΕΔϝιουΛ͏ • อଘɺߋ৽ • domainInstance.save() • আ
• domainInstance.delete() • Ұཡऔಘ • DomainClass.list() • 1݅औಘ • DomainClass.get(id)
CRUDૢ࡞ͷྫ // 新規作成 def person = new Person(name: "山田", age:
20) person.save() def person = new Person(name: "山田", age: 20).save() // 参照 def person = Person.get(1) // IDを指定して取得 def people = Person.list() // 一覧を取得 def people = Person.list(offset: 10, max: 20) // 開始位置、件数を指定して一覧を取得 def people = Person.list(sort: "name", order: "asc") // ソート条件を指定して取得 int personCount = Person.count() // 件数を取得 // 更新 def person = Person.get(1) person.name = "鈴木" person.save() // 削除 def person = Person.get(1) person.delete()
ϒʔτετϥοϓ • GrailsΞϓϦέʔγϣϯͷىಈ࣌ͱऴྃ࣌ʹɺ؆୯ʹҙ ͷॲཧΛ࣮ߦͰ͖ΔΈ • grails-app/init/BootStrap.groovyʹ࣮͢Δ • init: ॳظԽ࣌ͷॲཧ •
destroy: ऴྃ࣌ͷॲཧ • ڥ͝ͱͷઃఆΛهड़Ͱ͖Δ
ڥ • ࣮ߦڥʹԠͯ͡ઃఆɺ෦ͷॲཧΛΓସ͑ΔͨΊͷΈ • ઃఆϑΝΠϧBootstrapͳͲͰσϑΥϧτͰར༻Ͱ͖Δ • σϑΥϧτͰ༻ҙ͞Ε͍ͯΔڥҎԼͷ3ͭ • development •
run-appίϚϯυͰىಈͨ͠ͱ͖ͷڥ • ͘͘͞͞ͱ։ൃͰ͖ΔΑ͏ʹɺࣗಈϦϩʔυΩϟογϡͷແޮԽ͞ΕΔ • test • test-appͳͲͰςετΛ࣮ߦͨ͠ͱ͖ͷڥ • production • warίϚϯυͳͲͰੜ͞ΕͨϑΝΠϧΛىಈͨ͠ͱ͖ͷڥ
ઃఆϑΝΠϧͰͷ༻ྫ environments: development: dataSource: dbCreate: create-drop url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE test: dataSource:
dbCreate: update url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE production: dataSource: dbCreate: update url: jdbc:h2:./prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
Bootstrap.groovyͰͷ༻ྫ class BootStrap { def init = { servletContext
-> environments { development { // 開発時の初期化処理 } test { // テスト時の初期化処理 } production { // 本番環境での初期化処理 } } } ... }
ىಈ࣌ʹςετσʔλΛೖ͢Δ grails-app/init/BootStrap.groovyʹҎԼΛهड़͢Δɻ import todo.Todo class BootStrap { def
init = { servletContext -> environments { development { new Todo(content: "山田さんに電話").save() new Todo(content: "ゴミ袋を買う").save() new Todo(content: "ネコに餌をやる").save() } } } … } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-bootstrap-groovy
࠶ىಈͯ͠ಈ࡞Λ֬ೝ GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͢Δɻ grails> stop-app grails> run-app ϒϥβͰdbconsoleΛ։͘ɻ TodoͷσʔλΛ֬ೝ͢Δɻ
ը໘ʹTODOϦετ Λදࣔ͢Δ
ίϯτϩʔϥͷ֓ཁ • ίϯτϩʔϥͱWebϒϥβ͔ΒͷϦΫΤ ετΛड͚ͯɺϨεϙϯεΛฦ͢Ұ࿈ͷॲཧ Λఆٛ͢ΔΫϥε • ϝιου͝ͱʹݸผͷॲཧΛఆٛ͢Δ͜ͱ͕ Ͱ͖ɺ͜ͷϝιουΛΞΫγϣϯͱݺͿ
ίϯτϩʔϥͱΞΫγϣϯͷఆٛྫ class BookController { def index() { ... } def
list() { ... } def show() { ... } }
ΞΫγϣϯͷத͔ΒϨεϙϯεΛฦ͢ • renderϝιουΛ͏ • ༷ʑͳλΠϓͷϨεϙϯεΛฦ͢͜ͱ͕Ͱ͖Δ • ςΩετ • ҙͷϏϡʔΛࢦఆͯ͠ը໘Λදࣔͨ͠Γ •
JSONΛฦ٫ • etc • respondϝιουΛ͏ • AcceptURLͷ֦ுࢠͳͲΛͬͯσʔλΛదͳϑΥʔϚοτͰϨϯμϦϯάͯ͘͠ΕΔ • http://grails.github.io/grails-doc/latest/ref/Controllers/respond.html • ΞΫγϣϯͰϨεϙϯεΛࢦఆ͠ͳ͔ͬͨ߹ίϯτϩʔϥ໊ɺΞΫγϣϯ໊͔ΒࣗಈతʹϏϡʔ͕બ͞ ΕΔ • ྫ: BookControllerͷshowΞΫγϣϯͷ߹ɺgrails-app/views/book/show.gsp͕Ϗϡʔ͕༻͞ΕΔ
renderϝιουͷ༻ྫ class BookController { def action1() { // 何も指定しない //
grails-app/views/book/action1.gspが使われる } def action2() { // grails-app/views/book/display.gspが使われる render(view: 'display') } def action3() { // grails-app/views/shared/display.gspが使われる render(view: '/shared/display') } def action4() { // 文字列を表示 render 'Hello World!' } def action5() { // Bookの一覧をJSONで表示 render Book.list() as JSON // grails.converters.JSON } }
ΞΫγϣϯ͔ΒϏϡʔʹΛ͢ • ͍͔ͭ͘Γํ͕͋Δ͕جຊతͳํ๏ҎԼ ͷ̎ͭ • ΞΫγϣϯ͔ΒMapͷΠϯελϯεΛฦ͢ • renderͷҾͰmodelΛࢦఆ͢Δ
modelͷࢦఆྫ // アクションからMapのインスタンスを返す def show() { [message: 'hello'] }
// renderの引数でmodelを指定する def show() { render(view:'display', model: [message: 'hello']) }
Ϗϡʔ(GSP) • GrailsͰϏϡʔͷ࣮ͱͯ͠GSP(Groovy Server Pages)Λ͏ • ؆୯ʹݴ͏ͱJSP(JavaServer Page)ͷGroovy൛ • ${expr}ͱ͍ͬͨܗͰGSPͷதͰGroovyͷࣜΛॻ͚Δ
• σϑΥϧτͰ༻ҙ͞ΕͨλάϥΠϒϥϦ͕͑Δ • λάϥΠϒϥϦͷҰཡ http://grails.github.io/grails- doc/latest/ ͷӈʹදࣔ͞Ε͍ͯΔTagsΛࢀর
GSPͷྫ <!DOCTYPE html> <html> <head> <meta name="layout" content="main"/> <title>Render Domain</title>
</head> <body> <table> <tr> <td>Name</td> <td>Age</td> </tr> <g:each in="${list}" var="person"> <tr> <td>${person.lastName}, ${person.firstName}</td> <td>${person.age}</td> </tr> </g:each> </table> </body> </html>
ίϯτϩʔϥΛੜ͢Δ GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͢Δɻ grails> create-controller todo ҎԼͷϑΝΠϧ͕ੜ͞ΕΔɻ grails-app/controllers/todo/TodoController.groovy src/test/groovy/todo/TodoControllerSpec.groovy
ίϯτϩʔϥΛ࣮͢Δ IDEAͰgrails-app/controllers/todo/TodoController.groovyΛ։͍ͯҎԼΛ هड़͢Δɻ package todo class TodoController {
def index() { [todos: Todo.list()] } } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-todocontroller1-groovy
ϏϡʔΛ࣮͢Δ IDEAͰgrails-app/views/todoΛӈΫϦοΫͯ͠[New]-[File]͔Βindex.gsp ͱ͍͏ϑΝΠϧΛ࡞͢Δɻ ҎԼͷ༰Λهड़͢Δɻ <!doctype html> <html> <head> <meta charset="UTF-8">
<title>MYTODO</title> </head> <body> <h2>TODOリスト</h2> <ul> <g:each in="${todos}" var="todo"> <li>${todo.content}</li> </g:each> </ul> </body> </html> https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-index1-gsp
TODOΛՃ/আ Ͱ͖ΔΑ͏ʹ͢Δ
ը໘͔ΒύϥϝʔλΛड͚औΔ • جຊతͳΓํҎԼ • ҉ͷมparamsͷ༻ • ϦΫΤετύϥϝʔλΛΞΫγϣϯͷҾʹࢦఆ͢Δ • ίϚϯυΦϒδΣΫτΛΞΫγϣϯͷҾʹࢦఆ͢Δ •
υϝΠϯΫϥεΛίϚϯυΦϒδΣΫτͱͯ͠͏͜ͱͰ͖Δ • υϝΠϯΫϥεΛࢦఆ͞Ε͍ͯΔɺ͔ͭϦΫΤετύϥϝʔλͷ தʹidͷύϥϝʔλ͕͋ΕࣗಈతʹͦͷidʹରԠ͢ΔσʔλΛऔ ಘͯ͘͠ΕΔ
paramsͷྫ def save() { new Person(name: params.name, age: params.int('age')).save() new
Person(params).save() } def show() { [person: Person.get(params.id)] }
ϦΫΤετύϥϝʔλΛΞΫγϣ ϯͷҾʹ͢Δྫ def save(String name, Integer age) { new Person(name,
age).save() } def show(Long id) { [person: Person.get(id)] }
υϝΠϯΫϥεΛΞΫγϣϯͷ Ҿʹ͢Δྫ def save(Person person) { person.save() } def
show(Person person) { [person: person] }
save/deleteΞΫγϣϯΛ࣮͢Δ grails-app/controllers/todo/TodoController.groovyʹsaveΞΫγϣϯͱ deleteΞΫγϣϯΛՃ͢Δɻ package todo class TodoController { …
def save(Todo todo) { todo.save(flush: true) redirect action: 'index' } def delete(Todo todo) { todo.delete(flush: true) redirect action: 'index' } } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-todocontroller2-groovy
Ճ/আͷը໘Λ࣮͢Δ grails-app/views/todo/index.gspʹҎԼΛՃ͢Δɻ … <body> <g:form action="save"> <g:textField name="content" /> <g:submitButton
name="create" value="作成" /> </g:form> <h2>TODOリスト</h2> <ul> <g:each in="${todos}" var="todo"> <li> ${todo.content} <g:form action="delete" id="${todo.id}"> <g:submitButton name="delete" value="削除" /> </g:form> </li> </g:each> </ul> … https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-index2-gsp
ը໘ͰೖྗνΣοΫΛͰ͖ΔΑ ͏ʹ͢Δ
੍ • υϝΠϯΫϥεͷconstraintsϒϩοΫʹఆٛ͢Δ • nullable:false͚ͩσϑΥϧτͰઃఆ͞ΕΔ • ੍ҎԼͷ3ͭͰओʹར༻͞ΕΔ • όϦσʔγϣϯ •
੍ͷϝΠϯͷ༻్ • Grails͕ߦ͏ೖྗͷόϦσʔγϣϯʹΘΕΔ • σʔλϕʔεͷεΩʔϚ • dbCreate͕createͳͲͷ߹ʹHibernateʹΑͬͯࣗಈੜ͞ΕΔσʔλϕʔεʹ͓͍ͯɺΧϥϜͷܕα ΠζɺNOT NULLͳͲͷ੍ʹΘΕΔ • εΩϟϑΥϧυ • ࣗಈੜ͞ΕΔϏϡʔͷϑΥʔϜཁૉͷछྨଐੑʹΘΕΔ
੍ͷྫ static constraints = { age nullable:false username blank: false
type inList: ["Commercial", "Personal"] username unique:true username matches: /[a-zA-Z]/ password validator: { value, self -> ... } children minSize:5, maxSize:25 age min:0, max:120 mailAddress email: true webSite url: true username notEqual:'root' age range:0..120 price scale:2 children size:5..25 cardNumber creditCard: true }
όϦσʔγϣϯ • ੍ͷఆٛΛجʹೖྗνΣοΫΛ࣮ࢪ͢Δ • όϦσʔγϣϯؔ࿈ͷϝιουυϝΠϯΫϥεʹGrails͕ࣗಈతʹՃ͢Δ • ໌ࣔతͳݺͼग़͠ • domainInstance.validate() •
҉తͳݺͼग़͠ • domainInstance.save()ͷ࣮ߦͨ͠߹ • ίϯτϩʔϥͷΞΫγϣϯͷҾʹυϝΠϯΫϥεΛࢦఆͨ͠߹ • όϦσʔγϣϯ࣮ࢪޙɺΤϥʔ͕͋Δ߹domainInstance.hasErrors()͕trueΛฦ͢
όϦσʔγϣϯͷ༻ྫ // 明示的なバリデーションの実行 def save() { def book = new
Book(params) book.validate() // 明示的に実行 if (book.hasErrors()) { ... } ... } // saveを実行した場合 def save() { def book = new Book(params).save() // 暗黙的にバリデーションを実行ずみ if (book.hasErrors()) { ... } ... } // コントローラのアクションの引数にドメインクラスを指定した場合 def save(Book book) { // 暗黙的にバリデーションを実行ずみ if (book.hasErrors()) { ... } ... }
όϦσʔγϣϯΤϥʔͷ߹ೖ ྗը໘Λදࣔ͢Δ grails-app/controllers/todo/TodoController.groovyʹҎԼΛՃ͢Δɻ class TodoController { … def save(Todo todo)
{ if (todo.hasErrors()) { render view: 'index', model: [todo: todo, todos: Todo.list()] return } todo.save(flush: true) redirect action: 'index' } … } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-todocontroller3-groovy
ೖྗΤϥʔΛදࣔ͢Δ … <body> <g:hasErrors bean="${todo}"> <g:renderErrors bean="${todo}" /> </g:hasErrors> <g:form
action="save"> <g:textField name="content" value="${task?.content}" /> <g:submitButton name="create" value="作成" /> </g:form> <h2>TODOリスト</h2> … grails-app/views/todo/index.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-index3-gsp
ϝοηʔδΛΧελϚΠζ͢Δ
i18n (internationalization; ࠃࡍԽ) • ༷ʑͳݴޠͷϝοηʔδΛूཧ͠ɺWebϒϥβͷݴޠ ઃఆαʔό্ͷݴޠઃఆͳͲΛجʹɺదͳݴޠͷϝοηʔ δΛద༻͢ΔΈ • grails-app/i18nσΟϨΫτϦԼʹϩέʔϧ͝ͱͷϓϩύςΟ ϑΝΠϧͰཧ͢Δ
• λάϦϒͷg:messageΛͬͯϝοηʔδΛࢀরͰ͖Δ • όϦσʔγϣϯͷσϑΥϧτϝοηʔδͷఆٛʹΘ͍ͯΔ
ϝοηʔδϑΝΠϧͷྫ my.sample.message.hello = こんにちは、i18n。 my.sample.message.withArgs = 引数1番目:{0}, 2番目:{1}, 3番目:{2}
ϝοηʔδͷࢀরྫ // コントローラで使う def show() { println message(code: 'my.sample.message.hello') println
message(code: 'my.sample.message.withArgs', args: ['Grails', 'Groovy', 'Advocate']) ... } // GSPの内でタグライブラリを使う <g:message code="my.sample.message.hello" /> <g:message code="my.sample.message.withArgs" args="${ ['Grails', 'Groovy', 'Advocate'] }" /> // GSPの評価式の中で使う ${message(code: 'my.sample.message.hello')} ${message(code: 'my.sample.message.hello', args=['Grails', 'Groovy', 'Advocate'])}
όϦσʔγϣϯͷϝοηʔδΛม ߋ͢Δ grails-app/i18n/messages_ja.propertiesͰҎԼͷΑ͏ʹϝοηʔδΛมߋ ͢Δɻ default.invalid.max.size.message={1}の{0}は{3}文字以内で入力してください。 default.null.message={1}の{0}が入力されていません。 todo.label=TODO todo.content.label=内容 ಉ͡ϑΝΠϧʹҎԼͷϝοηʔδΛՃ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-messages1-properties
ը໘ͷϝοηʔδΛఆٛ͢Δ app.name=MYTODO grails-app/i18n/messages_ja.propertiesʹҎԼͷϝοηʔδΛՃ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-messages2-properties
ϝοηʔδΛࢀর͢Δ … <head> <meta charset="UTF-8"> <title><g:message code="app.name" /></title> </head> <body>
… <g:form action="save"> <g:textField name="content" value="${task?.content}" /> <g:submitButton name="create" value="${message(code: 'default.button.create.label')}" /> </g:form> <h2><g:message code="default.list.label" args="${[message(code: 'todo.label')]}"/></h2> <ul> <g:each in="${todos}" var="todo"> <li> ${todo.content} <g:form action="delete" id="${todo.id}"> <g:submitButton name="delete" value="${message(code: 'default.button.delete.label')}" /> </g:form> … grails-app/views/todo/index.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-index4-gsp
TODOͷߜΓࠐΈػೳΛ࣮͢Δ
ΫΤϦ • GrailsͰɺσʔλϕʔεʹݕࡧΫΤϦΛൃߦ͢ΔͨΊͷ༷ʑͳํ๏͕ఏڙ͞ Ε͍ͯΔ • μΠφϛοΫϑΝΠϯμ • ΫϥΠςϦΞ • whereΫΤϦ
• HQL (Hibernate Query Language) • ໊લ͖ΫΤϦ • ωΠςΟϒSQL
μΠφϛοΫϑΝΠϯμ • ໋໊نଇʹԊͬͨϝιουݺͼग़͠Λ͢ΔͱࣗಈతʹΫΤϦΛൃߦͯ͘͠ΕΔػೳ • Ұ൪ੲ͔Β͋ΔΫΤϦͷػೳ • جຊύλʔϯҎԼͷ3छ • findBy* •
ࢦఆ͞Εͨ݅ʹҰக͢Δ࠷ॳͷ1݅Λฦ͢ • findAllBy* • ࢦఆ͞Εͨ݅ʹҰக͢Δͯ͢Λฦ͢ • countBy* • ࢦఆ͞Εͨ݅ʹҰக͢ΔϨίʔυ݅Λฦ͢ • ϝιου໊ͷʮ*ʯͷ෦ʹݕࡧ݅Λࢦఆ͢Δ • ରԠ͢Δϝιου͕͋Β͔͡Ίఆٛ͞Ε͍ͯΔ༁Ͱͳ͍ • ࢦఆ͞Εͨϝιου໊͔Βಈతʹݕࡧ݅Λஅͯ͠ΫΤϦΛ࣮ߦ͢Δ
μΠφϛοΫϑΝΠϯμͷྫ Book.findAllByTitleAndAuthor("The Hoge", "Mike Davis") Book.findAllByReleaseDateBetween(firstDate, new Date()) Book.findAllByReleaseDateGreaterThanEquals(firstDate) Book.findAllByTitleLike("%Hobbit%")
Book.findAllByTitleIlike("%Hobbit%") // ignore case Book.findAllByTitleNotEqual("Harry Potter") Book.findAllByReleaseDateIsNull() Book.findAllByReleaseDateIsNotNull() Book.findAllPaperbackByAuthor("Douglas Adams") Book.findAllNotPaperbackByAuthor("Douglas Adams") Book.findAllByAuthorInList(["Douglas Adams", "Hunter S. Thompson"])
ΫϥΠςϦΞ • HibernateͷCriteria APIΛϥοϓͨ͠GroovyͷDSL Ͱɺෳࡶͳݕࡧ݅ΛߏஙͰ͖Δ • ରυϝΠϯΫϥεʹର͢ΔCriteriaΦϒδΣΫτΛੜ ͯ͠ɺͦ͜ʹ݅Λ༩͍ͯ͘͠ • ੜʹDomainClass.createCriteria·ͨ
DomainClass.withCriteriaϝιουΛ͏ • ifจͳͲͷGroovyίʔυ͕ී௨ʹ͑ΔͨΊɺಛఆͷ ߹ͷΈ༗ޮͳ݅Λࢦఆ͢Δ͜ͱ؆୯
ΫϥΠςϦΞͷྫ def c = Book.createCriteria() def results = c.list {
def now = new Date() between('releaseDate', now - 7, now) like('title', '%Groovy%') } def results = Book.withCriteria { def now = new Date() between('releaseDate', now - 7, now) // 管理者以外の場合は公開された本だけに限定する if (!person.isAdmin()) { eq('available', true) } like('title', '%Groovy%') }
WhereΫΤϦ • GroovyͷASTมΛ׆༻ͨ͠ΫΤϦͷ Έ • ίϯύΠϧ࣌ʹ੩తʹܕνΣοΫ͕Մೳ • ݅ࣜΛॻ͘Α͏ʹΫΤϦͷ݅Λهड़ Ͱ͖Δ •
Ұ൪৽͍͠
WhereΫΤϦͷྫ Person.where { name == "Bart" }.list() Person.where { (name
== "Bart") && (age == 35) }.list() Person.where { (name == "Bart") || (age > 18) }.list() Person.where { age in 18..65 }.list() Person.where { name ==~ /%yamada%/ }.list()
Ωʔϫʔυ͕ࢦఆ͞Εͨ߹Ωʔ ϫʔυͰݕࡧ͢Δ grails-app/controllers/todo/TodoController.groovyʹҎԼΛՃ͢Δɻ class TodoController { def index(String keyword)
{ if (keyword) { return [todos: Todo.where { content ==~ /%$keyword%/ }.list()] } [todos: Todo.list()] } … } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-todocontroller4-groovy
ΩʔϫʔυΛೖྗͰ͖ΔΑ͏ʹ͢Δ … <h2><g:message code="default.list.label" args="${[message(code: 'todo.label')]}"/></h2> <g:form action="index"> <g:textField
name="keyword" value="${params.keyword}"/> <g:submitButton name="filter" value="絞り込み"/> </g:form> <ul> <g:each in="${todos}" var="todo"> <li> ${todo.content} <g:form action="delete" id="${todo.id}"> <g:submitButton name="delete" value="${message(code: 'default.button.delete.label')}" /> </g:form> </li> </g:each> </ul> … grails-app/views/todo/index.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-index5-gsp
ڞ௨ͷϨΠΞτΛఆٛ͢Δ
SiteMesh • GrailsͰSiteMeshΛͬͯɺϏϡʔͷϨΠΞτ Λߦ͍ͬͯΔ • ༻ྫ: ϔομϑολɺαΠυόʔͳͲΛఆٛ͠ ͨϨΠΞτϑΝΠϧΛఆٛ͢Δ • ϨΠΞτgrails-app/views/layoutsσΟϨΫτϦ
ʹஔ͢Δ • ϨΠΞτϑΝΠϧΛ͏ଆhtmlͷmetaλάΛ ͬͯlayoutsσΟϨΫτϦͷϑΝΠϧ໊Λࢦఆ͢Δ
ϨΠΞτϑΝΠϧͷఆٛྫ <!doctype html> <html> <head> <title><g:layoutTitle default="Grails"/></title> <asset:stylesheet src="application.css"/> <asset:javascript
src="application.js"/> <g:layoutHead/> </head> <body> <header>My App</header> <g:layoutBody/> <footer>Copyright 2015</footer> </body> </html>
ϨΠΞτϑΝΠϧͷࢦఆྫ <!doctype html> <html> <head> <meta name="layout" content="main" /> <title>My
Page</title> </head> <body> <p>Hello Grails!</p> </body> </html>
ϨΠΞτϑΝΠϧΛఆٛ͢Δ grails-app/views/layouts/main.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ <!doctype html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible"
content="IE=edge"> <title><g:layoutTitle default="${message(code: 'app.name')}"/></title> <g:layoutHead/> </head> <body> <h1><g:message code="app.name" /></h1> <g:layoutBody/> </body> </html> https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-main1-gsp
ϨΠΞτϑΝΠϧΛࢦఆ͢Δ <!doctype html> <html> <head> <meta name="layout" content="main"/> <title><g:message code="app.name"
/></title> </head> … grails-app/views/todo/index.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-index6-gsp
URLϚοϐϯάΛΧελϚΠζ ͢Δ
URLϚοϐϯά • URLϚοϐϯάΛΧελϚΠζ͢Δ͜ ͱͰɺURLͱίϯτϩʔϥ/ΞΫγϣ ϯ/ϏϡʔͷؔΛࣗ༝ʹઃఆͰ͖Δ • grails-app/controllers/ UrlMappings.groovyʹఆٛ͢Δ
URLϚοϐϯάͷྫ class UrlMappings { static mappings = { //
デフォルトのマッピング設定 "/$controller/$action?/$id?(.$format)?"{ constraints { // apply constraints here } } // controllerとactionを指定 "/product"(controller: "product", action: "list") "/product"(controller: "product") // viewを指定した設定 "/"(view:"/index") // ステータスコードを指定した設定 "500"(controller: "errors", exception: MyException) "500"(view: "/errors/serverError", exception: MyAnotherException) "500"(view:'/error') "404"(view:'/notFound') } }
ϗʔϜը໘ΛTODOʹ͢Δ grails-app/controllers/UrlMappings.groovyΛҎԼͷΑ͏ʹมߋ͢Δɻ class UrlMappings { static mappings = {
"/$controller/$action?/$id?(.$format)?"{ constraints { // apply constraints here } } "/"(controller: 'todo') "500"(view:'/error') "404"(view:'/notFound') } } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-urlmappings-groovy
੩తϦιʔεΛ͏
Asset Pipeline ϓϥάΠϯ • JavascriptɺελΠϧγʔτɺը૾ͱ͍ͬͨ੩తϦιʔεΛཧ͢ ΔϓϥάΠϯ • Rails͔ΒͷΠϯεύΠΞ͞Ε։ൃ͞Εͨ • ҎԼͷػೳΛఏڙ͢Δ
• Ξηοτͷ݁߹ɺ࠷খԽɺѹॖ • ϑΟϯΨʔϓϦϯτ • CoffeeScriptSASSͱ͍ͬͨΞηοτͷίϯύΠϧ • Ξηοτgrails-appԼͷassets/javascriptsɺassets/ stylesheetsɺassets/imagesσΟϨΫτϦʹ֨ೲ͢Δ
ϚχϑΣετͱσΟϨΫςΟϒ • Asset PipelineΛ͏ʹϚχϑΣετϑΝΠϧΛఆٛͯ͠ɺͦΕΛϏϡʔ͔ΒಡΈࠐΉ • ϚχϑΣετෳͷϦιʔεΛవΊΔͨΊͷఆٛ • ΤϯτϦʔϙΠϯτͷϑΝΠϧͷΑ͏ͳͷ • σΟϨΫςΟϒͦͷϚχϡϑΣετͷதͰϦιʔεΛࢦఆ͢ΔͨΊͷه๏
• ϚχϑΣετͷதͰίϝϯτͱͯ͠هड़͢Δ • Α͘͏σΟϨΫςΟϒҎԼͷ3ͭ • require • ࢦఆ͞ΕͨϦιʔεΛಡΈࠐΉ • require_tree • ࢦఆ͞ΕͨύεͷϦιʔεΛ࠶ؼతʹಡΈࠐΉ • require_self • ࣗͷϦιʔεΛಡΈࠐΉ
CSSͷϚχϑΣετͷྫ /* *= require jquery *= require main *= require_self
*/ body { color: red; }
Ϗϡʔ͔ΒͷಡΈࠐΈྫ <!doctype html> <html> <head> <title><g:layoutTitle default="Grails" /></title> <asset:stylesheet src="application.css"
/> <asset:javascript src="application.js" /> <g:layoutHead/> </head> <body> <header>My App</header> <g:layoutBody/> <footer>Copyright 2015</footer> </body> </html>
JavaScriptɺCSSͷϥΠϒϥϦΛ Asset PipelineͰ͏ • ҎԼͷ3ͭͷํ๏͕͋Δ • ϑΝΠϧΛμϯϩʔυͯ͠grails-app/assets σΟϨΫτϦʹखಈͰల։͢Δ • GrailsͷϓϥάΠϯΛ͏
• WebJarsΛ͏ • http://www.webjars.org/
WebJarsΛͬͯTwitter BootstrapͷϦιʔεΛऔಘ͢Δ build.gradleʹҎԼΛՃ͢Δɻ … dependencies { … console "org.grails:grails-console"
provided "org.webjars.bower:bootstrap:3.3.5" } … https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-build-gradle
ϚχϑΣετΛఆٛ͢Δ grails-app/assets/stylesheets/application.cssΛҎԼͷΑ͏ʹมߋ͢Δɻ /* *= require webjars/bootstrap/3.3.5/dist/css/bootstrap *= require_self */ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-application-css
ϨΠΞτϑΝΠϧ͔ΒϦιʔε ΛಡΈࠐΉ grails-app/views/layouts/main.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ <!doctype html> <html> <head> <meta charset="UTF-8"> <meta
http-equiv="X-UA-Compatible" content="IE=edge"> <title><g:layoutTitle default="${message(code: 'app.name')}" /></title> <asset:stylesheet src="application.css"/> <asset:javascript src="application.js"/> <g:layoutHead/> </head> <body> <h1><g:message code="app.name" /></h1> <g:layoutBody/> </body> </html> https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-main2-gsp
Twitter BootstrapͷελΠϧద༻ ͢Δ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-index7-gsp grails-app/views/todo/index.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ grails-app/views/layouts/main.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-main3-gsp
͕࣌ؒ͋·ͬͨ߹ͷΞυϦϒωλީิ • ςετ • αʔϏε • Πϯλʔηϓλʔ • ϩά •
Ϗϧυ • ϓϥάΠϯ
ࢀߟใ
Grails http://grails.github.io/grails-doc/latest/ ຊՈϦϑΝϨϯε http://grails.jp/doc/latest/ ຊޠ༁
Groovy http://gihyo.jp/book/2011/978-4-7741-4727-7 ϓϩάϥϛϯάGROOVY
Spring Boot http://www.amazon.co.jp/dp/4777518655 ͡ΊͯͷSpring Boot http://projects.spring.io/spring-boot/ ຊՈϦϑΝϨϯε
Gradle http://www.amazon.co.jp/dp/4798136433/ Gradleపఈೖ http://gradle.monochromeroad.com/docs/ ϦϑΝϨϯεͷ༁
Q & A