おぴよの気まぐれ日記

おぴよの気まぐれ日記

岡山やプログラミング、ファッションのこと、子育てや人生、生き方についての備忘録。

css上で動的に値の変更はできないけどhtmlに書けばできる

cssに設定する値を動的に変更するとかできないのか?なんて思ったことないでしょうか?

例えば、

  • ユーザー毎にボタンの色や背景色などを変更したい!
  • 条件に応じてbackgroundに画像を入れたり、差し替えたりしたい!

結論から言うとできます!

ただcssでは出来ないのでhtmlにstyle属性を指定してcssを書き込めばokです!

今回はナビゲーションバーにある予約ボタンをユーザー毎に変更する例です。

model

前提としてユーザー = userモデルがあり、そこにカラーコードが設定されているとします。

id name color created_at updated_at
1 opiyo 30a8b5
2 kuma 000
3 fuzo fff

シンプルな使い方

view

haml

.event__nav
  = link_to '予約', '#', style: "background-color: ##{user.color}"

html

<div class="event__nav">
  <a style="background-color: #30a8b5" href="#">予約</a>
</div>

条件がいっぱいあるときの使い方

view

haml

.event__nav
  = link_to '予約', '#', style: event_nav_btn_style(user)"

link_toは第三引数にstyle属性を使えば、htmlに直接cssの設定ができます。

html

<div class="event__nav">
  <a style="background-color: #30a8b5" href="#">予約</a>
</div>

helper

条件が多かったりする場合はhtmlからhelperメソッドを呼び出して上げればokです!

def event_nav_btn_style(user)
  if user.name == 'hogehogehoge'
    return 'background-color: #FF7A00;'
  elsif user.name == 'piyopiyopiyo'
    return "background-color: ##{user.color};"
  else
    return 'background-color: black;'
  end
end

ActiveJobを使った非同期処理の方法

Ruby on Railsを使って非同期で処理する方法 ActiveJobについてです。

ざっくり手順

  1. generateでjobファイルを作成する
  2. jobをキューに登録する
  3. jobを実行する

generateでjobファイルを作成する

コマンドで $ bundle exec rails g job hoge_job

手作業で jobs/hoge_job.rbに.rbファイルを作成する

jobをキューに登録する

HogeJob.perform_later(event_id: @event.id)

perform_later: 実行キューが積まれて随時処理する

perform_now: 今すぐに実行する

時間指定する方法

明日の正午("2019/06/12 12:00")

HogeJob.set(wait_util: Date.tomorrow.noon).perform_later()

1週間後

HogeJob.set(wait: 1.week).perform_later()

※要注意は時間を指定してはいけない。あくまで日数を指定すること。

呼び出された方

#jobs/hoge_job.rb
class HogeJob
  queue_as :default

  URL = Rails.application.secrets.url
  SUCCESS = 200
  FAILED = 400

  def perform(event_id, status_code = 100)
    response = RestClient.get "#{URL}?event_id=#{event_id}&status_code=#{status_code}"
    result = JSON.parse(response.body).dig('root')
    if result['code'] == FAILED
      Bugsnag.notify(exception, { event_id: event_id, error_message: result['message']})
    end
  end
end

コールバックを指定することもできるので、model的な感じで色々できそうです。

参考サイト

Active Job の基礎 - Rails ガイド

Ruby on Railsでcsvダウンロード機能の作り方

Ruby on Railsを使ってcsvファイルのダウンロード機能を作りたくて調べました。

大枠の手順

  1. routesを設定
  2. csvを受け付けるアクションをコントローラーに設定
  3. 対象データを抽出しcsvフォーマットのファイルへ渡す
  4. CSVデータを作成する
  5. 画面にダウンロードボタンを作成する

コントローラーにアクションを定義する

# controller/users_controller.rb
  def index
    @users = User.all

    respond_to do |format|
      format.html
      format.csv do
        filename = ERB::Util.url_encode('ユーザー一覧.csv')
        send_data render_to_string, filename: filename, type: :csv
      end
    end
  end

ERB::Util.url_encodeは引数に渡された文字列をURLエンコードしてくれる。これによって日本語や半角スペースをよしなにしてくれる

send_dataは動的に生成されたデータをダウンロードする。 render_to_stringは表示結果を文字列として取得するメソッド。

