今更ながGitHubのカンバン機能「Project」を使って見る

GitHub Projects

http://dev.classmethod.jp/tool/git/github-projects/

目的

プロジェクトで、ClientとServer間のコミュニケーションの効率化/作業の見える化をしたかった

概要

ディレクターとデザイナーとのコミュニケーションは別ツールを使用。 Projectは基本プロジェクトのエンジニア間での連携で使用するつもりです。

GitHub Projectは大きく分け3つの概念があります。

  • Project
  • Card
  • Note





Projectについて

Projectは名前のとおりです。発足したプロジェクト名を記載してください Projectの単位ですが2通りあります。

  • organization単位(Organizationプランのみ)
  • repository単位

どちらでもいいと思いますが、 organization単位で作成しておくと、一目でどういうプロジェクトが 動いているのかを確認できるので私はこちらをお勧めします。

Cardについて

CardはProjectに1対多で紐づく概念です。 役割や概念の違いで分けるといいと思います。

Noteについて

NoteはCardに1対多で紐づく概念です。 役割や概念に関係するTopicsを記載するものと考えていいと思います。

またNoteはRepositoryのIssueの情報と紐づけることができます。





ルールExample

こういうツール系はある程度ルールを決めておくと運用しやすいですね

Projectのルール

  • 命名規則
    • {事業部名}_{サービス名}_{Version}
    • もちろん英語

Cardのルール

  • 画面機能構成がわかるカードを1つ
  • 大枠の仕様がわかるカードを1つ
  • あとは役割別でCardを作成(ex: Android, iOS, API etc)

Noteのルール

  • 命名規則
    • [画面機能構成名][新規 or 修正 or 要望]-{簡潔なタイトル}
  • 着手中のものはIssueと紐付けて自分をassigneesとして登録
  • 要望のものはIssueと紐付けて要望に対応してほしい人をassigneesとして登録
  • CloseしたIssueのNoteはその機能がリリースされてからCardから削除する

Issueの運用ルール

  • 基本的に直接Issueは登録しない(NoteをIssueに紐づける)
  • タイトルはNoteの命名規則に従う(自然と統一されるはず)
  • ボールを持っている人がassignees
  • labelはNoteのルールの[新規 or 修正 or 要望]に従う
    • 新規 ラベルなし
    • 修正 bugs
    • 要望 help_wanted

Commitルール

  • CommitのタイトルはIssueのタイトル名#nでコミット(Issueのタイトルコピペ)
  • 詳細を記載するっ場合は改行して記載





良いなと感じているところ

プロジェクトを開くと開発で必要な情報がどこにあるのか一目でわかるかも? 誰が今何をやっているのかわかるので、自分のタスクの優先順位づけがしやすくなる?

足りないと感じているところ

期日を設定しにくい(他のツールと組み合わせたりして改善?) まだ運用の準備段階なので何とも言えないですが、

運用し始めてからも改善していけたらと思います。

気軽にRDSの負荷分散

RDSの負荷分散にnginxのstream機能を使った話。

やりたいイメージ
f:id:sonedayuya:20170211160017p:plain


前提

  • RDSのマスターとレプリカで負荷分散させたい
  • ELBでやろうとしたけどEC2にしか対応していない
  • HAproxy MySQLproxyちゃんと構築する時間がない


nginx再ビルド

  • nginxの標準で対応しているロードバランサーはhttp通信のみ対応。
  • tcpでも対応させたい場合はstreamモジュールが必要
  • Nginx v1.9.0以上が対応している機能

TCP Load Balancing with NGINX Plus R6 and NGINX 1.9.0

http://nginx.org/download/nginx-1.11.9.tar.gz
./configure --with-stream
make
make install


設定ファイル
nginx.conf

helthcheckの設定とかtimeout設定したい場合は、

色々オプションあるので適当に。

以下最低限

stream {
    error_log /var/log/nginx/mysql_proxy.log;

    upstream mysql {
        zone backends 64k;
        server rds-master:3306 fail_timeout=30s;
        server rds-rep:3306 fail_timeout=30s;
    }

    server {
        listen 3360;
        proxy_pass mysql;
        proxy_timeout 10s;
        proxy_connect_timeout 1s;
    }
}




確認

  • 接続できればOK
mysql -h 127.0.0.1 --port 3360 --protocol tcp -u hoge --pass
  • ちゃんと分散されてるかはMasterとReplicaにそれぞれ接続しコネクション数を確認
 SELECT * FROM information_schema.PROCESSLIST;
  • あとはwightの調整とか分散具合に合わせて調整すればOK
  • writeの場合はmasterに書き込みにいくようApplicatino側を修正。
  • 書き込めたらOK


