やる気がストロングZERO

やる気のストロングスタイル

DBスペシャリスト勉強メモ

候補キーを全て洗い出す問題

※推移的関数従属や部分関数従属があればそれも記載するような問題

言葉に馴染みがないので何を問われているのかがそもそもわからなかったりした。

候補キーとは

候補キーとは「主キーとなる候補」の事らしい。
「主キーになる候補が副数ある?」と混乱したが、これは普段見るテーブルは正規化が行われているからで、ここで問われるときに提示される「関係スキーマ」は正規化されていないことが多く主キーになる候補が副数あったりする。

例:
電子会議室投稿(電子会議室番号、議題、分野番号、分野名、表示順、作成者ユーザーID、投稿番号、投稿本文、投稿者ユーザーID)
※ちなみにこの関係スキーマと一緒に各項目の説明も一緒に記載されていたりする。それを見ながら従属判断をする。

以下が主キー(これが決まればデータが一意に特定できるもの)として使えそうだと導き出せた。

{電子会議室番号, 投稿番号}
「電子会議室番号」が決まれば「議題」「分野番号」「表示順」「作成者ユーザーID」が決まる。また、「分野番号」が決まることによって「分野名」が決まる(推移的従属性)
「投稿番号」が決まれば、「投稿番号」「暴行者ユーザーID」が決まる。また、「電子会議室番号、投稿番号」が決まることによって「投稿本文」が決まる

{分野名, 表示順, 投稿番号}
「分野名, 表示順」が決まることによって「電子会議室番号」が決まる。あとは上記と同じ。

うーむ、、理解できているか怪しい。。なんとなく解けるが突き詰めて定義を学ぼうとすると混乱してくる。

関数従属性

Aが決まれば自動的に決まってくる値(B)があれば、これは関数従属性があると言える A -> Bと表記する

副数の値が関数従属性に関わる場合もある。 AとBが決まればCが決まる場合 {A, B} -> Cと表記する

Aが決まれば、BとCが決まる場合 A -> {B, C}と表記する

部分関数従属性

上記の関数従属性でいう{A, B} -> Cの時、 別途B -> Dという関数従属性があるなら、これを部分関数従属といえる。 ただし、Dは他の関数従属性のキーになっている場合は部分関数従属性があるとは言えない

推移的関数従属性

上記の関数従属性でいうAが決まればBが決まり、Bが極まればCが決まる場合 A -> B -> C これを推移的関数従属性という。

システムの説明があって、テーブル設計の問題点などについての問題

細かい挙動をイメージしだすと全体像がわからなくなってしまう事が多かった。

最初に問題文を読むときに、定義されているテーブルにどういう感じでレコードが追加されていくのかをイメージしながら読み、 そのレコードのキーは何になるのかをイメージしておくと全体像を見失わないで済む。

【rails】コード読んだメモ

rubyのコードリーディングの練習でrailsのコードを少し読んだのでメモ

railsでのautoloadについて

railsではActiveSupportというmoduleが使われていて、色々便利な機能を提供してくれているらしい。
その一つにActiveSupport::Autoloadがあって、これをextendすると、 デフォルトのautoloadがオーバーライドされ、シンボル名だけでファイルパスを指定することなくautoloadを出来るようになるっぽい。 ただし、ディレクトリ構造はActiveSupport::Autoloadが想定している構造にしておく必要がある

module ActiveSupport
  extend ActiveSupport::Autoload
 
  autoload :Concern

上記で、active_support/concern.rbのConcernをautoload指定してくれるようになる。

ActiveRecordでのwhereの定義場所などについて

Modelの継承元であるActiveRecord::Baseを見ると、 whereメソッドはActive_Supportで提供されているdelegateメソッドで委譲されている。
※delegeteメソッドもActiveSupportの機能

委譲先はallメソッドによって導きだされる。

例えば、SomeModel.where("条件")を実行した場合
Relationクラスのインスタンス[SomeModel]が委譲先として導きだされ、Relation#whereが実行される。

whereメソッドはQueryMethodsモジュール(query_methods.rb)に定義されており、Relationにてincludeされている。

【Rails】 Capybaraの使い方メモ

参考)
https://www.rubydoc.info/github/jnicklas/capybara/Capybara
https://github.com/teamcapybara/capybara
https://qiita.com/morrr/items/0e24251c049180218db4

