需求是这样的。 我需要储存一些 metadata,所以搞了个序列化的 information 作为储存。然后呢,为了用着方便,通过 method_missing 完成了幽灵方法。这样我就可以通过类似 s.info_location 来访问 s.infomation['location'],同时也方便在 form_for 中使用。 代码如下,其余代码均由 scaffold 生成:
class Series < ActiveRecord::Base
validates :name, :uniqueness => true
serialize :information, JSON
def method_missing(name,* args)
super unless respond_to? name
attribute = name.to_s
attribute = attribute[5..attribute.length]
if attribute[-1]=='='
self.information[attribute.chop]=args[0]
else
self.information[attribute]
end
end
def respond_to?(method)
method_str= method.to_s
method_str.include? 'info_' || super
end
def to_s
name
end
end
结果出现的情况是,渲染/series/2/edit 页面时,却错误的渲染成了 new 的页面,如下图所示:
将 Series.rb 修改为下面代码后,正常渲染。(既,删掉对 respond_to? 的复写)
class Series < ActiveRecord::Base
has_many :episodes
has_many :hobbies
validates :name, :uniqueness => true
serialize :information, JSON
def method_missing(name,* args)
attribute = name.to_s
super unless attribute.include? 'info_'
attribute = attribute[5..attribute.length]
if attribute[-1]=='='
self.information[attribute.chop]=args[0]
else
self.information[attribute]
end
end
# def respond_to?(method)
# method_str= method.to_s
# method_str.include? 'info_' || super
# end
def to_s
name
end
end
最后完成效果是: views/series/_form.html.erb
<%= form_for(@series) do |f| %>
<% if @series.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@series.errors.count, "error") %> prohibited this series from being saved:</h2>
<ul>
<% @series.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_field :description %>
</div>
<div class="field">
<%= f.label :info_location %><br>
<%= f.text_field :info_location %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
controllers/series_controller.rb
class SeriesController < ApplicationController
before_action :set_series, only: [:show, :edit, :update, :destroy]
def index
@series = Series.all
end
def show
end
def new
@series = Series.new
end
def edit
end
def create
@series = Series.new(series_params)
respond_to do |format|
if @series.save
format.html { redirect_to @series, notice: 'Series was successfully created.' }
format.json { render :show, status: :created, location: @series }
else
format.html { render :new }
format.json { render json: @series.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if @series.update(series_params)
format.html { redirect_to @series, notice: 'Series was successfully updated.' }
format.json { render :show, status: :ok, location: @series }
else
format.html { render :edit }
format.json { render json: @series.errors, status: :unprocessable_entity }
end
end
end
def destroy
@series.destroy
respond_to do |format|
format.html { redirect_to series_index_url, notice: 'Series was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_series
@series = Series.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
######################################
# add :info_xxx to access information['xxx'] #
######################################
def series_params
params.require(:series).permit(:name, :description, :info_location)
end
end
既,在 form_for 中可以直接像普通属性一样通过 info_xxx 来访问 information 这个 hash 中的变量。