1日1%成長するブログ

毎日成長するために仕事/プライベートで得た学びをアウトプットするブログです

CarrierwaveでUUIDを使ったファイル名にする方法

UUIDとは

  • Universally (普遍的に) Unique (一意な) IDentifier (識別子) の略
  • 誰でも作れるが世界中で重複しないようになっているID
  • RFC4122という共通の規格が決められている

UUIDの種類

  • UUIDの作成方法には5種類存在する。
1: 時刻とMACアドレスを元にするバージョン
2: DCEセキュリティバージョン
3: バイト列を元にするバージョン。MD5ハッシュを利用
4: ランダム生成するバージョン
5: バイト列を元にするバージョン。SHA-1ハッシュを利用
  • RubyのSecureRandom.uuidはバージョン4
  • 16進数を32桁で乱数生成したもの
  • 340澗(かん)通り。重複はしないと考えていい。

www.slideshare.net

なぜUUIDを使うのか?

  • ファイル名にIDを使っている場合、推測ができてしまい他のユーザーが閲覧できてしまう
  • IDからサービスのユーザー数がバレてしまう

上記のようなニーズからUUIDは使われる

UUIDにする方法

  • xxx_uploader.rbのfilenameメソッドを上書きすればいい
def filename
  "#{SecureRandom.uuid}.#{file.extension}" if original_filename
end

How to: Create random and unique filenames for all versioned files · carrierwaveuploader/carrierwave Wiki · GitHub

公式では以下のような書き方がサンプルにあるが、uuidだけでも十分だし読みやすいのでいいのではと思っている。

def filename
  "#{secure_token}.#{file.extension}" if original_filename.present?
end

protected
def secure_token
  var = :"@#{mounted_as}_secure_token"
  model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.uuid)
end

Rails5でajaxを使っていいね機能を実装する

ルーティングを用意する

resources :diaries do
  resources :diary_comments, only: [:create]
  resources :diary_likes, only: [:create, :destroy]
end
  • diaryが重複するので後でスッキリさせる

モデルを用意する

class Diary < ApplicationRecord
  belongs_to :user
  has_many :diary_likes, dependent: :destroy

  # ユーザーのいいねを取得
  def like(user_id)
    diary_likes.find_by(user_id: user_id)
  end
end

部分テンプレートを用意する

<% if diary.like(user.id) %>
  <button class="btn btn-default btn-xs disabled" type="button">
    <i class="fa fa-thumbs-o-up">いいね済み</i>
  </button>
<% else %>
  <button class="btn btn-default btn-xs" type="button">
    <i class="fa fa-thumbs-o-up">いいね</i>
  </button>
<% end %>
<% else %>

button_toに書き換える

 <%= button_to diary_likes_path(diary), remote: true, class: "btn btn-default btn-xs" do %>
   <i class="fa fa-thumbs-o-up">いいね</i>
 <% end %>
  • button_toは実際にはformとbuttonタグとして出力される。こんな感じ
<form class="button_to" method="post" action="/diaries/1/likes" data-remote="true"
    <button class="btn btn-default btn-xs" type="submit">
      <i class="fa fa-thumbs-o-up">いいね</i>
    </button>
    <input type="hidden" name="authenticity_token" value="xx">
</form>
<% if diary.like(user_id) %>
  <%= button_to diary_diary_like_path(diary, diary.like(user_id)), remote: true, method: :delete, class: "btn btn-default btn-xs" do %>
    <i class="fa fa-thumbs-o-up">いいね済み</i>
  <% end %>
<% else %>
  <%= button_to diary_diary_likes_path(diary), remote: true, class: "btn btn-default btn-xs" do %>
    <i class="fa fa-thumbs-o-up">いいね</i>
  <% end %>
<% end %>
<% else %>

いいねボタンを用意

.like-button{:class => "like-button-#{diary.id}"}
  = render "diary_likes/like", diary: diary, user_id: diary.user_id
  • 日記ごとに部分テンプレート化したいいね!ボタンをrenderする

いいね機能を実装する

def create
  @like = DiaryLike.new(user_id: current_user.id, diary_id: params[:diary_id])
  @like.save
end

def destroy
  @like = DiaryLike.find_by(user_id: current_user.id, diary_id: params[:diary_id])
  @like.destroy
end
  • diary_idしか送られてこない & そのまま保存するわけでもないのでストロングパラメータは不要
  • remote: trueで送っているので、xxx.js.erbが呼ばれる

create.js.erbを作成

$(".like-button-<%= @like.diary_id %>").html("<%= j(render partial: 'like', locals: { diary: @like.diary, user_id: @like.user_id }) %>")

destroy.js.erbを作成

$(".like-button-<%= @like.diary_id %>").html("<%= j(render partial: 'like', locals: { diary: @like.diary, user_id: @like.user_id }) %>")

react-nativeでbox-shadowをつける

IOSの場合

