:conditions の遅延評価

投稿者 nanki 2007-07-05 15:38:00 GMT

泥臭い&あんまり使わないので忘れがちだが、Associationを定義する時に、シングルクォートを使うと、参照時のインスタンスで評価し直してくれる。

  class Road
    belongs_to :node, :foreign_key => [:meshcode, :nodeno], :conditions => 'nodes.linkno = #{linkno}'
  end

  Road.find(:first).node # => "SELECT * FROM nodes WHERE nodes.linkno = 1"
  class Road
    belongs_to :node, :foreign_key => [:meshcode, :nodeno], :conditions => 'nodes.linkno = #{table_name}.linkno'
  end

  Road.find(:first, :include => :node) # => "SELECT * FROM roads LEFT OUTER JOIN nodes ON ... WHERE nodes.linkno = roads.linkno"

ここでtable_nameが(やってみたら)使えるのは、

# lib/active_record/associations.rb
  class JoinBase
    attr_reader :active_record, :table_joins
    delegate    :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record

このdelegateのおかげか?

次のように書けば、どちらでもいける。

  belongs_to :node, :foreign_key => [:meshcode, :nodeno], :conditions => 'nodes.linkno = #{linkno rescue "#{table_name}.linkno"}'

いいのか、こんなんで。


今日のGreasemonkey - Table Filter

投稿者 nanki 2007-03-12 08:38:00 GMT

screenshot

はじめてのGreasemonkeyでもある。

10行以上のtableや、リストの上にインクリメンタルな検索ボックスが追加される。 使ってみると、いたるところに現れる。

vim で言うところの、SmartCaseも使える。

tablefilter.user.js


JavaScript URLMapper

投稿者 nanki 2007-02-13 15:03:00 GMT

ちょっと複雑なパス体系を持つRailsアプリでJSONでAJAXする人には役に立つかもしれない。 GoogleMapsとかね。

要 prototype.js

使い方はこんな風。

URLMapper.connect(
  "/user/:user_id/category/:category_id/entry/:action/:entry_id",
  {controller: 'entry', action: 'index', entry_id: null});
URLMapper.connect(
  "/user/:user_id/category/:action/:category_id",
  {controller: 'category', action: 'index', category_id: null});
URLMapper.connect(
  "/user/:action/:user_id",
  {controller: 'user', action: 'index', user_id: null});
URLMapper.connect(
  "/:controller/:action/:id",
  {action: 'index', id: null});

URLMapper.url_for({controller: 'entry'});
// -> "/entry/index/"
URLMapper.url_for({controller: 'entry', action: 'new'});
// -> "/entry/new/"
URLMapper.url_for({controller: 'category', action: 'new'});
// -> "/category/new/"
URLMapper.url_for({controller: 'category', action: 'new', user_id: 3});
// -> "/user/3/category/new/"
URLMapper.url_for({controller: 'category', action: 'update', user_id: 3, category_id: 2});
// -> "/user/3/category/update/2"
URLMapper.url_for({controller: 'entry', action: 'update', user_id: 3, category_id: 2, entry_id: 1});
// -> "/user/3/category/2/entry/update/1"

url_for は可変長引数をとって、引数をHashとしてマージするので、

//サーバからJSONを取得。
//[{user_id: 3, category_id: 2, entry_id: 1}, ...]
var entries = getJSON();

URLMapper.url_for({controller: 'entry', action: 'update'}, entries[0]);
// -> "/user/3/category/2/entry/update/1"

とか。

ソースはこんなの。

Hash.prototype.subtract = function(op2) {
  var result = $H().merge(this);
  this.remove.apply(result, $H(op2).keys());
  return result;
};


