タケユー・ウェブ日報

Ruby on Rails や Flutter といったWeb・モバイルアプリ技術を武器にお客様のビジネス立ち上げを支援する、タケユー・ウェブ株式会社の技術ブログです。

クラスの属性は、クラスの特異クラスでattr_xxxxx

やりたいこと

  • クラスごとに値をもたせたい
  • サブクラスやスーパークラスに共有したくない

こういうとき、クラスのインスタンス変数を使うわけですが、attr_accessorみたいにクラスマクロ的なの使ってわかりやすくしたいです。

答え

クラスの特異クラスでattr_accessor

class MyClass
  class << self
    attr_accessor :attr1
  end
end

class HogeClass
  class << self
    attr_accessor :attr2
  end
end

class SubMyClass < MyClass
  class << self
    attr_accessor :attr3
  end
end

MyClass.attr1 = 'hoge'
p MyClass.attr1         # => "hoge"
p SubMyClass.attr1  # => nil
# p HogeClass.attr1  # => undefined method `attr1' for HogeClass:Class (NoMethodError)
SubMyClass.attr3 = 'fuga'
p SubMyClass.attr3 # => "fuga"
# p MyClass.attr3  # => undefined method `attr3' for MyClass:Class (NoMethodError)

スレッド

threads = []
t = Thread.new do
  Thread.pass
  MyClass.attr1 = 'hoge'
  sleep 2
  p MyClass.attr1 # => "hoge"
end
threads.push(t)
t = Thread.new do
  Thread.pass
  sleep 1
  p MyClass.attr1 # => "hoge"
  MyClass.attr1 = "fuga"
end
threads.push(t)

threads.each(&:join)

実行結果

C:\Ruby22-x64\bin\ruby.exe -e $stdout.sync=true;$stderr.sync=true;load($0=ARGV.shift) C:/Users/Yuichi/IdeaProjects/metaprogrammingruby/example/class_attributes.rb
"hoge"
"fuga"

当然ながらスレッドセーフではないので、認識した上で上手く使いましょう。

このあたりのことはメタプログラミングRubyが大変わかりやすいです。おすすめ。

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

分岐元のブランチの変更を取り込む

master から develop を切って作業中、うっかり master に commit しちゃった時など、develop に master の変更を取り込みたい。

リモート(ここではorigin)に最新があるなら

$ git checkout master
$ git status
On branch master

$ git pull origin master

ってやって追従してから

$ git checkout develop
$ git status
On branch develop

$ git rebase master

って感じ。

assetsのデプロイメモ

デプロイ先でprecompileしたくない

Gitにプリコンパイル結果を含めちゃう

  • デプロイ楽。
  • 画像等が多い場合はアレ
  • minifyされたファイルでコミットログに無駄が

やるならgit clean(rake assets:clean すれば勝手にやってくれる模様)するようにする。

$ rake assets:clean
$ rake assets:precompile
$ git add public/assets
$ git commit -m 'assets precompile'

asset_sync

下準備が少し面倒なのと、アセット用のS3に開発中の奴とか公開中の奴とかごちゃごちゃになる点を除けばいいかんじ

ActiveRecordモデルにincludeしたりするModuleの結合テスト

テストのモデルを作って使う方法メモ。

参考にしたコード

CarrierWave::ActiveRecordのテスト

carrierwave/activerecord_spec.rb at 7d4713f251399ca6f107e0660616cd7b9494929c · carrierwaveuploader/carrierwave · GitHub

require 'rails_helper'

# インタフェース的に使うFeatureモジュールのテスト例

# テスト用のActiveRecordモデル
class FeatureTestObject < ActiveRecord::Base
  include Feature
end
$arclass = 0

# とそのマイグレーションを用意して
class FeatureTestMigration < ActiveRecord::Migration
  def self.up
    create_table 'feature_test_objects', :force => true do |t|
    end

  end

  def self.down
    drop_table 'feature_test_objects'
  end
end