railsへのインストール

Gemfilesへ追記し、bundle installでインストールする。
rails 5.2.2では初期インストールされていた。

使い方

参考)
https://github.com/teamcapybara/capybara#using-capybara-with-rspec

テストスクリプトの配置

spec/features/以下 または spec/systems/以下にxxxxx_spec.rbとして配置する。
※機能中心にテストするならfeatures以下、表示中心にテストするならviews以下に配置するのかと想像している。systems/以下はどのようなケースで使うべき?

記述内容サンプル

/hello/indexページにhelloと表示されているかを確認する場合

require 'rails_helper'

describe "capybaraの実験", type: :feature do
  before :each do
    # 何か事前に必要な処理などがあれば
    visit '/hello/index' # 指定urlへアクセス
  end

  # RSpecマッチャー(以下参照)を使うサンプル
  # pageはページ自体を表す
  it "アクセスできるかテストする" do
    expect(page).to have_content "hello" 
  end

  # 要素を取得して、その属性が意図しているものか確認するサンプル
  it "全ての画像タグが挿入されているか" do
    all('.imageList__img').zip(get_all_urls) do |element, expected_url|
      expect(element[:src]).to eq(expected_url)
    end
  end

  # 要素を指定して取得して、そのinnerHTMLの要素が意図しているものか確認するサンプル
  it "表示されている名前がただしいか?" do
    elm = find('#name')
    expect(elm.text).to eq("山田太郎")
  end

RSpecで使う場合のマッチャー

一覧)
https://www.rubydoc.info/github/jnicklas/capybara/Capybara/RSpecMatchers

上記サンプルのhave_contentは指定した文字列を含んでいるかどうかをチェックしてる。

capybaraのElementクラスのメソッド一覧

一覧)
https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Node/Element

要素に対して、クリックを行ったり、テキストを挿入したりするメソッドもある。
element[:src]のような書き方でその属性にアクセスできる。

実行方法

RSpecを実行したら実行された。

bundle exec rspec

【Rails】RSpecの使い方メモ

参考:
GitHub - rspec/rspec-rails: RSpec for Rails-3+
RSpec Expectations 3.8 - RSpec Expectations - RSpec - Relish

インストール

Gemfileに追記

# Run against the latest stable release
group :development, :test do
  gem 'rspec-rails', '~> 3.8'
end

bundle installを実行してインストール

bundle update rspec-railsを実行して初期化

model用RSpec

コマンドでRSpec用ファイル作成

rails generate rspec:model user

テスト記述サンプル

Dogモデルをテストすると仮定。
dob.nameにはunique indexが貼られているとする

require 'rails_helper'

RSpec.describe Dog, type: :model do
  context "存在するdogを取得" do
    it "取得できる" do
      dog = Dog.find_by(name: "pochi")
      expect(dog).to be_an_instance_of(Dog)
    end
  end

  context "存在しないdogを取得しようとした場合" do
    it "nilが取得される" do
      dog = Dog.find_by(name: "bobobobo")
      expect(dog).to be_nil
    end
  end

  context "nameが重複しないdogを保存しようとした場合" do
    it "保存できる" do
      dog = Dog.new(name: "hachi")
      expect(dog.save).to be true
    end
  end

  context "nameが重複するdogを保存しようとした場合" do
    it "例外が発生して保存できない" do
      dog = Dog.new(name: "pochi")
      expect{ Dog.save }.to raise_error(ActiveRecord::RecordNotUnique)
    end
  end
end

実行

bundle exec rspec

マッチャー一覧

Built in matchers - RSpec Expectations - RSpec - Relish

Railsの開発環境とステージング環境をDockerを使って構築したのでメモ

Railsの開発環境とステージング環境をDockerを使って構築してみた。
注意:Docker周りインフラ周りについてエキスパートではないので本当にこれが要求を満たせているのかは不明

ディレクトリ構成

.
├── app
│   ├── Dockerfile  # webサーバー構築用
│   ├── conf.d 
│   │   └── rails_app.conf  # nginxの設定ファイル
│   ├── rails_app/  # railsアプリのルートディレクトリ
├── db
│   ├── data/  # dbの実データのマウントディレクトリ
│   └── db_init.sql  # db初期化時に実行するスクリプト(dbとuserの作成)
└── docker-compose.yml