var URLMapper = {
  url_options: $A(),

  UrlOption: function(url, required, defaults, match) {
    this.url      = url.gsub(/\/:([A-z_][A-z0-9_]*)/, function(match) {return '/#{' + match[1] + '}'});
    this.required = required;
    this.defaults = defaults;
    this.match    = match;
  },

  extractParameters: function(url) {
    var params = $H();
    url.scan(/\/:([A-z_][A-z0-9_]*)/, function(match) {params[match[1]] = true});
    return params;
  },

  connect: function(url, defaults) {
    url = url.gsub(/%3A/, ':');
    var params = this.extractParameters(url);
    var required = params.subtract(defaults);
    this.url_options.push(new this.UrlOption(url, required, $H(defaults), $H(defaults).subtract(params)));
  },

  url_for: function() {
    var options = $A(arguments).inject($H(), function(r, v){return r.merge(v)});
    var detected = this.url_options.select(
      function(url_option) {
        return url_option.required.subtract(options).size() == 0 && url_option.match.all(function(pair){return options[pair.key] == pair.value});
      }
    ).sortBy(function(url_option) {
      return $H(options).subtract(url_option.required).subtract(url_option.defaults).size();
    }).first();


    if (!detected) {
      throw "no URL matches.";
    }

    return (new Template(detected.url)).evaluate($H().merge(detected.defaults).merge(options));
  }
}

Operaのバグ?

投稿者 nanki 2007-01-17 14:39:00 GMT

最近、Javascript付いている。

Ajaxのレスポンス中に<script>タグを埋め込んでいたら、Operaで動いてない。
prototype 1.4.0のevalScriptsがOperaでは動かないらしい。

  evalScripts: function() {
    return this.extractScripts().map(eval);
  },

  evalScripts: function() {
    return this.extractScripts().map(function(a){return eval(a)});
  },

と変更することで直るのだとか。

1.5.0を使おう。

参考

Bug in Opera? Problems calling Prototype’s evalScripts()


W3C Range

投稿者 nanki 2007-01-15 21:35:00 GMT

W3C Range in Internet Explorer
選択したテキストをDOM上で操作するAPIが例によってIEでは変なので、これを使うと解決してくれそう。

参考

DOM Range


ActiveRecord - フォーマット

投稿者 nanki 2006-12-27 21:09:00 GMT

小分けにしないとサーバが止まる・・・

model_formatter
  • 特定のカラムを、フォーマット付きでアクセス。
  • formatted_weight で、フォーマット済みのweightにアクセス。
  • フォーマット済み属性の名前は、:formatted_attr, :prefixで変更。
  • class Widget < ActiveRecord::Base
    # FormatInteger という名前のFormatterでフォーマットを指定
    # :boolean, :currency, :decimal, :integer, :percent, :phone が定義済み
    # :as には、Formatterのインスタンスも指定できる
    format_column :weight, :as => :integer

# Procを使って format_column :weight,

<span class="symbol">:formatted_attr</span> =&gt; <span class="symbol">:formatted_weight</span>,
<span class="symbol">:options</span> =&gt; {<span class="symbol">:delimiter</span> =&gt; <span class="string"><span class="delimiter">'</span><span class="content">,</span><span class="delimiter">'</span></span>},
<span class="symbol">:from</span> =&gt; <span class="constant">Proc</span>.new{|attribute, options| ... },
<span class="symbol">:to</span>   =&gt; <span class="constant">Proc</span>.new{|str, options| ... }

# ブロックによる定義も format_column :weight do

<span class="keyword">def</span> <span class="function">from</span>(attribute, options)
  <span class="comment"># ...</span>
<span class="keyword">end</span>

<span class="keyword">def</span> <span class="function">to</span>(str, options)
  <span class="comment"># ...</span>
<span class="keyword">end</span>

end end

w = Widget.new(:weight => 12345) w.formattedweight # => 12,345 # 書き込みもできる! w.formattedweight = 54,321 w.weight # => 54321

auto_convert_fields
  • attribute=を定義。
  • class MyModel < ActiveRecord::Base
    # name=, :text= の前に、TextHelper#striptags をはさむ。
    autoconvert :name, :text, :with => :strip_tags

# 同じ auto_convert :name, :text do |value|

strip_tags(value)

end end


