reportの作成方法やpageの作成方法はいくつも書き方があるので、 自分の使った書き方をメモ。
user_contents = { user_name: 'ユーザー名', user_address: '[email protected]' }
report = ThinReports::Report.create do |r|
# ユーザーページレイアウト
r.start_new_page :layout => File.join(Rails.root, 'app', 'reports', 'user.tlf') do |page|
page.values(user_contents)
end
# 本一覧ページレイアウト(表id: details)
r.start_new_page :layout => File.join(Rails.root, 'app', 'reports', 'books.tlf') do |page|
# フッタ作成時の処理
page.layout.config.list(:details) do
use_stores :total_cost => 0
events.on :footer_insert do |e|
e.section.item(:total_cost).value(total_cost)
end
end
# リスト作成処理
@books.each do |book|
book_contents = {title: book.title, cost: book.cost}
page.list(:details).add_row(book_contents)
page.list(:details).store.total_cost += cost
end
end
end
send_data report.generate,
filename: 'report.pdf',
type: 'application/pdf',
disposition: 'attachment'
既存データの一覧を表示し、まとめて編集できる表において、 行を追加して新規データも登録できるようなケースを想定します。
単純にsubmitすると次のエラーが発生します。
expected Hash (got Array) for param `samples'
これはParameterに対し、既存データはidをキーとしたハッシュで格納され、 新規データはidがないのでハッシュの配列で格納されるためです。
既存データ例:
"comments"=>{
"1"=>{"id"=>"1", "title"=>"a", "content"=>"aa"},
"2"=>{"id"=>"2", "title"=>"b", "content"=>"bb"},
"3"=>{"id"=>"3", "title"=>"c", "content"=>"cc"},
"4"=>{"id"=>"4", "title"=>"d", "content"=>"dd"},
"5"=>{"id"=>"5", "title"=>"e", "content"=>"ee"},
"6"=>{"id"=>"6", "title"=>"f", "content"=>"ff"}
}
新規データ例:
"comments"=>[
{"id"=>"", "title"=>"a", "content"=>"aa"},
{"id"=>"", "title"=>"b", "content"=>"bb"},
{"id"=>"", "title"=>"c", "content"=>"cc"},
{"id"=>"", "title"=>"d", "content"=>"dd"},
{"id"=>"", "title"=>"e", "content"=>"ee"},
{"id"=>"", "title"=>"f", "content"=>"ff"}
]
回避策として、新規データにダミーidを割り振り 受け取ったときにidの有無を見て新規作成と更新を切り分けます。
View例:
<table>
<tr>
<th>title</th>
<th>content</th>
</tr>
<% (0..5).each do |i| %>
<% if @comments.present? && @comments[i].present? %>
<!-- 既存データ -->
<%= f.fields_for 'comments[]', @comments[i], index: @comments[i].id || "x#{i}" do |c| %>
<tr id="comment<%=i+1%>">
<%= c.hidden_field :id, :id => "id#{i+1}", :value => @comments[i].id %>
<td>
<%= c.text_field :title, :id => "title#{i+1}", :value => @comments[i].title %>
</td>
<td>
<%= c.text_field :content, :id => "content#{i+1}", :value => @comments[i].content %>
</td>
</tr>
<% end %>
<% else %>
<!-- 新規データ -->
<tr id="comment<%=i+1%>">
<input id="id<%=i+1%>" name="user[comments][x<%=i+1%>][id]" type="hidden"/>
<td>
<input id="title<%=i+1%>" name="user[comments][x<%=i+1%>][title]" />
</td>
<td>
<input id="content<%=i+1%>" name="user[comments][x<%=i+1%>][content]" />
</td>
</tr>
<% end %>
<% end %>
</table>
Parameters例:
(次の例では2つが既存データでidのない2つが新規データ)
"comments"=>{
"1"=>{"id"=>"1", "title"=>"a", "content"=>"aa"},
"2"=>{"id"=>"2", "title"=>"b", "content"=>"bb"},
"x3"=>{"id"=>"", "title"=>"c", "content"=>"cc"},
"x4"=>{"id"=>"", "title"=>"d", "content"=>"dd"},
}
一番手軽に実現できたjQuery UIのdraggableを使います。
1.まずGemfileに追加。
gem 'jquery-ui-rails'
2.bundle install実行。
$ bundle install
3.app/assets/javascripts/application.jsに次の行を追加。
//= require jquery.ui.all
4.実装
view例:
app/view/common/index.html.erb
<a class="btn btn-primary" data-toggle="modal" data-target="#myModal" href="show">Show Modal</a>
<div class="modal hide" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-backdrop="">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="myModalLabel">Modal title</h4>
</div>
<div class="modal-body">
loadling...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
myModalのmodal-dialog全体をdraggableにする場合、次の設定を追加します。
app/assets/javascripts/common.js.coffee
$("#myModal").draggable({
handle: ".modal-dialog"
})
modal-body内にスクロールバーがつくような場合は、 さらにその内側のタグをdraggableに設定する方が良いです。
(ブラウザによってはスクロールバーの制御とdraggableの制御が競合し、 例えばスクロールバーをドラッグすると同時にmodalも移動されたりします)
#Railsのjoinsでplaceholderを使う
team_code='team_A'に所属するメンバーを取得する場合
class User < ActiveRecord::Base
def self.join_team(team_code)
User.joins(sanitize_sql_array(['INNER JOIN teams ON team_code = ?', team_code]))
end
end
呼び出し側
User.join_team('teem_A').where('users.team_id = team.id')
#RailsでBootstrap Modal
モーダルで別ページの内容を表示させます。
(別ページは読み直すたびに内容が変化するページ)
app/views/common/index.html.erb
<a class="btn btn-primary" data-toggle="modal" data-target="#myModal" href="show">Show Modal</a>
<div class="modal hide" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-backdrop="">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="myModalLabel">Modal title</h4>
</div>
<div class="modal-body">
loadling...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
app/views/common/show.html.erb
<div>
<%= 'test' %>
<%= "#{Time.now}" %>
</div>
app/controllers/common_controller.rb
class CommonController < ApplicationController
def index
end
def show
end
end
config/routes.rb
パスを追加します。
get 'common/index'
get 'common/show'
app/assets/javascripts/common.js.coffee
$('#myModal').on 'show.bs.modal', ->
# リモートページを表示するまで前回のページが残ってしまうので空にする
$('.modal-body').empty()
$('.modal-body').append('loading...')
# modal-backdropが重ねられるので削除
$('.modal-backdrop').remove()
補足:
今回のようにAjaxを使う場合、toggleは使わず、明示的に
$('#myModal').modal('show')
$('#myModal').modal('hide')
を呼ぶべきかもしれません。(次の現象が発生したので)
操作:
data-backdrop="true"の状態で、ダイアログ表示<->ダイアログ外をクリックして閉じるを繰り返すと、
現象:
・modal-backdropがどんどん重ねられ、背景が真っ黒になり、ダイアログ外を重ねられた数分クリックしないと元に戻れなかった。
・ダイアログが表示されなくなる場合があった。(表示/非表示が逆転するような動き)
#Railsで帳票出力(thinreportsによるPDF表示)
Gemfileに次の行を追加します。
gem 'thinreports'
インストールします。
$ bundle install
ThinReportsEditorでレイアウトを作成します。
(例としてapp/reports/sample.tlfに配置します)
レポートを作成したいコントローラーに出力コードを追加します。
require 'thinreports'
:
def show_reports
report = ThinReports::Report.new layout: File.join(Rails.root, 'app', 'reports', 'sample.tlf')
report.start_new_page do |page|
page.item(:name).value('Foo')
page.item(:address).value('Bar')
end
send_data report.generate, filename: 'sample.pdf',
type: 'application/pdf',
disposition: 'attachment'
end
Rakefileに次の一行を追加します。
gem 'devise'
インストールします。
$ bundle install
$ bundle exec rails g devise:install
表示されるメッセージにしたがって対応します。
===============================================================================
Some setup you must do manually if you haven't yet:
1. Ensure you have defined default url options in your environments files. Her e
is an example of default_url_options appropriate for a development environm ent
in config/environments/development.rb:
config.action_mailer.default_url_options = { :host => 'localhost:3000' }
In production, :host should be set to the actual host of your application.
2. Ensure you have defined root_url to *something* in your config/routes.rb.
For example:
root :to => "home#index"
3. Ensure you have flash messages in app/views/layouts/application.html.erb.
For example:
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
4. If you are deploying on Heroku with Rails 3.2 only, you may want to set:
config.assets.initialize_on_precompile = false
On config/application.rb forcing your application to not access the DB
or load models when precompiling your assets.
5. You can copy Devise views (for customization) to your app by running:
rails g devise:views
===============================================================================
Controllerに次の一行を追加します。
app/controllers/application_controller.rb
before_filter :authenticate_user!
Userモデルを作成します。
$ bundle exec rails g devise User
Userにカラム(例:name)を追加します。
$ bundle exec rails g migration AddNameToUsers
db/migrate/20140228020415_add_name_to_users.rb
class AddNameToUsers < ActiveRecord::Migration
def change
add_column :users, :name, :string
end
end
$bundle exec rake db:migrate
追加したカラム用の入力エリアをViewに追加します。
app/views/devise/registrations/new.html.erb
<div><%= f.label :name %><br />
<%= f.text_field :name %></div>
app/views/devise/registrations/edit.html.erb
<div><%= f.label :name %><br />
<%= f.text_field :name %></div>
追加したカラム用のStrong Parameter設定を追加します。
app/controllers/application_controller.rb
before_filter :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << :name
end
サンプルのトップページを作成します。 (root :to => "home#index"とした場合)
$bundle exec rails g controller Home
app/controllers/home_controller.rb
class HomeController < ApplicationController
def index
end
end
app/views/home/index.html.erb
<div>
<%= 'test' %>
</div>
全体レイアウトにログアウト用ヘッダを追加します。 (twitter-bootstrap-railsを使用)
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= content_for?(:title) ? yield(:title) : "Sample" %></title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %>
</head>
<body>
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container-fluid">
<a class="btn btn-navbar" data-target=".nav-collapse" data-toggle="collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<div class="container-fluid nav-collapse">
<ul class="nav">
<%= link_to "Sample", '/', :class => "brand" %>
<% if user_signed_in? %>
<%= current_user.email %> <%= link_to "Sign out", destroy_user_session_path, :method => :delete %>
<% end %>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
</div>
<div class="container-fluid">
<%= yield %>
<footer>
</footer>
</div> <!-- /container -->
</body>
</html>
固定ヘッダにしたので、body部にパディングを追加します。
app/assets/stylesheets/bootstrap_and_overrides.css.less
body {
padding-top: 60px;
}