./docker-compose.yml

version: '3'

services:
  app:
    build: ./app/
    volumes:
      - ./app/log/nginx:/var/log/nginx
      - ./app/conf.d:/etc/nginx/conf.d
      - ./app/rails_app:/app
    ports:
      - "80:80"
    depends_on:
      - db
  db:
    image: postgres
    ports:
      - "5432:5432"
    volumes:
      - ./db/data/data:/var/lib/postgresql/data
      - ./db/db_init.sql:/docker-entrypoint-initdb.d/db_init.sql

・DBサーバーとWEBサーバーを使ってrailsアプリを動作させるための設定
・./app/DockerfileにNginxとRubyrailsのインストールと起動コマンドが記載されている
・volumesのマウント系はDockerfileに書こうとすると絶対パスで書かないといけない?っぽかったのでこちらに書いた。(どちらになにを書くべきなのか理解がまだあやふや)
・現状はrailsアプリコードをvolumeでマウントするようにしているが、railsが生成するキャッシュファイルなどがroot権限で作成されてしまい、ホスト側から触りづらいので、マウントではなくcopyやaddなのでイメージに含めてしまうのがいいのかも。(で、キャッシュファイルなどを保存したければ個別にvolumeマウント設定をしておく)

・- ./db/data/data:/var/lib/postgresql/data
DBのvolumeマウントについて、dockerが管理するvolumeに任せることもできるみたいだったが、ディレクトリ内にファイルとして存在してくれていたほうが管理しやすいと思っているのでプロジェクトディレクトリ内にマウントする。

・- ./db/db_init.sql:/docker-entrypoint-initdb.d/db_init.sql
postgresqlの初期化時にdb_init.sqlを実行してもらう設定。
db_init.sqlの内容は、railsで使用するDBとuserを作成するsqlが書かれている。

./db/init_db.sql

railsで使用するDBとuserを作成するsqlが書かれている。
・fixtureの実行時にSUPERUSERであることが求められたのでSUPERUSERにした。

create role rails_app_dev login password 'rails_app_dev';
ALTER USER rails_app_dev WITH SUPERUSER;
create role rails_app_prod login password 'rails_app_prod';
ALTER USER rails_app_prod WITH SUPERUSER;

create database rails_app_dev;
ALTER DATABASE rails_app_dev OWNER TO rails_app_dev;

create database rails_app_prod;
ALTER DATABASE rails_app_prod OWNER TO rails_app_prod;

create database rails_app_test;
ALTER DATABASE rails_app_test OWNER TO rails_app_dev;

./app/Dockerfile

・NginxとRubyrailsのインストールと起動コマンドの設定。 ・bundle installをCMDでやっているがここでやるべきなのかわからないがとりあえず動くようだ

FROM ubuntu:18.04


WORKDIR /root


# ロケール設定
ENV LC_ALL C.UTF-8
ENV LANG C.UTF-8


# shellをbashに指定
SHELL ["/bin/bash", "-c"]


# 最新状態へ
RUN apt update
RUN apt -y upgrade


# 必要なコマンドインストール
RUN apt -y install build-essential wget zlib1g-dev libssl-dev libbz2-dev libreadline-dev libpq-dev


# nginxのインストール
RUN apt -y install nginx


# Rubyのインストール
RUN wget https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.1.tar.gz && \
tar zxvf ruby-2.6.1.tar.gz && \
cd ruby-2.6.1 && \
./configure && \
make && \
make install


# railsのインストール
RUN gem install rails


WORKDIR /app
EXPOSE 80
CMD (bundle install && rails assets:precompile RAILS_ENV=production && rails s -e production) & nginx -g "daemon off;"

./app/conf.d/rails_app.conf

・Nginxからpumaへリクエストが通るように設定

# 書籍 Ruby on Rails5 アプリケーションプログラミングを参考に記載。
upstream rails_app {
  server localhost:3000;
}


server {
  listen 80;
  server_name localhost;
  root /app/public;


  location / {
    try_files $uri @rails_app;
  }


  location @rails_app {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://rails_app;
  }


  error_page 500 502 503 504 /500.html;
}

./app/rails_app/config/database.yml

railsのDB設定