fluentdによるlogの集約とS3への保存

業務でログ収集をやることになったのでその時のメモ

やりたいイメージ

f:id:sonedayuya:20170210231910p:plain

  • A B C Dのnginxのログはltsv形式
  • A B C DのログをEに集約
  • Eに集約したログを大dailyでzipしてs3に保存

各サーバーのHDのスペックは以下

A B C D - 8G
E - 100G

まずは各サーバーの設定とtd-agentのインストール

[公式ドキュメント]
http://docs.fluentd.org/v0.12/articles/quickstart

[インストール前に]

/etc/security/limits.conf

root soft nofile 65536
root hard nofile 65536
* soft nofile 65536
* hard nofile 65536

/etc/sysctl.conf

net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10240    65535


[インストール]

curl -L https://toolbelt.treasuredata.com/sh/install-redhat-td-agent2.sh | sh

[設定]

コレクター A B C D

<source>
  @type tail
  tag nginx.log
  path /usr/local/nginx/logs/access.log
  pos_file /var/log/td-agent/access.log.pos
  format ltsv
  time_key time
</source>

<match nginx.log>
  type forward
  buffer_path /var/log/td-agent/buffer/access.*.buffer
  buffer_chunk_limit 8M
  buffer_queue_limit 256
  queued_chunk_flush_interval 20s
  try_flush_interval 1
  flush_interval 60s
  retry_wait 30s
  retry_limit 5
  num_threads 2
  <server>
    name nginx_log
    host xx.xxx.xxx.xxx
    port 24224
  </server>
</match>

集約サーバー E

<source>
  type forward
  port 24224
  bind 0.0.0.0
</source>

<match nginx.log>
  type copy
  #<store>
  #  type file
  #  format ltsv
  #  path /var/log/fluentd/nginx_log
  #  buffer_chunk_limit 30g
  #  time_slice_format %Y%m%d
  #  time_slice_wait 1m
  #  compress gzip
  #  flush_at_shutdown true
  #</store>

  <store>
    @type s3
    path log/nginx/%Y/%m/%d/ #S3の保存ディレクトリ
    format ltsv #logの形式
    aws_key_id XXXXXXXXXXXXXXX
    aws_sec_key XXXXXXXXXXXXXXX
    s3_bucket hoge_bucket
    s3_region hoge_region
    acl public-read
    buffer_path /var/log/fluentd/access.*.log
    time_slice_format %d
    buffer_type file
    time_slice_wait 10m
    buffer_chunk_limit 30g #デイリーのログの容量に合わせて変更
  </store>
</match>


※ログのローテトでハマりました。buffer_chunk_limitが肝
参考





[監視用スクリプト]*cronで動かす

参考

www.slideshare.net

  • td-agentのプロセスが死んだ場合再起動&slackへの通知
#!/bin/sh
#slack通知
function post_to_slack () {
  SLACK_MESSAGE="*$1*"
  SLACK_URL=hoge
  case "$2" in
    RECOVER)
      SLACK_ICON=':slack:'
      ;;
    DOWN)
      SLACK_ICON=':warning:'
      ;;
    *)
      SLACK_ICON=':slack:'
      ;;
  esac

  curl -X POST --data "payload={\"text\": \"<!channel> \n ${SLACK_ICON} ${SLACK_MESSAGE}\"}" ${SLACK_URL}
}


function restart_td_agent () {
  sudo /etc/init.d/td-agent restart
}

function stop_td_agent () {
  sudo /etc/init.d/td-agent stop
}

#test用
#stop_td_agent

SERVER_NAME="manga-OauthChargeUser-api-prod_0"

# process数が2以下ならalert&t再起動
PROCESS_COUNT=`ps w -C ruby -C td-agent --no-heading | grep td-agent | wc -l`
if  [ ${PROCESS_COUNT} != 2 ] ; then
  DATE=`date`
  STATUS="DOWN"
  MESSAGE="[${SERVER_NAME}][td-agent] \`\`\`プロセスがダウンしました\`\`\`"
  post_to_slack "${MESSAGE}" "${STATUS}"

  restart_td_agent

  NOT_RECOVER=true
  while $NOT_RECOVER
  do
    PROCESS_COUNT=`ps w -C ruby -C td-agent --no-heading | grep td-agent | wc -l`
    if  [ ${PROCESS_COUNT} == 2 ] ; then
      STATUS="RECOVER"
      MESSAGE="[${SERVER_NAME}][td-agent] \`\`\`プロセスが復帰しました\`\`\`"
      NOT_RECOVER=false
      post_to_slack "${MESSAGE}" "${STATUS}"
    fi
  done
fi

exit 0

nginxの設定ファイルのメモ

