Skip to content

Instantly share code, notes, and snippets.

@t-oginogin
Last active August 29, 2015 13:56
Show Gist options
  • Save t-oginogin/9268171 to your computer and use it in GitHub Desktop.
Save t-oginogin/9268171 to your computer and use it in GitHub Desktop.

ThinReportsで複数レイアウトを使って一覧表を作成する

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'

同じFormで同じModelに対しての修正と登録を混在させる(一度のrequestでinsertとupdateを同時に行う)

既存データの一覧を表示し、まとめて編集できる表において、 行を追加して新規データも登録できるようなケースを想定します。

単純に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"}, 
}

Twitter Bootstrap Modalを移動(drag)したい

一番手軽に実現できた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">&times;</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">&times;</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

Railsアプリにユーザー認証機能deviseを導入

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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment