Created
April 10, 2012 19:22
Revisions
-
ahoward revised this gist
Apr 11, 2012 . 1 changed file with 21 additions and 20 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -19,8 +19,10 @@ class CommentConducer < Dao::Conducer def initialize(user, post, comment, params = {}) @user, @post, @comment = user, post, comment # conducers can be specialized based on the current request/action, which # is known to them but which can also be specificed using: # Conducer.call(action, ...) # case action when 'new', 'create' update_attributes(:content => 'default comment <blink> content </blink>') @@ -33,7 +35,7 @@ def initialize(user, post, comment, params = {}) end def url url_for('/comments/%d' % @comment.id) # you have access to all of rails' route helpers... end def to_s @@ -55,8 +57,9 @@ def save cc = CommentConducer.for('new', user, post, comment, params) # the state is determined by one model - the front model - the one being # # 'conduced' (default is the *last* model given to #initialize) , but all the # models are available... # p 'cc.attributes' => cc.attributes p 'cc.action' => cc.action @@ -132,27 +135,25 @@ def save __ # and we can display errors over a group of models, related or note, # trivially using Errors#to_s/to_html # puts puts cc.errors.to_html # errors.to_s == errors.to_html. you can override it. <<-__ <div class="dao errors summary"> <h3 class="caption">We're so sorry, but can you please fix the following errors?</h3> <dl class="list"> <dt class="title field">User Name</dt> <dd class="message field">is fucked</dd> <dt class="title field">Teh Api Call</dt> <dd class="message field">did not work</dd> </dl> </div> __ -
ahoward created this gist
Apr 10, 2012 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,259 @@ # -*- encoding : utf-8 -*- # # conducers combine the presenter pattern with the conductor pattern. they # can present and conduct an arbitrary number of models. they can also unify # models with api calls and other non-persisted data in way that supports # restufl interfaces, validations, form helpers, etc. consider conducers and # being 'conductive to' writing web applications: you can prepare arbirtray # data for the view and serialize arbirtray data out of it into a data store. # # to your controllers and views a conducer looks *just like a model* except # that they are not limited to showing flat data: they can show any arbitrary # graph of data - they do not suffer from the 'nested model' problem. # require 'rubygems' require 'dao' class CommentConducer < Dao::Conducer def initialize(user, post, comment, params = {}) @user, @post, @comment = user, post, comment conduces(@comment) case action when 'new', 'create' update_attributes(:content => 'default comment <blink> content </blink>') when 'edit', 'update', 'show' :et_cetera end update_attributes(params) end def url url_for('/comments/%d' % @comment.id) end def to_s sanitize attributes.content end def save errors.add :teh_api_call, 'did not work' return false end end # conducers can unify a few models for presentation... # user = User.new(:email => 'ara@dojo4.com') post = Post.new(:body => 'teh post...') comment = Comment.new(:text => 'teh comment', :id => 42) params = {:key => :val} cc = CommentConducer.for('new', user, post, comment, params) # the state is determined by one model - the front model - the one being # 'conduced', but all the models are available... # p 'cc.attributes' => cc.attributes p 'cc.action' => cc.action p 'cc.new_record?' => cc.new_record? p 'cc.persisted?' => cc.persisted? <<-__ {"cc.attributes"=>{"content"=>"default comment <blink> content </blink>", "key"=>:val} } {"cc.action"=>"new"} {"cc.new_record?"=>true} {"cc.persisted?"=>false} __ # being a presenter, of course we have access to helper-y methods... # p 'cc.url' => cc.url p 'cc.to_s' => cc.to_s <<-__ {"cc.url"=>"/comments/42"} {"cc.to_s"=>"default comment content "} __ # yes yes, we are a form too. notice how building a nest form is *exactly* as # hard as building a non-nested one... # p cc.form.input :user, :name p cc.form.input :comment, :text puts <<-__ "<input type=\"text\" name=\"dao[comment][user.name]\" class=\"dao\" id=\"comment_user-name\"/>" "<input type=\"text\" name=\"dao[comment][comment.text]\" class=\"dao\" id=\"comment_comment-text\"/>" __ # in the end, we want to validate and conduct data into the database... we can # easily have loop-y or nested validations... # cc.errors.add :user, :name, 'is fucked' <<-__ "<input type=\"text\" name=\"dao[comment][user.name]\" class=\"dao errors\" id=\"comment_user-name\" data-error=\"User Name: is fucked\"/>" __ # note that data can be invalid on the form independently from model # validations, and that form helpers know this... # p cc.form.input :user, :name puts # saving is a no-op in this example (we'd simply save models) but note that we # can unify model errors with other, tangential errors such as api errrors # seamlessly for the view... # cc.save p cc.errors <<-__ {"user"=>{"name"=>["is fucked"]}, "teh_api_call"=>["did not work"]} __ # and we can display errors over a group of models, related or note, # trivially... # puts puts cc.errors <<-__ <div class="dao errors summary"> <h3 class="caption">We're so sorry, but can you please fix the following errors?</h3> <dl class="list"> <dt class="title field">User Name</dt> <dd class="message field">is fucked</dd> <dt class="title field">Teh Api Call</dt> <dd class="message field">did not work</dd> </dl> </div> __ # MOCK-Y teh modelz... BEGIN { ## # class Model { :new_record => true, :persisted => false, :destroyed => false, }.each do |state, value| class_eval <<-__ attr_writer :#{ state } def #{ state } @#{ state } = #{ value } unless defined?(@#{ state }) @#{ state } end def #{ state }? #{ state } end __ end def initialize(attributes = {}) self.attributes.update(attributes) end def attributes @attributes ||= Map.new end def [](key) attributes[key] end def []=(key, val) attributes[key] = val end def update_attributes(hash = {}) hash.each{|k,v| attributes[k] = v } end def method_missing(method, *args, &block) re = /^([^=!?]+)([=!?])?$/imox matched, key, suffix = re.match(method.to_s).to_a case suffix when '=' then attributes.set(key, args.first) when '!' then attributes.set(key, args.size > 0 ? args.first : true) when '?' then attributes.has?(key) else attributes.has?(key) ? attributes.get(key) : super end end def inspect(*args, &block) "#{ self.class.name }( #{ attributes.inspect } )" end def errors @errors ||= Map.new end def valid? errors.empty? end def save return false unless valid? self.new_record = false self.persisted = true return true end def destroy true ensure self.new_record = false self.destroyed = true end end ## # class User < Model end class Post < Model end class Comment < Model end }