なので、これらを組み合わせる事でCSVファイルに出力する為の文字列を作ってダウンロードを可能にしている。

参考: https://docs.ruby-lang.org/ja/latest/class/ERB=3a=3aUtil.html

参考: http://railsdoc.com/references/send_data

参考: http://railsdoc.com/references/render_to_string

View

# views/users/index.haml
= link_to 'CSVダウンロード' users_path(format: :csv)

ポイントはリンクを生成するpathにフォーマット: csvを指定してあげます。これによってコントローラー側で切り分けれるようになります。

csvファイル

# views/users/index.csv.ruby
require 'csv'
require 'nkf'
csv_data = CSV.generate do |csv|
  csv << %w(id 名前 年齢)
  @users.each do |user|
    csv << [
        user.id,
        user.name,
        user.age
    ]
  end
end
NKF::nkf('--sjis -Lw', csv_data)

CSV.generateは文字列csv形式=カンマ区切りの文字列を生成してくれます。 NKF::nkfは文字コードを強制的に変換する為のプログラムです。

参考: https://docs.ruby-lang.org/ja/1.9.3/class/NKF.html

【Rails】Strong Parametersで`param is missing...`エラーになる

こんちには。opiyoです。

ActionController::ParameterMissing in ImagesController#create
param is missing or the value is empty: image

    def image_paprams
      params.require(:image).permit(:name, :picture)
    end

end

こんな感じでエラーになるのだが、paramsが空っぽだよと。 実際にparams[:image]ってやるとnilが返ってくる。

が、すげー凡ミスでpermitて定義している:nameをview側に書いてなかった。 だから、来るはずのデータが来てなくてエラーになったのではなかろうかと思ってる。

<%= form_for(@image) do |f| %>
  <div class="form-group">
    <%= f.text_field :name %> # ここを書いてなかったけど、書いたらエラーにならなくなった    
    <%= f.file_field :picture %>
    <%= f.submit "画像を投稿する" %>
  </div>
<% end %>

そんな感じ。

Docker For MacでPostgreSQL9.5系の環境を作ってみた!

こんばんは。opiyoです。

brewでインストールするとなんだかPostgreSQL9.6系がインストールされてしまうのだけど、本番環境と同じバージョンをローカルに使いたくてウズウズしていたのだが...

Docker使えば良いじゃんってアドバイス貰ったので教えてもらった

Docker For Macのインストール

https://www.docker.com/docker-mac

docker-compose.ymlを作る

docker-compose.ymlの場所を作る

$ mkdir -p ~/compose/postgresql95/
$ vi docker-compose.yml

PostgreSQL9.5を配置する場所を作る

$ mkdir ~/postgres95

docker-compose.ymlを修正

postgresql:
  image: postgres:9.5.8-alpine
  environment:
    POSTGRES_USER: taku
    POSTGRES_INITDB_ARGS: "--encoding=UTF8 --no-locale"
    LANG: ja_JP.UTF-8
  ports:
    - "5432:5432"
  volumes:
    - /Users/taku/postgres95:/var/lib/postgresql/data #「/Users/taku/postgres95」の箇所を、さっき作った場所を指定する      
  restart: always

やることは、

  • POSTGRES_USERを各自設定する
  • volumesのパスをさっき作った場所を指定する

imageの部分には、こちらのサイトに書いてあるように好きなバージョンを書けばおk https://hub.docker.com/r/library/postgres/tags/

自動起動されているpostgresqlを止める

本当は9.6系と9.5系を共存させたいのだけど、上手くいかないので既にPostgreが動いている場合は止める。 私の場合は、brewでインストールしてたので簡単だった。

$ brew services list
Name       Status  User
postgresql started taku

$ brew services stop postgresql

$ brew services list
Name       Status  User
postgresql stopped

パスを通す

$ sudo vi ~/.bash_profile
export PGHOST=localhost

$ env | grep HOST
PGHOST=localhost

これは教えてもらうがままにやったけど、何してるのかいまいち分かってないぞ。

構築と起動

$ docker-compose up -d

psql -l -p5432とかやればアクセスできるはず! めちゃくちゃ簡単でした。

その他Dockerコマンド

ステータス

$ docker ps

停止

$ docker stop CONTAINER_NAMES

コンテナに入る

$ docker exec -it CONTAINER_NAMES /bin/bash

こんな感じ。

【Rails】ファイルのフルパス、ファイル名を取得する

過去データとかでファイルを一括で読み込みたい場合で使える技です。

> files = Dir.glob("/Users/taku/rails/gist/test/*.xls")
=> ["/Users/taku/rails/gist/test/1.xls", "/Users/taku/rails/gist/test/2.xls", "/Users/taku/rails/gist/test/3.xls", "/Users/taku/rails/gist/test/4.xls", "/Users/taku/rails/gist/test/5.xls"]

これでfilesをグルグル回して一つ一つのファイルを読み込んでやれば色々処理できますね。 取り込み処理も出来るし、csvを作ったりも出来るし。

globを使うと拡張子で絞り込めるので、結構便利ですね。

> files = Dir.entries("/Users/taku/rails/gist/test/")
=> [".", "..", ".DS_Store", "1.csv", "1.txt", "1.xls", "1.xlsx", "2.csv", "2.txt", "2.xls", "2.xlsx", "3.csv", "3.txt", "3.xls", "3.xlsx", "4.xls", "5.xls"]

単純にディレクトリの中にあるファイル名だけ取得したい場合はentriesを使うそうです。

【Ruby】配列の中身が重複しているかをチェックする方法(select、find)

csvのデータを取り込み別のcsvへ吐き出す処理をしていたのですが、値が重複していることに気がつきました。

こんな感じ。

data = []
inport = CSV.read("./inport.csv")
inport.each do |c|
  data << c
end

CSV.open("./export.csv", "w") do |export|
  data.each do |d|
    export << d
  end
end

この中で取り込んだデータが重複しているかどうかチェックする方法が分からず、グルグル回さないとダメなのかと思っていたのですが良い方法がありました。

> ff6 = [["Tina", 1],["Rokku", 2],["Edoga", 3],["Masshu", 4]]
=> [["Tina", 1], ["Rokku", 2], ["Edoga", 3], ["Masshu", 4]]

> ff6.select{|f| f[0] == "Masshu"}
=> [["Masshu", 4]]
irb(main):003:0>

selectを使うと合致したものだけを取ってきてくれます。 なので重複しているかチェックしたい場合は、sizeを使って数を調べてやれば良いです。

> ff6.select{|f| f[0] == "Masshu"}.size
=> 1

> if ff6.select{|f| f[0] == "Masshu"}.size > 0
>   # 処理
> end

めでたし、めでたし

追記

findを使った方が早いとのこと。

> ff6
=> [["Tina", 1], ["Rokku", 2], ["Edoga", 3], ["Masshu", 4]]

> ff6.find {|ff| ff[0] == "Masshu" }
=> ["Masshu", 4]

> ff6.find {|ff| ff[0] == "Masshu" }.size
=> 2

selectとの違いは、見つけた最初の配列を一つだけ返す。ってことですかね。 例えば、

# 「カイエン 4」を追加します
> ff6 = [["Tina", 1],["Rokku", 2],["Edoga", 3],["Masshu", 4], ["Kaien", 4]]
=> [["Tina", 1], ["Rokku", 2], ["Edoga", 3], ["Masshu", 4], ["Kaien", 4]]


# selectの場合
> ff6.select {|ff| ff[1] == 4 }
=> [["Masshu", 4], ["Kaien", 4]]

# findの場合
> ff6.find {|ff| ff[1] == 4 }
=> ["Masshu", 4]

最初に見つけた一つを返すんだから、そりゃー早いは。 今回やりたいことは、重複しているかのチェックだから1件でもあればNG。 ってことでfindを使うことにした。

【bootstrap3】ナビゲーションバー(Navbar)で起きたレイアウト崩れと右寄せへの対応方法

こんばんは。opiyoです。

結論としては勝手な解釈をして余計なことしてた!に尽きるのだけど、同じようなハマり方すると時間もったい無いので共有です。

<header>
  <nav class="navbar navbar-inverse">
      <div class="container-fluid">
          <div class="navbar-header">
              <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbarEexample1">
                  <span class="sr-only">Toggle navigation</span>
          <% if logged_in? %>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
          <% else %>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          <% end %>
              </button>
              <a class="navbar-brand" href="#">
          <%= link_to "RChannel", root_path, id: 'logo' %>
              </a>
          </div>
          <div class="collapse navbar-collapse" id="navbarEexample1">
              <ul class="nav navbar-nav navbar-right">
          <% if logged_in? %>
            <li class="inline-block"><%= link_to "キーワード登録", new_user_keyword_path(user_id: @current_user.id) %></li>
            <p class="navbar-text inline-block"><%= @current_user.name %> さん</p>
            <li class="inline-block"><%= link_to "マイリスト", my_list_topic_path(id: @current_user.id) %></li>
            <li class="inline-block"><%= link_to "ログアウト", logout_path, method: :delete %></li>
          <% else %>
            <li class="inline-block"><%= link_to "アカウント登録", new_user_path %></li>
            <li class="inline-block"><%= link_to "ログイン", login_path %></li>
          <% end %>
              </ul>
          </div>
      </div>
  </nav>
</header>

ポイントはサンプル通りそのまま記載してください!

で僕が何をしていたのかご覧ください。

画面小さくした時に文字が隠れない

header側にもリンクが必要だと思ったんだよ。

          <div class="navbar-header">
              <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbarEexample4">
                  <span class="sr-only">Toggle navigation</span>
          <% if logged_in? %>
                    <span class="icon-bar"><%= link_to "キーワード登録", new_user_keyword_path(user_id: @current_user.id) %></span>
                    <span class="icon-bar"><%= link_to "マイリスト", my_list_topic_path(id: @current_user.id) %></span>
                    <span class="icon-bar"><%= link_to "ログアウト", logout_path, method: :delete %></span>
          <% else %>
            <span class="icon-bar"><%= link_to "アカウント登録", new_user_path %></span>
            <span class="icon-bar"><%= link_to "ログイン", login_path %></span>
          <% end %>
              </button>
              <a class="navbar-brand" href="#">
          <%= link_to "RChannel", root_path, id: 'logo' %>
              </a>
          </div>

最初こんな感じで.navbar-headerspanタグにもリンクが必要だと思って記載していたのですが、これやるとレイアウトが崩れます!

f:id:opiyotan:20180202201534p:plain

なので、spanタグには何も書いてはいけません!

左寄せが出来ない

          <div class="collapse navbar-collapse" id="navbarEexample1">
              <ul class="nav navbar-nav">
          <% if logged_in? %>
            <li class="inline-block navbar-right"><%= link_to "キーワード登録", new_user_keyword_path(user_id: @current_user.id) %></li>
            <p class="navbar-text inline-block navbar-right"><%= @current_user.name %> さん</p>
            <li class="inline-block"><%= link_to "マイリスト", my_list_topic_path(id: @current_user.id) %></li>
            <li class="inline-block"><%= link_to "ログアウト", logout_path, method: :delete %></li>
          <% else %>
            <li class="inline-block navbar-right"><%= link_to "アカウント登録", new_user_path %></li>
            <li class="inline-block navbar-right"><%= link_to "ログイン", login_path %></li>
          <% end %>
              </ul>
          </div>

サンプル見ると、pタグにnavbar-right設定しているから真似してたのだけど全然右寄せにならない。

で、Chromeで確認してみると

f:id:opiyotan:20180202201709p:plain

分かりづらいけど、既に領域が固定されているからfloatかけたって無駄。 なので、上の要素divタグに対して.navbar-rightを設定してやることでうまくいった!

まとめ

勝手な解釈はせずに、基本に忠実にやりましょう!

【Rails】XX件以上登録できないようにする独自バリデーションメソッドを設定する方法

こんばんは。opiyoです。

Railsアプリで「XX以上は登録できない!」というバリデーションを設定したかったので、やり方を調べてみました。

やりたいこと

Keywordというテーブルにデータを登録する処理で、5つまでしか登録できないようにバリデーションを設定します。

もし5つ以上の登録を行おうとした場合はsaveでエラーになるようにします。

Model

class Keyword < ApplicationRecord
  validates :title, presence: true
  validates :url, presence: true
  validate :check_count

  def check_count
    errors.add(:keyword, "は5つまで登録可能です。") if Keyword.count >= 5
    # errors.add(:keyword, "は5つまで登録可能です。") if Keyword.where(user_id: self.user_id).count >= 5
  end
end

モデルにバリデーションメソッドを定義します。 validateにメソッド名を記述し、そのメソッド内でKeywordの件数を取得しif文を記述します。 もし5件以上あれば、errorsの中に名前とエラーメッセージを追加します。

またコメントアウトしている部分ですが、selfを使うことで自分が登録したデータのみを対象とすることが出来ます。こうやって使うことが基本になりそうですね。

また、errorsの正体についてはActiveModel::Errorsで詳細はこちらを。

Controller

class KeywordsController < ApplicationController
  def create
    @keyword = Keyword.new(keyword_params)
    @keyword.check_count

    if @keyword.save
      redirect_to root_url
    else
      render 'new'
    end
  end

  private
    def keyword_params
      params.require(:keyword).permit(:title, :url)
    end
end

コントローラーではKeywordをインスタンス化して、メソッドを呼び出すだけです。 5件以上ある場合はerrorsにエラーが追加されているので、saveする時に失敗します。

という流れです。

【Bootstrap】navbarの背景色、文字色の悩みはこれ一発で解決だわー

何か作る時bootstrapにお世話になることあると思うのですが、cssいじっただけだと何故か上手くいかない。

例えば今回のnavbarですね。

ナビゲーションメニューがかっこよく決まらないとモチベーションが全く上がらないので、いろいろ探していたらあったわー。神様が。 https://work.smarchal.com/twbscolor/

自分の好きなカラーコードを入力したら勝手にcssのコード吐き出してくれる。

  • scss
  • sass
  • less
  • css

全部あるよ。

OhkEventRails.png

haml

    .navbar.navbar-default.navbar-static-top
      .container
        %button.navbar-toggle(type="button" data-toggle="collapse" data-target=".navbar-responsive-collapse")
          %span.icon-bar
          %span.icon-bar
          %span.icon-bar
        %a.navbar-brand(href="#") ホーム
        .navbar-collapse.collapse.navbar-responsive-collapse
          %ul.nav.navbar-nav
            %li= link_to "Link 1", "/path1"
            %li= link_to "Link 2", "/path2"
            %li= link_to "Link 3", "/path3"

scss

$bgDefault      : #dd4444;
$bgHighlight    : #e8350e;
$colDefault     : #ecf0f1;
$colHighlight   : #ecdbff;
$dropDown       : false;

.navbar-default {
  background-color: $bgDefault;
  border-color: $bgHighlight;
  .navbar-brand {
    color: $colDefault;
    &:hover, &:focus {
      color: $colHighlight; }}
  .navbar-text {
    color: $colDefault; }
  .navbar-nav {
    > li {
      > a {
        color: $colDefault;
        &:hover,  &:focus {
          color: $colHighlight; }}
      @if $dropDown {
        > .dropdown-menu {
          background-color: $bgDefault;
          > li {
            > a {
              color: $colDefault;
              &:hover,  &:focus {
                color: $colHighlight;
                background-color: $bgHighlight; }}
            > .divider {
              background-color: $bgHighlight;}}}}}
    @if $dropDown {
      .open .dropdown-menu > .active {
        > a, > a:hover, > a:focus {
          color: $colHighlight;
          background-color: $bgHighlight; }}}
    > .active {
      > a, > a:hover, > a:focus {
        color: $colHighlight;
        background-color: $bgHighlight; }}
    > .open {
      > a, > a:hover, > a:focus {
        color: $colHighlight;
        background-color: $bgHighlight; }}}
  .navbar-toggle {
    border-color: $bgHighlight;
    &:hover, &:focus {
      background-color: $bgHighlight; }
    .icon-bar {
      background-color: $colDefault; }}
  .navbar-collapse,
  .navbar-form {
    border-color: $colDefault; }
  .navbar-link {
    color: $colDefault;
    &:hover {
      color: $colHighlight; }}}
@media (max-width: 767px) {
  .navbar-default .navbar-nav .open .dropdown-menu {
    > li > a {
      color: $colDefault;
      &:hover, &:focus {
        color: $colHighlight; }}
    > .active {
      > a, > a:hover, > a:focus {
        color: $colHighlight;
        background-color: $bgHighlight; }}}
}