今更なが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のルール
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機能を使った話。
やりたいイメージ
前提
- RDSのマスターとレプリカで負荷分散させたい
- ELBでやろうとしたけどEC2にしか対応していない
- HAproxy MySQLproxyちゃんと構築する時間がない
nginx再ビルド
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への保存
業務でログ収集をやることになったのでその時のメモ
やりたいイメージ
- 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が肝
参考
@Spring_MT buffer_size_limit ではなくて buffer_chunk_limit でしたorz しかし v0.10.27 の buf_file だとデフォルトで 8MB なので、何か違う気がする…。
— Sadayuki Furuhashi (@frsyuki) 2012年11月27日
[監視用スクリプト]*cronで動かす
参考
- 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
virtualbox vagrant chef で仮想環境作りたい
vvirtualbox4.3.12
vagrant1.3.5
インストール済み
Ubuntu 14.04
http://www.ubuntulinux.jp/News/ubuntu1404
作成後、設定→ストレージから
CD/DVDドライブにダウンロードしてきたubuntu14.04のisoファイルセット
設定→システムから起動順序変更
ubuntuのインストル設定手順に従いインストール。
ネットワークはNATにした。
ブリッジにしようと思ったがPPPoeでの設定になるので、IP固定できない。
【ssh】
ポートワードの設定にsshを追加。
ターミナルからssh接続できるようにする。
ubuntuにsshデーモンインストール
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
・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
|
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
と思ったけどわからないまた今度。
今日思った事。
遠回りしてるけど、コレが自分のやり方です。