:yの悲劇

投稿者 nanki 2006-12-21 18:51:00 GMT

GPS情報を格納するのに、

class Entry < ActiveRecord::Base
  has_one :location
end
  
class Location < ActiveRecord::Base
  # 実際にはGeometry型で格納するので、アクセサを定義
  def x
    point.x
  end
  def y
    point.y
  end
end

こんなモデルを作ると・・・

$ script/console
>> entry = Entry.find(:first)
>> entry.location.x
=> 135.0
>> entry.location.y
=> 35.0
>> entry.location.send :x
=> 135.0
>> entry.location.send :y
ArgumentError: wrong number of arguments (0 for 1)
        from (irb):35:in `y'
        from (irb):35:in `send'
        from (irb):35

xは大丈夫なのにyはだめ?

yの正体は、pのようにyamlを吐くprivate method、Kernel#y in yaml.rb。 何故だ?

entry.location の中身は、実はAssociationProxyで、ActiveRecordのソースを見るとこんな風になっている。

# lib/active_record/associations/association_proxy.rb
module ActiveRecord
  module Associations
    class AssociationProxy #:nodoc:
      attr_reader :reflection
      alias_method :proxy_respond_to?, :respond_to?
      alias_method :proxy_extend, :extend
      instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?|^proxy_extend|^send)/ }

entry.location.send :x すると、AssocationProxy#xが無いのでtargetにsendされるが、entry.location.send :y だと、Kernel#yが見つかってしまうのか・・・

当然entry.location.target.send :y だとうまくいく。

Ruby1.9 だとsendの仕様が違ってうまくいきそう。


Rails Plugin - View系2

投稿者 nanki 2006-10-05 06:55:00 GMT
MenuEngine
  • DHTMLのメニューを作る。
  • # in config/environment.rb
    module ApplicationHelper
    include MenuHelper
    end

# in app/helpers/applicationhelper.rb module ApplicationHelper include MenuHelper end

*
module MenuEngine
  # UserEngineと連携
  # trueで許可されないcontroller/actionへのメニューが表示されなくなる
  config :accesscontrol, false
  # エフェクトの設定。element はメニューを指す。
  config :onshow, "new Effect.Appear(element);"
  config :onhide, "new Effect.Fade(element);"
end
*
module ApplicationHelper
  def my_menu

item0 = <span class="constant">MenuItem</span>.new(<span class="symbol">:text</span> =&gt;  <span class="string"><span class="delimiter">&quot;</span><span class="content">Users</span><span class="delimiter">&quot;</span></span>, <span class="symbol">:controller</span> =&gt; <span class="string"><span class="delimiter">&quot;</span><span class="content">user</span><span class="delimiter">&quot;</span></span>)
item1 = <span class="constant">MenuItem</span>.new(<span class="symbol">:text</span> =&gt;   <span class="string"><span class="delimiter">&quot;</span><span class="content">List</span><span class="delimiter">&quot;</span></span>, <span class="symbol">:controller</span> =&gt; <span class="string"><span class="delimiter">&quot;</span><span class="content">user</span><span class="delimiter">&quot;</span></span>, <span class="symbol">:action</span>=&gt;<span class="string"><span class="delimiter">&quot;</span><span class="content">list</span><span class="delimiter">&quot;</span></span>)
item2 = <span class="constant">MenuItem</span>.new(<span class="symbol">:text</span> =&gt; <span class="string"><span class="delimiter">&quot;</span><span class="content">Images</span><span class="delimiter">&quot;</span></span>, <span class="symbol">:controller</span> =&gt; <span class="string"><span class="delimiter">&quot;</span><span class="content">image</span><span class="delimiter">&quot;</span></span>)

item0.items &lt;&lt; item1
[item0, item2]

end end

*
<%= enginestylesheet "menuengine" %>
<%= enginejavascript "menuengine" %>
...
<%= menu :menu => my_menu, :layout => :vertical %>
* YAMLで設定する方法もある。