describe Feature do
  # migrate/rollbackの実行
  before(:all) { FeatureTestMigration.up }
  after(:all) { FeatureTestMigration.down }

  # after_rollbackコールバックやafter_commitコールバックでエラーを発生する場合にrescueしない
  before :all do
    ActiveRecord::Base.raise_in_transactional_callbacks = true
  end

  # テスト毎に新しいクラスオブジェクトを使うようにする
  # クラスのインスタンス変数をテストごとに共有してしまわないようにする?
  before do
    $arclass += 1
    @class = Class.new(FeatureTestObject)
    Object.const_set("FeatureTestObject#{$arclass}", @class)
    @class.table_name = 'feature_test_objects'
  end

  # テスト本体
  describe ".hoge=(fuga)" do 
    before do 
      @class.hoge = 'fuga'
    end
    it do
      expect(@class.new.hoge).to eq('fuga')
    end
  end
end

Movable Type で特定のMT::Objectサブクラスのコールバックを無理矢理仕込む

こんな感じで無理矢理フックを仕込むことができる。

callbacks:
    init_app: $yourplugin::YourPlugin::Plugin::cb_init_app

lib/YourPlugin/Plugin.pm

package YourPlugin::Plugin;
use strict;
use warnings;

# Registering callbacks
sub cb_init_app {
    my ( $cb, $app ) = @_;

    # MT::Assetおよびそのサブクラスに仕込む
    my $object_typs = MT->registry( 'object_types' );
    foreach my $class_type ( keys %$object_typs ) {
        my $class = MT->model( $class_type ) or next;
        if ( $class->isa('MT::Asset') ) {
            $class->add_trigger(
                post_save => sub {
                    my $asset = shift;
                    my ( $orig ) = @_;
                    # 保存前のやつ
                },
                post_save => sub {
                    my $asset = shift;
                    my ( $orig ) = @_;
                    # 保存後のやつ
                }
            );
        }
    }
    1;
}

1;

Amazon SNS + SQS な構成をAWS Ruby SDK V2で自動化する

Amazon SQSへのキューイングの際、直接SQSを使っても良いですが、SNSを使うと抽象度が高くなり捗ることもあります。 CloudWatchを使ってログを残すこともできますし、便利です。

http://dev.classmethod.jp/cloud/aws/sns-topic-should-be-placed-behind-sqs-queue/ http://docs.aws.amazon.com/ja_jp/sns/latest/dg/SendMessageToSQS.html

そんなSNSトピックとSQSキュー、マネージドコンソールから作っても良いのですが、SDKを使って自動化することでデプロイを自動化したり、環境を作り直すときに便利です。

今回Ruby SDK V2での作り方を調べたのでそのメモです。

続きを読む

Rails Asset / Sprockets関係gem使ったものの感想

はじめに 個人的な感想

ガンガンassets pipeline使う

  • Railsエンジニアだけでやる分には楽

assets pipeline / Sprockets使わない

  • SPA開発などで、フロントエンジニアさんが別にいる場合、フロントエンジニアさんが使いやすいツールでやって貰った方が楽
  • 自然と分業できる

プロジェクトによってどちらかに寄せる

今回は「ガンガンassets pipeline使った」例で使ってるassets関係gemの紹介をします

rails-fluxchat-example https://github.com/takeyuweb/rails-fluxchat-example

bower-rails

  • Bower ばうわー JavaScriptパッケージマネージャ
  • rakeコマンドrake bower:installでパッケージのインストールや更新ができる
  • assets precompileの前に自動でパッケージのインストールなどできる
  • インストールしたパッケージのパスをassets precompileの入れてくれる

RAILS_ROOT/vendor/assets/bower_components/alt/dist/alt-with-addons.js

//= require alt/dist/alt-with-addons

さらに後述のbrowserify-railsと組み合わせると

var Alt = require('alt/dist/alt-with-addons');

react-rails

browserify-rails

Browserify

ブラウザ上のJavaScriptvar hoge = require('./hoge');ってできるやつ

browserify-rails

  • Rails(Sprockets)でvar hoge = require('./hoge');ってできるやつ
  • モジュール化
  • ファイルごとに依存関係を記述できて読みやすい、整理しやすい
// app/assets/javascripts/components/Messages.jsx
var React = require('react/react-with-addons');
var AltContainer = require('alt/AltContainer');
var MessageStore = require('../stores/MessageStore.es6');
var MessageActions = require('../actions/MessageActions.es6');

var MessageForm = React.createClass({
// (snip)
});

var Messages = React.createClass({
// (snip)
});