shadowColor: #ccc,
shadowOffset: {
  width: 0,
  height: 2,
},
shadowRadius: 0,
shadowOpacity: 1,
  • shadowOffset
    • width: 横への影
    • height: 縦の影
  • shadowOpacity
    • 影の透明度
    • 0が透明、1は透明なし

f:id:masaru_furuya:20180126160202p:plain

これでこんな感じで下に影をつけられる

Androidの場合

shadowColor: #ccc,
shadowOffset: {
  width: 0,
  height: 2,
},
shadowRadius: 0,
shadowOpacity: 1,
elevation: 2
  • Androidの場合はelevationをつける

yenta風の特定の位置までドラッグしたらフェードアウトする処理を実装する

$('.draggable').draggable({
    drag: function() {
      var offset = $(this).offset();
      var xPos = offset.left;
      var yPos = offset.top;
      if (xPos >= 210) {
        $('.xxx).addClass('fadeout-right').delay(700).fadeOut(1);
      }
      if (xPos <= -210) {
        $('.xxx).addClass('fadeout-left').delay(700).fadeOut(1);
      }
      if (yPos <= -250) {
        $('.xxx).addClass('fadeout-top').delay(700).fadeOut(1);
      }
      if (yPos >= 300) {
       $('.xxx).addClass('fadeout-bottom').delay(700).fadeOut(1);
      }
    }
  });
.fadeout-right {
  transform: rotate(30deg) scale(0.8);
  transition: 1s;
  opacity: 0;
  margin-left: 200px;
}

.fadeout-left {
  transform: rotate(-30deg) scale(0.8);
  transition: 1s;
  opacity: 0;
  margin-left: -200px;
}

.fadeout-top {
  transform: rotate(30deg) scale(0.8);
  transition: 1s;
  opacity: 0;
  margin-bottom: 200px;
}

.fadeout-bottom {
  transform: rotate(-30deg) scale(0.8);
  transition: 1s;
  opacity: 0;
  margin-bottom: -200px;
}

ちなみにスマホだとブラウザのドラッグイベントが検知できないので、 jquery-ui-touch-pinch.js のライブラリを使いました。

Railsでコメント機能を作る

ルーティングの追加

resources :diaries do
  resources :comments, only: [:create]
end

コントローラー側

@diaryComment = DiaryComment.new(diary_comment_params)
@diaryComment.user_id = User.first.id
respond_to do |format|
  if @diaryComment.save
    format.html { redirect_to diaries_url, notice: 'Diary was successfully created.' }
    format.json { render :show, status: :created, location: @diaryComment }
  else
    format.html { redirect_to diaries_url }
    format.json { render json: @diaryComment.errors, status: :unprocessable_entity }
  end
end

def diary_comment_params
  # params.require(:diary).permit(:content)
  params.require(:diary_comment).permit(:content, :diary_id)
end

フォームの追加

= form_for([diary, diary.diary_comments.build]) do |f|
  %img.img-responsive.img-circle.img-sm{:alt => "Alt Text", :src => "xxxx"}/
  / .img-push is used to add margin to elements next to floating images
  .img-push
    = f.hidden_field :diary_id
    = f.text_field :content, class: "form-control input-sm", placeholder: "コメントを入力"

ポイント

  • 1:多のform_forの指定
  • ネストしたリソースのルーティング

今回はAjaxとしては実装しなかったのでまたそれは次回

Tinder風のUIをJavaScriptで実装する

https://codepen.io/developingidea/pen/meAIncodepen.io

こちらのコードを参考に学んでみた。

$(".buddy").on("swiperight",function(){
      $(this).addClass('rotate-left').delay(700).fadeOut(1);
      $('.buddy').find('.status').remove();

      $(this).append('<div class="status like">Like!</div>');      
      if ( $(this).is(':last-child') ) {
        $('.buddy:nth-child(1)').removeClass ('rotate-left rotate-right').fadeIn(300);
       } else {
          $(this).next().removeClass('rotate-left rotate-right').fadeIn(400);
       }
    });  
.rotate-left
  transform: rotate(30deg) scale(0.8);
  transition: 1s;
  margin-left: 400px;
  cursor: e-resize;
  opacity: 0;
  z-index: 10;

「右に400px,30度傾けて80%の大きさに変更する」を1秒かけて行うという処理

 transform: rotate(30deg) scale(0.8);
 transition: 1s;
 margin-left: 400px;
  • transition: 1sがないと一瞬で動いてしまう

やっていること

  • rotate-leftで要素を傾ける
  • delayとfadeOutで少し遅らせてフェードアウト(hide)する
  • スワイプイベントはhammer.jsを使っている

思ったこと

Tinderのように左右のスワイプはこれで実現できる。 ただこれだと毎回同じ見た目のスワイプになってしまうので、 Yentaのようにドラッグしてどこかに飛ばすということは出来ないなぁ