scaffoldã䜿ããšCRUDãæã£ããåå°ããäžçºã§äœããããã§ããããããã¢ã¬ã³ã¬ããŠå
šãŠã®æäœãAjaxåããŠã¿ãã®ã§ãæé ããŸãšããŸããã
èšäºãèªãã®ãã ããã£ãŠæ¹ã¯ãœãŒã¹ã³ãŒããGitHubã§å ¬éããŠãã®ã§ããã¡ããã芧ãã ããã
RubyãRailsããããŸãè§Šã£ãäºããªãã®ã§ãèè ã«ããããã³ãæè¿ããŸãã (ïœã»Ïã»ÂŽ)
æŠèŠ
ããããš
indexã®ç»é¢ã ãã§CRUDãã€ãŸãæ°èŠäœæ (Create)ã衚瀺 (Read)ãç·šé (Update)ãåé€ (Delete)ãè¡ãããããscaffoldã§äœæãããã¡ã€ã«ãããããŸãã
çµè«
form_for()
ã«remote: true
ãäžããã ãã§ããšããããAjaxåããŸããããšã¯ãµãŒããŒåŽã®ã¬ã¹ãã³ã¹ã®å
å®¹ãæŽããŠãã¯ã©ã€ã¢ã³ãåŽã§é©åã«åŠçããŠããã°OKã§ãã
äœæ¥
- äžæºåïŒ
scaffold
ãšãïŒ - indexã«ç·šéãã©ãŒã ãåã蟌ã¿
- ç·šéãã©ãŒã ãAjaxå
- Ajaxã§åããæ°èŠäœæãã©ãŒã ãäœæ
- Ajaxã§åããåé€ãã¿ã³ãäœæ
ãœãŒã¹ã³ãŒã
GitHubã§å ¬éããŠããŸããæ®µéäºã«ã³ãããããŠããã®ã§ãåã³ãããã®å·®åãèŠããšãããããããšæããŸãã
- ginpei/20111128_ajax_on_rails – GitHub
- Commit History for ginpei/20111128_ajax_on_rails – GitHub ïŒå·®åïŒ
ç°å¢
- ruby 1.9.2
- Rails 3.1.2
- jQuery 1.7.1
jQuery 1.7ã§è¿œå ããã .on()
ã䜿ã£ãŠããã§ã1.6ãšãã ãšåããªãã§ãããŸãåæã«ææ°ã®ãã®ãå
¥ãã¯ãã§ããã©ã
æ¬èšäºãèªãäžã§ã®æ³šæç¹
ç·šéã®éœåã«ããã€ã³ãã³ããæ¬ èœããéšåããããŸããCoffeeScriptã§ã¯ã€ã³ãã³ããæå³ãæã£ãŠããã®ã§ãã³ããã ãšãã®å Žååããªãã©ããããšã©ãŒã«ãªããŸããé©å®è£å®ããŠãã ããã
ïŒSyntaxHighlighter Evolvedã䜿ã£ãŠããã§ããã©ãã©ãã«ããªããªãã§ãããïŒïŒ
äžæºå
ããã¯è§£èª¬çç¥ã
rails new memobook cd memobook rails g scaffold memo body:string rake db:migrate
http://localhost:3000/memos
ãšãããããéããšã空ã®äžèЧã衚瀺ãããŸããä»ããã³ãŒããããã£ãããã°ããäœæç»é¢ã«ã¢ã¯ã»ã¹ã§ããªããªãã®ã§ãé©åœã«å¹Ÿã€ãããŒã¿ã远å ããšããŠãã ããã
indexã«ç·šéãã©ãŒã ãåã蟌ã¿
äžèЧã®ç»é¢ã«å€æŽãã©ãŒã ã远å ããŸããæ¢åã®ãã¿ã³é¡ã¯ãããªãããåé€ããšããããã£ã€ãæžãæããŸããããšãã衚瀺ã¢ãŒãããšãç·šéã¢ãŒãããåãæ¿ããäºãæ³å®ã
ãã©ãŒã æ§é
app/views/memos/index.html.erb
<h1>Listing memos</h1> <div id="memos"> <% @memos.each do |memo| %> <div class="memo"> <div class="viewer"> <span class="body"><%= memo.body %></span> <%= button_tag 'Edit', class: 'edit', type: :button %> </div> <div class="editor"> <div> <%= button_tag 'Cancel', class: 'cancel', type: :button %> </div> <%= render 'form', memo: memo %> </div> </div> <% end %> </div> <br /> <%= link_to 'New Memo', new_memo_path %>
app/views/memos/_form.html.erb
<%= form_for memo do |f| %> <%= f.text_field :body, id: nil, class: 'body' %> <%= f.submit 'Update' %> <% end %>
äžå¿åäœç¢ºèªããŠã¿ãŸããããã倿ŽããŠUpdateãããšãåç
§ç»é¢ã«é·ç§»ããŠMemo was successfully updated.
ãšè¡šç€ºãããã
Ajaxã¯ãŸã ã ãã©ããšãããããã©ãŒã ã¯ããã§ãããããç¶ããŠè¡šç€ºãåãæ¿ããããããã«ããŸãã
ãã©ãŒã æäœ
“Edit”ãã¿ã³ãš”Cancel”ãã¿ã³ãæŒãããšãã衚瀺çšã®é åãšç·šéçšã®é åãããããåãæ¿ããããã«ããã
ãã£ãããªã®ã§ãCoffeeScript + jQueryã§ããæ®éã®JavaScriptãè¯ãã£ãŠäººã¯ããã¡ã€ã«åãmemos.js
ã«å€æŽããŠãå
容ãé©åœã«æžãçŽããŠãã ããã
app/assets/javascripts/memos.js.coffee
$ -> $('#memos') .on 'click', '.edit, .cancel', (event) -> # 衚瀺ãåãæ¿ã toggleEditor $(this).closest('.memo') # 衚瀺ã¢ãŒããšç·šéã¢ãŒããåãæ¿ããã toggleEditor = ($container) -> # 衚瀺ãé衚瀺ãåãæ¿ã $container.find('.viewer, .editor').toggle() # ç·šéã¢ãŒããªããå€ãæ»ã $bodyField = $container.find('.editor .body') if $bodyField.is(':visible') $bodyField .val($container.find('.viewer .body').text()) .select()
ããšã¹ã¿ã€ã«ã·ãŒãããã
app/assets/stylesheets/memos.css.scss
.memo { border: solid 1px #eee; margin: 10px; padding: 10px; } .memo .editor { display: none; }
ããã§ã±ãã±ã衚瀺ãåãæ¿ããããããã«ãªããŸãããã¡ãªã¿ã«ããã¯AjaxãããªããŠDHTMLãšåŒã°ããŸãã
ç·šéãã©ãŒã ãAjaxå
Ajaxå
ãã©ãŒã ãéåæéä¿¡ã§åŠçããããã«ããã«ã¯ãform_for()
ã«remote: true
ãäžããã ãããã²ããŒãç°¡åãããïŒïŒ
app/views/memos/_form.html.erb
<%= form_for memo, remote: true do |f| %>
äžå¿ãããã ãã§ãã©ãŒã ããAjaxåãå®äºã§ãã
詊ããŠã¿ãŠãã ãããã¡ãããšç»é¢é·ç§»ãªãã§å€ãæŽæ°ãããŸãïŒïŒãâŠâŠããŸããã©ãäœã衚瀺ãããªããæåã§ç»é¢ãåèªã¿èŸŒã¿ããªããšå€ãå€ãã£ããã©ãããããããªããåèªã¿èŸŒã¿ãããšå€ãã£ãŠããã ãã©ãã
ãŸããšãããããã£ãããã ãã§Ajax䜿ããŸããããšã
ãããããããããã倧å€ã ãïŒ
Ajaxãšã¯JavaScriptã§XHRã䜿ã£ãŠãµãŒããŒãšéåæéä¿¡ãè¡ããç»é¢é·ç§»ãªãã§åŠçãå®çµãããäºãïŒã£ãŠäºã«ããŠãããŠãã ãããïŒãã€ãŸããã®ãããã¯JavaScriptã§åŠçãããããæžãã€ã€ããããšãè¡ããµãŒããŒåŽã®Rubyããã£ã±ãæžããŠãã£ãŠäºã«ãªããŸãã
ãããããã°ããã¯ãäžæ©ãã€é²ããŠãããŸããçµè«ã ãèŠãã人ã¯ã¡ãã£ãšé£ã°ããŠãã
éä¿¡ãæŸã
ãšãããããå
·äœçãªåŠçã¯çœ®ããŠãããŠãéä¿¡ãæŸã£ãŠã¿ãŸãããããajax:complete
ãšããã€ãã³ããform
èŠçŽ ã§çºç«ããŸããïŒä»çµã¿ã¯çç¥ãïŒ
ããã€ã³ãã³ãæ°ãä»ããŠãã ãããã
app/assets/javascripts/memos.js.coffee
$ -> $('#memos') .on 'click', '.edit, .cancel', (event) -> # 衚瀺ãåãæ¿ã toggleEditor $(this).closest('.memo') .on 'ajax:complete', '.edit_memo', (event, ajax, status) -> # çºç«ãç¢ºèª alert status
ããã§ãUpdateãããã¢ã©ãŒãã衚瀺ãããããã«ãªããŸãããéä¿¡ã«æåãããš"success"
ã衚瀺ãããŸãã
ææãªæ å ±ãè¿ãããã«ãã
次ã¯ãµãŒããŒããããŒã¿ãè¿ããŠã倿ŽåŸã®å€ã衚瀺ããŠã¿ãŸããããéä¿¡å
ã¯form_for
ã®ãªãã·ã§ã³ã§å€ãããããã©ãä»åã¯ããã©ã«ãã®memos#update
ãåŒã°ããŠãŸãããšããããã§ãããã®ã³ãŒããä¿®æ£ããŸãã
app/controllers/memos_controller.rb
# PUT /memos/1 # PUT /memos/1.json def update @memo = Memo.find(params[:id]) if @memo.update_attributes(params[:memo]) status = 'success' else status = 'error' end render json: { status: status, data: @memo } end
render({json: data})
ã§ãJSON圢åŒã§åºåããŸããããã§ã¯äžžããšè¿ãããã«ããŸããã
ãããšHTTPã¬ã¹ãã³ã¹ã¯ãããªæãã«ãªããŸããïŒFirebugãšããããã¯ãŒã¯èŠãç³»ã®ããŒã«ãšãã§ç¢ºèªã§ããŸããïŒ
{"status":"success","data":{"body":"hoge","created_at":"2011-11-27T14:28:24Z","id":1,"updated_at":"2011-11-27T14:56:16Z"}}
ã¬ã¹ãã³ã¹ãè§£æããŠèšå®å€ãååŸãã
ããŠãåä¿¡åŽã¯JavaScript/CoffeeScriptã§ããäžèšã®HTTPã¬ã¹ãã³ã¹ã¯ããã®ã³ãŒãã ãšajax.responseText
ã«æ ŒçŽãããŠããŸãã
app/assets/javascripts/memos.js.coffee
.on 'ajax:complete', '.edit_memo', (event, ajax, status) -> # HTTPã¬ã¹ãã³ã¹ããã®ãŸãŸè¡šç€º alert ajax.responseText
ããããã€ã³ãã³ãã«æ°ãä»ããŠãã ãããïŒãç·šéã®éœåäž1æåç®ããã«ãªã£ãŠãŸããã©ãã¡ãããš4æåã®ç©ºçœãè£å®ããå¿ èŠããããŸãããæ°ãä»ããŠãã ããã
ã§ãããã§ã¬ã¹ãã³ã¹ã衚瀺ãããŸããããšãã£ãŠããããªçã®ããŒã¿ã«ã¯çšã¯ãªãããã§ãJSONããŒã¿ãè§£æããŠJavaScriptã®ãªããžã§ã¯ãã«å€æããŸããjQuery.parseJSON()
ã䟿å©ã§ãã
.on 'ajax:complete', '.edit_memo', (event, ajax, status) -> response = $.parseJSON(ajax.responseText) body = response.data.body # èšå®å€ããšãããã衚瀺 alert body
åŠççµæãç»é¢ã«åæ ããã
èšå®å€ãååŸã§ããã®ã§ãããã§ç»é¢ãæŽæ°ããŠããã€ã€ããã©ãŒã ã¯éããŠããŸããŸãããã
.on 'ajax:complete', '.edit_memo', (event, ajax, status) -> response = $.parseJSON(ajax.responseText) body = response.data.body $container = $(this).closest('.memo') # 衚瀺ãããŠãå€ãæŽæ° $container.find('.viewer .body').text body # è¡šç€ºãæ»ã toggleEditor $container
ãããŒã§ããããŒïŒ
ãŸãšã
form_for
ã«remote: true
ãäžããã ãã§ããã©ãŒã ãAjaxåãããupdate
ã¢ã¯ã·ã§ã³ã§çµæãè¿ãã- JavaScript/CoffeeScriptã§ã¬ã¹ãã³ã¹ãè§£æããŠç»é¢ãæžãæããã
- ã¬ã¹ãã³ã¹ã¯
render({json: data})
ã§JSON圢åŒã«ããŠãjQuery.parseJSON(ajax.responseText)
ã§ãªããžã§ã¯ãåããã®ãç°¡åã
ãã®èª¿åã§æ°èŠäœæãšåé€ãäœã£ãŠã¿ãŸãããïŒ
Ajaxã§åããæ°èŠäœæãã©ãŒã ãäœæ
ããããã¯ãã£ãããã£ããè¡ããŸããã
ããããšïŒ
- æ°èŠäœæãã©ãŒã ã远å
- é ç®ãæ°èŠäœæããããçµæãç»é¢ã«è¿œå ïŒç·šéãã©ãŒã ãå«ãïŒ
- ç»é¢ã«HTMLã远å ããçºã«ãHTTPã¬ã¹ãã³ã¹ã«è¿œå HTMLãæžãåºã (
render_to_string()
ã䜿ããŸã) - é ç®ã²ãšã€åãç»é¢ã«åºåããããããã³ãã¬ãŒããåå²
app/views/memos/index.html.erb
<h1>Listing memos</h1> <div id="memos"> <% @memos.each do |memo| %> <%= render 'show', memo: memo %> <% end %> </div> <br /> <h1>New memo</h1> <%= render 'form', memo: @new_memo, submit_text: 'Create' %>
app/views/memos/_show.html.erb
ïŒæ°èŠäœæïŒ
<div class="memo"> <div class="viewer"> <span class="body"><%= memo.body %></span> <%= button_tag 'Edit', class: 'edit', type: :button %> </div> <div class="editor"> <div> <%= button_tag 'Cancel', class: 'cancel', type: :button %> </div> <%= render 'form', memo: memo, submit_text: 'Update' %> </div> </div>
app/views/memos/_form.html.erb
<%= form_for memo, remote: true do |f| %> <%= f.text_field :body, id: nil, class: 'body' %> <%= f.submit submit_text %> <% end %>
app/controllers/memos_controller.rb
# GET /memos # GET /memos.json def index @memos = Memo.all @new_memo = Memo.new respond_to do |format| format.html # index.html.erb format.json { render json: @memos } end end
ããã£ã¡ãã
# POST /memos # POST /memos.json def create @memo = Memo.new(params[:memo]) if @memo.update_attributes(params[:memo]) status = 'success' html = render_to_string partial: 'show', locals: { memo: @memo } else status = 'error' end render json: { status: status, data: @memo, html: html } end
app/assets/javascripts/memos.js.coffee
$ -> $('#memos') .on 'click', '.edit, .cancel', (event) -> # 衚瀺ãåãæ¿ã toggleEditor $(this).closest('.memo') .on 'ajax:complete', '.edit_memo', (event, ajax, status) -> response = $.parseJSON(ajax.responseText) body = response.data.body $container = $(this).closest('.memo') # 衚瀺ãããŠãå€ãæŽæ° $container.find('.viewer .body').text body # è¡šç€ºãæ»ã toggleEditor $container $('#new_memo') .on 'ajax:complete', (event, ajax, status) -> response = $.parseJSON(ajax.responseText) html = response.html # ç»é¢ã«è¿œå $('#memos').append html # ãã©ãŒã ãåæå $(this)[0].reset() # 衚瀺ã¢ãŒããšç·šéã¢ãŒããåãæ¿ããã toggleEditor = ($container) -> # 衚瀺ãé衚瀺ãåãæ¿ã $container.find('.viewer, .editor').toggle() # ç·šéã¢ãŒããªããå€ãæ»ã $bodyField = $container.find('.editor .body') if $bodyField.is(':visible') $bodyField .val($container.find('.viewer .body').text()) .select()
Ajaxã§åããåé€ãã¿ã³ãäœæ
form_for()
ã«method: :delete
ãæå®ããã®ãããœâŠâŠãªãã ãã©ãæ£çŽãã®ããæ¹ã§ããã®ãããããŸããã
app/views/memos/_show.html.erb
<div class="memo"> <div class="viewer"> <span class="body"><%= memo.body %></span> <%= button_tag 'Edit', class: 'edit', type: :button %> </div> <div class="editor"> <div> <%= button_tag 'Cancel', class: 'cancel', type: :button %> </div> <%= render 'form', memo: memo, submit_text: 'Update' %> <%= form_for memo, method: :delete, remote: true, html: { id: nil, class: 'delete_memo' } do |f| %> <%= f.submit 'Destroy' %> <% end %> </div> </div>
app/controllers/memos_controller.rb
# DELETE /memos/1 # DELETE /memos/1.json def destroy @memo = Memo.find(params[:id]) @memo.destroy render json: { status: 'success', data: @memo } end
app/assets/javascripts/memos.js.coffee
$('#memos') .on 'click', '.edit, .cancel', (event) -> # 衚瀺ãåãæ¿ã toggleEditor $(this).closest('.memo') .on 'ajax:complete', '.edit_memo', (event, ajax, status) -> response = $.parseJSON(ajax.responseText) body = response.data.body $container = $(this).closest('.memo') # 衚瀺ãããŠãå€ãæŽæ° $container.find('.viewer .body').text body # è¡šç€ºãæ»ã toggleEditor $container .on 'ajax:complete', '.delete_memo', (event, ajax, status) -> # é ç®ãåé€ $(this).closest('.memo').remove()
ããã§CRUD宿ãã°ãããŒãã â©(ã»Ïã»)â©
ãããããããã
ããšã¯äœ¿ã£ãŠãªãviewãšãactionã¯åé€ãã¡ãã£ãŠããã§ããã
ãã¶ã€ã³ã¯ããã¡ãã£ãšé 匵ããŸãããâŠâŠåã¯ã»ã³ã¹ãªãã®ã§ããã§ããã§ãã
ãããšãšã©ãŒåŠçãšãã¹ãããã£ã¡ãããããã«ãããããããããªãããªãšæããŸããã¯ãã
ããŸã
Error: Parse error on line XXX: Unexpected 'INDENT'
ã£ãŠèšãããã
èªã¿èŸŒãã§ããCoffeeScriptã®ã€ã³ãã³ãããããããŠãã³ãŒãã®è§£æã«å€±æããã¿ããã§ããïŒCoffeeScriptèªäœã§ã¯ãªãããããèªã¿èŸŒãã§ããviewã®æ¹ã§è¡šç€ºãããŸããïŒãè¡çªå·ã衚瀺ãããŠããã®ã§ããããèŠçŽããŠã¿ãŠãã ããã
CoffeeScriptã¯ãããã¯ãäžæ¬åŒ§(braces){ ... }
ãããªããŠã€ã³ãã³ãã§èŠãŠããŸããPythonãããã§ããã
render()
ãããªããŠãã£ãšé«åºŠãªJSONãè¿ããã
app/view/memos/update.js.coffee.erb
ã¿ãããªååã§ãã³ãã¬ãŒããäœæããŠãããšãåŒæ°ãªãã§render()
ãåŒãã æã«èªã¿èŸŒãã§ãããã¿ããã§ãã
{"status":"<%= @status %>"}
callback
ãæå®ããŠJSONPã«ããã
ãã³ãã¬ãŒãã䜿ãã°ãããã§ãã§ãããã©ããªãããã£ãšãšã¬ã¬ã³ããªããæ¹ãããããããªãããªãã
<%= @callback_name %>({"status":"<%= @status %>"})
誰ãæããŠãŒã _(ã»Ïã»`_)â)_