module.exports = Messages;

※ Sprockets Asset Pipelineで使われているプリプロセッサ

sprockets-es6

  • Rails(Sprockets)でES6を書けるようになるやつ
  • Babel((ほぼ)ES6をES5に変換するやつ)
  • ES6前提のライブラリが使える
  • RailsでES6書きたいマン必携?
    • アロー関数書きたい
    • プロトタイプじゃなくてクラス書きたい
    • Promise書きたい など

browserify-rails とのからみ

.es6を付けないとダメ

// hello.es6
class Hello {
    constructor(name) {
        this.name = name
    }
    world() {
        alert('Hello ' + this.name+ ' World!')
    }
}
var Hello = require('./hello'); // NG
var Hello = require('./hello.es6'); // OK

Action Cable

Pub/Sub + WebSocket

Ruby WebSocketサーバ 及び Pub/Sub サーバ実装 JavaScript WebSocketクライアントライブラリ

Sub

Assetsでクライアントをつくる

Subscribe

// app/assets/javascripts/channels/ChatChannel.es6
var ChatChannel = window.App.cable.subscriptions.create("ChatChannel", {
    connected() {
        // Called once the subscription has been successfully completed
        console.log('ChatChannel connected');
    },

ActionCable Server側のRubyコードが呼び出される

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stop_all_streams
    stream_from 'chat_activity'
    stream_from "chat_activity:#{connection.uuid}"
  end

Pub

Rails App Server / ActionCable Server から Publish すると・・・

# "chat_activity:#{uuid}"でstream_fromしたSubscriberにメッセージ送信
ActionCable.server.broadcast "chat_activity:#{uuid}", message.to_json

Receive

Subscriber側のブラウザの以下のコードが呼び出される

// app/assets/javascripts/channels/ChatChannel.es6
var ChatChannel = window.App.cable.subscriptions.create("ChatChannel", {

   // (snip)
   
    received(json) {
        var message = JSON.parse(json);
        console.log('ChatChannel message');
        console.log(message);
        MessageServerActionCreators.receiveMessage(message);
    }

僕はES6で書きましたが、サンプルはだいたいCoffeeScriptでつらいです。

個人的感想

ツールとして

  • Rails上での扱いやすさ向上のためのツール類は割と使う
  • JSライブラリをgemにラップするRails AssetsよりはJSはJSの世界で管理できるbowar-railsの方が好き
    • JSライブラリのバージョンアップを追いやすい
    • JS側で依存関係管理しているのだから無理にbundlerでやんなくても。
    • Railsで使うからbundlerでまとめたい、というのも逆の意見であるだろう。おそらく感覚的なもので人それぞれ。

使い分け

  • フロント側の負荷が大きなプロジェクトならフロント側に寄せてあげる
    • Sprocketsで嵌まるのは忍びない
      • digest値のために.css.erbさせるとか
      • Rails.application.config.assets.precompile=を変更して貰うとか
    • turbolinksで嵌まるのは忍びない
    • 外れるなら完全に外せ!中途半端はわけわかんなくなるぞ!
    • 今やってる奴フロントのSPAの実装詳細は関知してないです。API仕様だけ決めてる。
      • 好きに書け、サーバ側でうまいことやってやるから。
  • そうじゃないやつは、ガンガン使ってみると面白い
    • Sprocketsらくちん
      • ERB使えるし
      • 好きな言語で書けるし
      • ソースコード中の記述やRails.application.config.assets.precompileで柔軟に設定できるし
      • 自動でにminifyしてくれるし
      • digest値付けてくれるから何も考えなくてもキャッシュで困らない
    • turbolinksだって真面目に使えばRailsらしいWebアプリがサクサク動いて気持ちいいよ。

Reactとか

  • やっぱり中途半端はよくない
  • UIコンポーネントと割り切って使う
    • 複雑なUI部品を実装する手段にする
  • SPAに使う
    • RailsAPIサーバにする勢いで
    • コントローラごとにSPAにするようなの試したことあるが、どうもコンポーネントの再利用とか、二度手間が増えたりみたいなでイマイチ感。
    • バックエンドやりつつフロントでSPAとかやろうとすると、開発効率すごく下がる実感あり。3倍ぐらい。
    • SPAやるならフロントではRails忘れろ。