openresty-1.11.2.2 Linux version 4.4.30-32.54.amzn1.x86_64

APIで高速化するために設定ファイルをごにょごにょ。 一旦こんな感じになった。 もっとこうしたらいいとか、 ここどうなってるのとかあればツッコミが欲しい。

他にもカーネルパラメーターとかちょっといじった。

どんなことしたか、別の機会に書こう!

nginx.conf

user  nginx;
worker_processes  auto;
worker_rlimit_nofile 65536;
pid /var/run/nginx.pid;

events {
    worker_connections 65536;
    #worker_connections 1024;
    multi_accept on;
    #accept_mutex off;
    use epoll;
}


http {
    include       mime.types;

    server_tokens off;
    client_header_timeout  3m;
    client_body_timeout    3m;
    send_timeout           3m;

    sendfile on;
    sendfile_max_chunk 512k;

    tcp_nopush on;
    tcp_nodelay on;

    access_log off;
    error_log off;

    open_file_cache max=1000 inactive=20s;
    open_file_cache_valid    30s;
    open_file_cache_min_uses 5;
    open_file_cache_errors   off;

    keepalive_requests 1000;
    keepalive_timeout 120;

    proxy_headers_hash_max_size 1024;
    proxy_headers_hash_bucket_size 256;

    log_format  main_ext  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for" '
                          '"$host" sn="$server_name" '
                          'rt=$request_time '
                          'ua="$upstream_addr" us="$upstream_status" '
                          'ut="$upstream_response_time" ul="$upstream_response_length" '
                          'cs=$upstream_cache_status' ;

    include /usr/local/openresty/nginx/conf/site-enable/*.conf;
}

site-enable/hoge.conf

upstream hoge {
    server unix:/var/run/circus/hoge.sock;
    keepalive 1000;
}

server {
    listen 80 default_server;

    server_name hoge.com;

    gzip on;
    gzip_http_version 1.0;
    gzip_comp_level  6;
    gzip_types       application/json;
    gzip_vary on;
    gzip_proxied any;
    gzip_buffers 16 8k;

    access_log logs/hoge.access.log main_ext buffer=32k;
    error_log  logs/hoge.error.log warn;

    # health check
    location = /hoge {
       access_log off;
       empty_gif;
       break;
    }

    location / {
      proxy_pass http://hoge;
      proxy_http_version 1.1;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-Host $host;
      proxy_set_header Connection "";
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $remote_addr;
      proxy_set_header X-Forwarded-Host $host;
    }

}

vagrant cloud

自前でvagrant boxファイル作ったメモ下書きにあると思ってたら消しちゃってた。あーあ、って思ってたら、vagrantがアップグレードされてて、vagrant cloudっていう便利なものがでてきていて、
なんだか感動しちゃった。と同時に焦りも。

https://vagrantcloud.com/

時間なんとか作ってただ使うだけに留まらないようにする。

virtualbox vagrant chef で仮想環境作りたい

vvirtualbox4.3.12
vagrant1.3.5
インストール済み

Ubuntu 14.04
http://www.ubuntulinux.jp/News/ubuntu1404

新規で仮想マシン作成
Ubuntuの64bit

作成後、設定→ストレージから
CD/DVDドライブにダウンロードしてきたubuntu14.04のisoファイルセット

設定→システムから起動順序変更

ubuntuのインストル設定手順に従いインストール。
ネットワークはNATにした。
ブリッジにしようと思ったがPPPoeでの設定になるので、IP固定できない。

ssh
ポートワードの設定にsshを追加。
ターミナルからssh接続できるようにする。

ubuntusshデーモンインストール
sudo apt-get install ssh

【VirtualboxGuestAdditionインストール】

Cのコンパイルに必要なライブラリー
sudo apt-get install build-essential

VirtualboxGuestAddition インストールに必要らしい
sudo apt-get install module-assistant
sudo m-a prepare

sudo mount /dev/cdrom /mnt

cd /mnt
sudo ./VboxLinunxAdditions.sh


参考
http://d.hatena.ne.jp/kumamidori/20130512/p1

ssh公開鍵認証】
ssh、公開鍵で認証できるようにする。
公開鍵作成済み。

ゲストOS側に.sshディレクトリ作成。
sudo mkdir .ssh

vagrantユーザーで認証するから
sudo chown vagrant:vagrant .ssh
.sshフォルダの権限700じゃないとssh接続できない
sudo chmod 700 .ssh

公開キーを転送(2222でゲストOSの22に転送する設定にしてある)
scp -P 2222 ~/.ssh/id_rsa.pub vagrant@localhost:~/.ssh/authorized_keys

vagrant box作成 vagrant up】

VMを元にボックスファイル作成
vagrant package --base ubuntu64(ホスト名)

・boxファイルから仮想環境(ubuntu14.04VagrantBox)作成
vagrant box add ubuntu14.04VagrantBox package.box

・仮想環境初期化
vagrant init ubuntu14.04VagrantBox

・仮想環境起動
vagrant up

ssh公開鍵認証なのでVagrantfileに秘密キーの場所設定してあげる

Vagrant::Config.run do |config|
    config.vm.box = "ubuntu14.04VagrantBox"
    config.ssh.private_key_path = "~/.ssh/id_rsa"
end


config.ssh.private_key_path = "~/.ssh/id_rsa"
~/.ssh/id_rsa → 公開鍵の場所

vagrant up うまく行けば一旦終わり。
もちょっとカスタマイズしよ。

redisインストール

ただのメモ

現状の安定板のgipファイル
wget http://download.redis.io/releases/redis-2.8.9.tar.gz

解凍
tar xvzf redis-2.8.9.tar.gz

できたフォルダに入って

make
make test
make install

tcl8.5以上必要って怒られたから
yum install tclでインストールした。

【Rails】WEBrickについて

RackでWebサーバーたてた。
よくわからないから、コードを追っていたら前からわからなかったWEBrickについてなんかわかったような。
めも

まずはコード張っとく。

require 'webrick'
require 'erb'

document_root = '/var/www/html/rails/rubyApp/app/'

server = WEBrick::HTTPServer.new({
  :DocumentRoot => document_root,
  :BindAddress => '0.0.0.0',
  :Port => 10080
})


server.mount_proc("/") { |req, res|

  path = File.join(document_root,*req.path.split("/"))
  path += ".utf-8" if /\.html\.[a-z][a-z]$/ =~ path 

  File.open(path){|file|
  	res.body = (ERB.new(File.read(path)).result(binding))
  }
  
  # 拡張子とContent-Typeの対応表
  content_types = {
    ".html" => "text/html", ".txt" => "text/plain",
    ".jpg" => "image/jpeg", ".jpeg" => "image/jpeg",
    ".gif" => "image/gif", ".png" => "image/png", 
    ".mp3" => "audio/mpeg", ".mid"   => "audio/midi", 
    ".css"   => "text/css", ".xhtml" => "application/xhtml+html",
    ".svg"   => "image/svg+xml"  
        
  }
  
  # filenameの拡張子を見てContent-Typeを設定
    content_type = content_types[File.extname(path)]
    
     #Content-Typeが見つからなかったらtext/htmlを設定
    if content_type==nil
      content_type = "text/html"
    end
         
    res["Content-Type"] = content_type
 }

['INT', 'TERM'].each {|signal|
  Signal.trap(signal){ server.shutdown }
}

server.start
<||


まず
werbrickとerb(テンプレートエンジン読み込む)

>|ruby|
require 'webrick'
require 'erb'

WEBrick::HTTPServer.newしてあげる。
/.rbenv/versions/2.1.0/lib/ruby/2.1.0/webrick.rb
のinitialize method で初期化される。

document_root = '/var/www/html/rails/rubyApp/app/'

server = WEBrick::HTTPServer.new({
  :DocumentRoot => document_root,
  :BindAddress => '0.0.0.0',
  :Port => 10080
})


webrickのinitialize method確認
/.rbenv/versions/2.1.0/lib/ruby/2.1.0/webrick.rb

def initialize(config={}, default=Config::HTTP)
      super(config, default)
      @http_version = HTTPVersion::convert(@config[:HTTPVersion])

      @mount_tab = MountTable.new
      if @config[:DocumentRoot]
        mount("/", HTTPServlet::FileHandler, @config[:DocumentRoot],
              @config[:DocumentRootOptions])
      end

      unless @config[:AccessLog]
        @config[:AccessLog] = [
          [ $stderr, AccessLog::COMMON_LOG_FORMAT ],
          [ $stderr, AccessLog::REFERER_LOG_FORMAT ]
        ]
      end

      @virtual_hosts = Array.new
end

いっぱいnewしてる。
ここで書くときりがないので、

super(config, default)

コレより前に

require 'webrick/server'

書いてあるのと

class HTTPServer < ::WEBrick::GenericServer
<||

::WEBrick::GenericServer継承してるから
中身確認
.rbenv/versions/2.1.0/lib/ruby/2.1.0/webrick/server.rb

>|ruby|
def initialize(config={}, default=Config::General)
      @config = default.dup.update(config)
      @status = :Stop
      @config[:Logger] ||= Log::new
      @logger = @config[:Logger]

      @tokens = SizedQueue.new(@config[:MaxClients])
      @config[:MaxClients].times{ @tokens.push(nil) }

      webrickv = WEBrick::VERSION
      rubyv = "#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
      @logger.info("WEBrick #{webrickv}")
      @logger.info("ruby #{rubyv}")

      @listeners = []
      unless @config[:DoNotListen]
        if @config[:Listen]
          warn(":Listen option is deprecated; use GenericServer#listen")
        end
        listen(@config[:BindAddress], @config[:Port])
        if @config[:Port] == 0
          @config[:Port] = @listeners[0].addr[1]
        end
      end
end

またいろいろ書いてる。。。
まぁログの設定とかか。
listen methodだけみとこ。多分ソケット作ってる。

def listen(address, port)
    @listeners += Utils::create_listeners(address, port, @logger)
end

また違うファイルから読んでるー。。
一応確認

.rbenv/versions/2.1.0/lib/ruby/2.1.0/webrick/utils.rb

>|ruby|
##
# Creates TCP server sockets bound to +address+:+port+ and returns them.
#
# It will create IPV4 and IPV6 sockets on all interfaces.


def create_listeners(address, port, logger=nil)
unless port
raise ArgumentError, "must specify port"
end
sockets = Socket.tcp_server_sockets(address, port)
sockets = sockets.map {|s|
s.autoclose = false
TCPServer.for_fd(s.fileno)
}
return sockets
end
|

Dir.chdirについて

rails server コマンド の ソース をおっていたら、Dir.chdirという関数があった。

  Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru"))

現在のパス上に「config.ru」がなければ、current path を引数の所に変更してくれる。

まぁなんて便利な関数!

Modelのvalidatesメソッドについて

なんかrails難しいな。よくわからず使いたくない笑
Rubyを知る為にRailsのコード読む。

class Member < ActiveRecord::Base
	validates :email, presence: true	
end

ModelはまずActiveRecordモジュールのBaseクラスをを継承している

/ruby/2.1.0/gems/activerecord-4.0.2/lib/active_record/base.rb

module ActiveRecord #:nodoc:
  class Base
    extend ActiveModel::Naming
   ・
 ・
 ・

    include Sanitization
    include AttributeAssignment
    include ActiveModel::Conversion
    include Integration
    include Validations
    include CounterCache
 ・
 ・
  end

  ActiveSupport.run_load_hooks(:active_record, Base)
end

その中でActiveRecord::Validations をinclude
/ruby/2.1.0/gems/activerecord-4.0.2/lib/active_record/validations.rb

module ActiveRecord
  # = Active Record RecordInvalid
  #
  # Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the
  # +record+ method to retrieve the record which did not validate.
  #
  #   begin
  #     complex_operation_that_calls_save!_internally
  #   rescue ActiveRecord::RecordInvalid => invalid
  #     puts invalid.record.errors
  #   end
  class RecordInvalid < ActiveRecordError
    attr_reader :record # :nodoc:
    def initialize(record) # :nodoc:
      @record = record
      errors = @record.errors.full_messages.join(", ")
      super(I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", :errors => errors, :default => :"errors.messages.record_invalid"))
    end
  end

  # = Active Record Validations
  #
  # Active Record includes the majority of its validations from <tt>ActiveModel::Validations</tt>
  # all of which accept the <tt>:on</tt> argument to define the context where the
  # validations are active. Active Record will always supply either the context of
  # <tt>:create</tt> or <tt>:update</tt> dependent on whether the model is a
  # <tt>new_record?</tt>.
  module Validations
    extend ActiveSupport::Concern
    include ActiveModel::Validations

    module ClassMethods
      # Creates an object just like Base.create but calls <tt>save!</tt> instead of +save+
      # so an exception is raised if the record is invalid.
      def create!(attributes = nil, &block)
        if attributes.is_a?(Array)
          attributes.collect { |attr| create!(attr, &block) }
        else
          object = new(attributes)
          yield(object) if block_given?
          object.save!
          object
        end
      end
    end

    # The validation process on save can be skipped by passing <tt>validate: false</tt>.
    # The regular Base#save method is replaced with this when the validations
    # module is mixed in, which it is by default.
    def save(options={})
      perform_validations(options) ? super : false
    end

    # Attempts to save the record just like Base#save but will raise a +RecordInvalid+
    # exception instead of returning +false+ if the record is not valid.
    def save!(options={})
      perform_validations(options) ? super : raise(RecordInvalid.new(self))
    end

    # Runs all the validations within the specified context. Returns +true+ if
    # no errors are found, +false+ otherwise.
    #
    # If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
    # <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.
    #
    # Validations with no <tt>:on</tt> option will run no matter the context. Validations with
    # some <tt>:on</tt> option will only run in the specified context.
    def valid?(context = nil)
      context ||= (new_record? ? :create : :update)
      output = super(context)
      errors.empty? && output
    end

  protected

    def perform_validations(options={}) # :nodoc:
      options[:validate] == false || valid?(options[:context])
    end
  end
end

require "active_record/validations/associated"
require "active_record/validations/uniqueness"
require "active_record/validations/presence"

include ActiveModel::Validations
さらにActiveModel::Validationsをincludeしてる
/gems/activemodel-4.0.2/lib/active_model/validations.rb

require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/except'

module ActiveModel

  # == Active \Model Validations
  #
  # Provides a full validation framework to your objects.
  #
  # A minimal implementation could be:
  #
  #   class Person
  #     include ActiveModel::Validations
  #
  #     attr_accessor :first_name, :last_name
  #
  #     validates_each :first_name, :last_name do |record, attr, value|
  #       record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
  #     end
  #   end
  #
  # Which provides you with the full standard validation stack that you
  # know from Active Record:
  #
  #   person = Person.new
  #   person.valid?                   # => true
  #   person.invalid?                 # => false
  #
  #   person.first_name = 'zoolander'
  #   person.valid?                   # => false
  #   person.invalid?                 # => true
  #   person.errors.messages          # => {first_name:["starts with z."]}
  #
  # Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+
  # method to your instances initialized with a new <tt>ActiveModel::Errors</tt>
  # object, so there is no need for you to do this manually.
  module Validations
    extend ActiveSupport::Concern

    included do
      extend ActiveModel::Callbacks
      extend ActiveModel::Translation

      extend  HelperMethods
      include HelperMethods

      attr_accessor :validation_context
      define_callbacks :validate, :scope => :name

      class_attribute :_validators
      self._validators = Hash.new { |h,k| h[k] = [] }
    end

    module ClassMethods
      # Validates each attribute against a block.
      #
      #   class Person
      #     include ActiveModel::Validations
      #
      #     attr_accessor :first_name, :last_name
      #
      #     validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
      #       record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
      #     end
      #   end
      #
      # Options:
      # * <tt>:on</tt> - Specifies the context where this validation is active
      #   (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>)
      # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
      # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
      # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
      #   if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
      #   or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
      #   proc or string should return or evaluate to a +true+ or +false+ value.
      # * <tt>:unless</tt> - Specifies a method, proc or string to call to
      #   determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
      #   or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
      #   method, proc or string should return or evaluate to a +true+ or +false+
      #   value.
      def validates_each(*attr_names, &block)
        validates_with BlockValidator, _merge_attributes(attr_names), &block
      end

      # Adds a validation method or block to the class. This is useful when
      # overriding the +validate+ instance method becomes too unwieldy and
      # you're looking for more descriptive declaration of your validations.
      #
      # This can be done with a symbol pointing to a method:
      #
      #   class Comment
      #     include ActiveModel::Validations
      #
      #     validate :must_be_friends
      #
      #     def must_be_friends
      #       errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
      #     end
      #   end
      #
      # With a block which is passed with the current record to be validated:
      #
      #   class Comment
      #     include ActiveModel::Validations
      #
      #     validate do |comment|
      #       comment.must_be_friends
      #     end
      #
      #     def must_be_friends
      #       errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
      #     end
      #   end
      #
      # Or with a block where self points to the current record to be validated:
      #
      #   class Comment
      #     include ActiveModel::Validations
      #
      #     validate do
      #       errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
      #     end
      #   end
      #
      # Options:
      # * <tt>:on</tt> - Specifies the context where this validation is active
      #   (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>)
      # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
      # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
      # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
      #   if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
      #   or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
      #   proc or string should return or evaluate to a +true+ or +false+ value.
      # * <tt>:unless</tt> - Specifies a method, proc or string to call to
      #   determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
      #   or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
      #   method, proc or string should return or evaluate to a +true+ or +false+
      #   value.
      def validate(*args, &block)
        options = args.extract_options!
        if options.key?(:on)
          options = options.dup
          options[:if] = Array(options[:if])
          options[:if].unshift("validation_context == :#{options[:on]}")
        end
        args << options
        set_callback(:validate, *args, &block)
      end

      # List all validators that are being used to validate the model using
      # +validates_with+ method.
      #
      #   class Person
      #     include ActiveModel::Validations
      #
      #     validates_with MyValidator
      #     validates_with OtherValidator, on: :create
      #     validates_with StrictValidator, strict: true
      #   end
      #
      #   Person.validators
      #   # => [
      #   #      #<MyValidator:0x007fbff403e808 @options={}>,
      #   #      #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
      #   #      #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
      #   #    ]
      def validators
        _validators.values.flatten.uniq
      end

      # Clears all of the validators and validations.
      #
      # Note that this will clear anything that is being used to validate
      # the model for both the +validates_with+ and +validate+ methods.
      # It clears the validators that are created with an invocation of
      # +validates_with+ and the callbacks that are set by an invocation
      # of +validate+.
      #
      #   class Person
      #     include ActiveModel::Validations
      #
      #     validates_with MyValidator
      #     validates_with OtherValidator, on: :create
      #     validates_with StrictValidator, strict: true
      #     validate :cannot_be_robot
      #
      #     def cannot_be_robot
      #       errors.add(:base, 'A person cannot be a robot') if person_is_robot
      #     end
      #   end
      #
      #   Person.validators
      #   # => [
      #   #      #<MyValidator:0x007fbff403e808 @options={}>,
      #   #      #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
      #   #      #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
      #   #    ]
      #
      # If one runs Person.clear_validators! and then checks to see what
      # validators this class has, you would obtain:
      #
      #   Person.validators # => []
      #
      # Also, the callback set by +validate :cannot_be_robot+ will be erased
      # so that:
      #
      #   Person._validate_callbacks.empty?  # => true
      #
      def clear_validators!
        reset_callbacks(:validate)
        _validators.clear
      end

      # List all validators that are being used to validate a specific attribute.
      #
      #   class Person
      #     include ActiveModel::Validations
      #
      #     attr_accessor :name , :age
      #
      #     validates_presence_of :name
      #     validates_inclusion_of :age, in: 0..99
      #   end
      #
      #   Person.validators_on(:name)
      #   # => [
      #   #       #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>,
      #   #       #<ActiveModel::Validations::InclusionValidator:0x007fe603bb8780 @attributes=[:age], @options={in:0..99}>
      #   #    ]
      def validators_on(*attributes)
        attributes.flat_map do |attribute|
          _validators[attribute.to_sym]
        end
      end

      # Returns +true+ if +attribute+ is an attribute method, +false+ otherwise.
      #
      #  class Person
      #    include ActiveModel::Validations
      #
      #    attr_accessor :name
      #  end
      #
      #  User.attribute_method?(:name) # => true
      #  User.attribute_method?(:age)  # => false
      def attribute_method?(attribute)
        method_defined?(attribute)
      end

      # Copy validators on inheritance.
      def inherited(base) #:nodoc:
        dup = _validators.dup
        base._validators = dup.each { |k, v| dup[k] = v.dup }
        super
      end
    end

    # Clean the +Errors+ object if instance is duped.
    def initialize_dup(other) #:nodoc:
      @errors = nil
      super
    end

    # Returns the +Errors+ object that holds all information about attribute
    # error messages.
    #
    #   class Person
    #     include ActiveModel::Validations
    #
    #     attr_accessor :name
    #     validates_presence_of :name
    #   end
    #
    #   person = Person.new
    #   person.valid? # => false
    #   person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={name:["can't be blank"]}>
    def errors
      @errors ||= Errors.new(self)
    end

    # Runs all the specified validations and returns +true+ if no errors were
    # added otherwise +false+.
    #
    #   class Person
    #     include ActiveModel::Validations
    #
    #     attr_accessor :name
    #     validates_presence_of :name
    #   end
    #
    #   person = Person.new
    #   person.name = ''
    #   person.valid? # => false
    #   person.name = 'david'
    #   person.valid? # => true
    #
    # Context can optionally be supplied to define which callbacks to test
    # against (the context is defined on the validations using <tt>:on</tt>).
    #
    #   class Person
    #     include ActiveModel::Validations
    #
    #     attr_accessor :name
    #     validates_presence_of :name, on: :new
    #   end
    #
    #   person = Person.new
    #   person.valid?       # => true
    #   person.valid?(:new) # => false
    def valid?(context = nil)
      current_context, self.validation_context = validation_context, context
      errors.clear
      run_validations!
    ensure
      self.validation_context = current_context
    end

    # Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
    # added, +false+ otherwise.
    #
    #   class Person
    #     include ActiveModel::Validations
    #
    #     attr_accessor :name
    #     validates_presence_of :name
    #   end
    #
    #   person = Person.new
    #   person.name = ''
    #   person.invalid? # => true
    #   person.name = 'david'
    #   person.invalid? # => false
    #
    # Context can optionally be supplied to define which callbacks to test
    # against (the context is defined on the validations using <tt>:on</tt>).
    #
    #   class Person
    #     include ActiveModel::Validations
    #
    #     attr_accessor :name
    #     validates_presence_of :name, on: :new
    #   end
    #
    #   person = Person.new
    #   person.invalid?       # => false
    #   person.invalid?(:new) # => true
    def invalid?(context = nil)
      !valid?(context)
    end

    # Hook method defining how an attribute value should be retrieved. By default
    # this is assumed to be an instance named after the attribute. Override this
    # method in subclasses should you need to retrieve the value for a given
    # attribute differently:
    #
    #   class MyClass
    #     include ActiveModel::Validations
    #
    #     def initialize(data = {})
    #       @data = data
    #     end
    #
    #     def read_attribute_for_validation(key)
    #       @data[key]
    #     end
    #   end
    alias :read_attribute_for_validation :send

  protected

    def run_validations! #:nodoc:
      run_callbacks :validate
      errors.empty?
    end
  end
end

Dir[File.dirname(__FILE__) + "/validations/*.rb"].each { |file| require file }
<||


Dir[File.dirname(__FILE__) + "/validations/*.rb"].each { |file| require file }
同じ階層のvalidationsディレクトリーのなかのrbファイルをrequireしてる!

あったvalidates.rb!
/gems/activemodel-4.0.2/lib/active_model/validations/validates.rb


>|ruby|
require 'active_support/core_ext/hash/slice'


module ActiveRecord
  module Validations
    module ClassMethods
   ・
 ・
 ・
  class RecordInvalid < ActiveRecordError
   ・
 ・
 ・
      def validates(*attributes)
        defaults = attributes.extract_options!.dup
        validations = defaults.slice!(*_validates_default_keys)

        raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
        raise ArgumentError, "You need to supply at least one validation" if validations.empty?

        defaults[:attributes] = attributes

        validations.each do |key, options|
          next unless options
          key = "#{key.to_s.camelize}Validator"

          begin
            validator = key.include?('::') ? key.constantize : const_get(key)
          rescue NameError
            raise ArgumentError, "Unknown validator: '#{key}'"
          end

          validates_with(validator, defaults.merge(_parse_validates_options(options)))
        end
      end
   ・
 ・
 ・
    protected

      # When creating custom validators, it might be useful to be able to specify
      # additional default keys. This can be done by overwriting this method.
      def _validates_default_keys # :nodoc:
        [:if, :unless, :on, :allow_blank, :allow_nil , :strict]
      end

      def _parse_validates_options(options) # :nodoc:
        case options
        when TrueClass
          {}
        when Hash
          options
        when Range, Array
          { :in => options }
        else
          { :with => options }
        end
      end
    end
  end
end


あったあったvalidates

     def validates(*attributes)
        defaults = attributes.extract_options!.dup
        validations = defaults.slice!(*_validates_default_keys)

        raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
        raise ArgumentError, "You need to supply at least one validation" if validations.empty?

        defaults[:attributes] = attributes

        validations.each do |key, options|
          next unless options
          key = "#{key.to_s.camelize}Validator"

          begin
            validator = key.include?('::') ? key.constantize : const_get(key)
          rescue NameError
            raise ArgumentError, "Unknown validator: '#{key}'"
          end

          validates_with(validator, defaults.merge(_parse_validates_options(options)))
        end
      end
   protected

      # When creating custom validators, it might be useful to be able to specify
      # additional default keys. This can be done by overwriting this method.
      def _validates_default_keys # :nodoc:
        [:if, :unless, :on, :allow_blank, :allow_nil , :strict]
      end

      def _parse_validates_options(options) # :nodoc:
        case options
        when TrueClass
          {}
        when Hash
          options
        when Range, Array
          { :in => options }
        else
          { :with => options }
        end
      end

Valitionモジュールのこのコード

 defaults = attributes.extract_options!.dup


作成したvalidates

validates :email, presence: true	

presence: true	

これを複製


これで

 validations = defaults.slice!(*_validates_default_keys)

よくわからないけど以下のオプションをスライス

  [:if, :unless, :on, :allow_blank, :allow_nil , :strict]

③ ①で複製した配列をループ。クラスを取得して、validate_withに渡す
※ next unless options → optionがtrueの場合処理へ進む
※to_s→配列を文字列に
※camelize → Camel方に直す
※constantize/const_get → モジュール、クラスの取得
※merge→ハッシュの統合

validations.each do |key, options|
    next unless options
    key = "#{key.to_s.camelize}Validator"

     begin
        validator = key.include?('::') ? key.constantize : const_get(key)
      rescue NameError
        raise ArgumentError, "Unknown validator: '#{key}'"
       end

      validates_with(validator, defaults.merge(_parse_validates_options(options)))
end


あー、仕事行かなきゃ、validate_withについては今日中に。


ということで今日はまだ終わっていない。
validates_with

同じ階層にwith.rbってある。
/gems/activemodel-4.0.2/lib/active_model/validations/with.rb

と思ったけどわからないまた今度。


今日思った事。
遠回りしてるけど、コレが自分のやり方です。