default: &default
  adapter: postgresql
  encoding: unicode
  port: 5432
  # For details on connection pooling, see Rails configuration guide
  # http://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>


development:
  <<: *default
  host: localhost
  database: rails_app_dev
  username: rails_app_dev
  password: rails_app_dev


test:
  <<: *default
  host: localhost
  database: rails_app_test
  username: rails_app_dev
  password: rails_app_dev


production:
  <<: *default
  host: db
  database: rails_app_prod
  username: rails_app_prod
  password: rails_app_prod

seedなどの設定

割愛するが、本来なら再設定が必要なのかも(デフォルトでもインストール毎に違う値が初期設定されているっぽかったが)

運用方法

開発時

'・docker-compose up -d db'でDBサーバーコンテナを起動しておく。
rails_app/以下のコードを編集し、rails serverコマンドでテスト実行する。(localhost:3000へアクセス)

ステージング環境テスト時

docker-composer up -d でWEBサーバーコンテナとDBサーバーコンテナを起動し localhost:80で確認する

【Docker】単なるUbuntuイメージを起動してすぐ終了させないようにするコマンド

直感的でないのですぐ忘れるからメモ

docker run -itd ubuntu /bin/bash

接続するには

docker ps

でコンテナ名を調べて

docker exec -it [コンテナ名] /bin/bash

【Django】ロギング設定について

いまいち使い方・設定の仕方がよくわからなかったので色々しらべた。
現状の知識をメモっとく。

Djangoで使われているロギング機構は、 別にDjango独自の機能ではなく、pythonの標準で用意されているloggingが使用されているという事のよう。

なので、特にDjangoでしか通用しない知識ではないが、 ここではDjangoでの設定の仕方をベースに記載する。

ロガーの設置方法

ロギングで必要なのは
(1)ログ出力したい部分にメッセージを指定
(2)指定したメッセージをどのように出力するか設定
の2つ。

(1)ログ出力したい部分にメッセージを指定

ライブラリ内やロジック内など要所要所に埋め込む。
例)

# このようにすることで、logger毎に異なるログ出力設定を指定できるようになる。
logger = logging.getLogger(__name__)

logger.debut("debugメッセージ")
logger.warning("warningメッセージ")
logger.info("infoメッセージ")
logger.error("errorメッセージ")
logger.critical("criticalメッセージ")

# ログ出力設定で指定されるログレベルによって、出力されるかどうかが変わる。
# 上記の場合、levelがERRORで指定された場合、logger.error(),logger.critical()のメッセージのみ出力される

(2)指定したメッセージをどのように出力するか設定

(1)で埋め込まれているメッセージをどのように出力するか(コンソールか、ファイルか等)の設定を行う。
Djangoでは、settings.py内でLOGGING変数に設定ディクショナリを設定する。

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'filters': {
        'require_debug_false': {
             # ↓このあたりの書式についてまだよくわかってない。
            '()': 'django.utils.log.RequireDebugFalse',
        },
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    # 出力形式を指定できる。下記のhandlersの設定に渡して使う。
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
    },
    #  どこに出力するなどが設定できる。下記のrootやloggers設定に渡して使う。
    'handlers': {
        # ↓これは、DEBUGレベルでStreamHandler(標準出力)に、verboseフォーマッタの形式で出力するという意味
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
        },
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler'
        },
        'file_images_importer': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',
            'filename': '/app/sync/logs/import_twitter_images.log',
            'formatter': 'verbose',
        },
        'file_django_log': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': '/app/sync/logs/django.log',
            'formatter': 'verbose',
        },
    },
    'root': {
        'handlers': ['console', 'file_django_log'],  # ハンドラは複数渡せる。
        'level': 'INFO',
    }
}

ライブラリ毎に設定を変えたければ、'loggers'キーに設定を追加する

    'loggers': {                                                                                                                    
        'django': {                                                                                                                 
            'handlers': ['console', 'mail_admins', 'file'],                                                                         
            'level': 'INFO',                                                                                                        
        },                                                                                                                          
        'py.warnings': {                                                                                                            
            'handlers': ['console', 'file'],                                                                                        
        },                                                                                                                          
        'django.db.backends': {                                                                                                     
            'handlers': ['console'],                                                                                                
            'level': 'DEBUG',                                                                                                       
        },