Rails Plugin - ActiveRecord拡張系2

投稿者 nanki 2006-08-29 16:24:00 GMT
acts_as_modified

# restore person.restore_attributes :only => :age person.age # 100

# clear person.clearoriginalattributes :only => :age

# dbと比較 person.modified?(:reload) # true

acts_as_bookmarkable

post.add_bookmark(Bookmark.new)

だけ。 * 構成はacts_as_commentableとほぼ同じ。

acts_as_commentable
acts_as_sluggable

urlfor :controller => "articles", :action => 'show', :id => @article # => "/articles/show/76-omg-my-cat-is-so-cute-lol" * toparam というメソッドを上書きしてるらしい。

acts_as_sequenced

class Customer < ActiveRecord::Base actsassequenced

<span class="comment"># 連番</span>
<span class="symbol">:column</span> =&gt; <span class="symbol">:customer_number</span>,
<span class="comment"># 有効範囲</span>
<span class="symbol">:scope</span> =&gt; <span class="symbol">:account</span>

end

class Job < ActiveRecord::Base actsassequenced :column => :job_number, :scope => :account endこれで、Account毎に連番が。 * プラグインの形では提供されてないので、コードをlib/以下にコピペして、environment.rbでrequireしてね。 * コードからは、削除して追加すると、過去の管理番号と重複する番号が割り当てられるように見える。(これはうれしくない) * acts_as_paranoid(削除フラグで削除したかの用に見せるプラグイン)と併用できるようになっていて、併用すればこの問題は起こらなさそう。

acts_as_most_popular

# すると、頻度の高い順に3個とってこられる。 Person.mostpopularnames # => ["Jajamaru", "Piccolo", "Porori"] * nameが複数形になるってどういうことよ。

acts_as_network

alice, bob = User.find([1,2])

# bobはaliceの友達です alice.connections << bob

# bobはaliceの2です alice.connections.pushwithattributes(bob, :role => 2) alice.connections.find(bob.id).role # => 2

# bobはaliceの友達ですか? alice.connections.include?(bob) # => true

# aliceは誰の友達ですか? alice.connections.all_in

acts_as_rateable

def self.down

drop_table <span class="symbol">:ratings</span>

end end *

class YourModel
  actsasrateable
end

# 5点をつける m.rate(5) m.rating = 5

# 5点 m.rating # => 5

# 最初に見つかった3点のもの YourModel.findbyrating(3) # 3点以上のもの全部 YourModel.findallbyrating(3..-1) # 1点または3-5点のもの全部 YourModel.findallbyrating([1,3..5])

acts_as_ratable

# currentuserが5点をつけました m.rateas(5, :by => current_user)

# currentuserによって付けられた点数 m.ratingby(current_user)

m.average_rating # => 平均点 m.raters # => 採点者 * 同じユーザが同じものに採点すると上書きされる。

acts_as_popular
acts_as_searchable
acts_as_rast_indexed
acts_as_ordered
acts_as_taggable
acts_as_versioned
acts_as_paranoid
acts_as_bitfield
acts_as_deque
acts_as_attachment
better_nested_set
acts_as_threaded
acts_as_ldapable
acts_as_habtm_list
acts_as_shellable
acts_as_enumerated

バーコード大作戦(予告編)

投稿者 nanki 2006-07-10 16:13:00 GMT

がーん。

バーコードペディア:Webカメラでスキャンできる製品情報コミュニティ - Engadget Japanese

以前からbarcoder.nuとしてやっていたところがコンセプトをちょっと変えてみた?よう。

半年くらい天日干しにしている社内プロジェクトがあって、それがちょうどこんなコンセプトなんです。

demo QuickTime:2.4MB

というわけで頑張って今月中にでもリリースします。と書いて、自分のお尻に火をつける。

点スイッチ有限会社の最初のサービスとしてリリースできるといいな。

リンク先がないのにリンクを張るのは、早く会社のサイトを作らなきゃ、というプレッシャーです。

あ、もちR on Railsです。