Skip to content

Commit

Permalink
Added <r:javascript> and <r:stylesheet tags to both pages and the tex…
Browse files Browse the repository at this point in the history
…t assets.

This includes some fancy tracking of dependencies to keep from mimicking the Radiant 5-minute-cache-per-page.
  • Loading branch information
chrisparrish committed Oct 18, 2008
1 parent 1b6819a commit 14befc8
Show file tree
Hide file tree
Showing 11 changed files with 353 additions and 8 deletions.
2 changes: 1 addition & 1 deletion app/controllers/text_asset_site_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def show_uncached_text_asset(filename, asset_class, url)
# set the last modified date based on updated_at time for the asset
# we can do this as long as there is no dynamic content in the assets
response.headers['Last-Modified'] = @text_asset.updated_at
response.body = @text_asset.content
response.body = @text_asset.render

# for text_assets, we cache no matter what (there's no status setting for them)
text_asset_cache.cache_response(url, response) if request.get?
Expand Down
19 changes: 18 additions & 1 deletion app/models/javascript.rb
Original file line number Diff line number Diff line change
@@ -1 +1,18 @@
class Javascript < TextAsset; end
class Javascript < TextAsset

class TagError < StandardError; end

tag('javascript') do |tag|
if name = tag.attr['name']
self.dependencies << tag.attr['name'].strip
if javascript = Javascript.find_by_filename(tag.attr['name'].strip)
javascript.render
else
raise TagError.new("javascript not found")
end
else
raise TagError.new("`javascript' tag must contain a `name' attribute.") unless tag.attr.has_key?('name')
end
end

end
19 changes: 18 additions & 1 deletion app/models/stylesheet.rb
Original file line number Diff line number Diff line change
@@ -1 +1,18 @@
class Stylesheet < TextAsset; end
class Stylesheet < TextAsset

class TagError < StandardError; end

tag('stylesheet') do |tag|
if name = tag.attr['name']
self.dependencies << tag.attr['name'].strip
if stylesheet = Stylesheet.find_by_filename(tag.attr['name'].strip)
stylesheet.render
else
raise TagError.new("stylesheet not found")
end
else
raise TagError.new("`stylesheet' tag must contain a `name' attribute.") unless tag.attr.has_key?('name')
end
end

end
64 changes: 63 additions & 1 deletion app/models/text_asset.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
class TextAsset < ActiveRecord::Base
set_inheritance_column :class_name

# Default Order
order_by 'filename'

# Associations
Expand All @@ -14,9 +13,72 @@ class TextAsset < ActiveRecord::Base
# the following regexp uses \A and \Z rather than ^ and $ to enforce no "\n" characters
validates_format_of :filename, :with => %r{\A[-_.A-Za-z0-9]*\Z}, :message => 'invalid format'

include Radiant::Taggable

serialize :dependencies, Array
before_save :update_dependencies

# for some reason setting dependencies to serialize as Array still lets this
# value be nil. AR should do this step for me. Oh well.
def after_initialize
self.dependencies = [] if dependencies.nil?
end

# after_save :effectively_updated_at

def url
StylesNScripts::Config["#{self.class.to_s.underscore}_directory"] +
"/" + self.filename
end


def update_dependencies
self.dependencies = []
parse(content, false)
self.dependencies.uniq!
end


def effectively_updated_at
if self.dependencies.empty?
self.updated_at
else
dependency_assets = self.class.find_all_by_filename(self.dependencies).compact
dependencies_last_updated_at = dependency_assets.sort_by{|i| i[:updated_at]}.last.updated_at
puts dependencies_last_updated_at.to_s
end
end


# def update_dependents_timestamps()
# self.class.find(:all).each do |text_asset|
# unless text_asset.dependencies.nil?
# if text_asset.dependencies.include?(filename)
# puts "updating #{text_asset.filename} to dependencies_updated_at: #{updated_at}"
# text_asset.dependencies_updated_at = updated_at
# TextAsset.record_timestamps = false
# text_asset.save
# TextAsset.record_timestamps = true
# end
# end
# end
# end

def render
text = self.content
text = parse(text)
end


private

def parse(text, show_errors = true)
unless @parser and @context
@context = TextAssetContext.new(self)
@parser = Radius::Parser.new(@context, :tag_prefix => 'r')
end
@context.show_errors = show_errors
@parser.parse(text)
end

end
40 changes: 40 additions & 0 deletions app/models/text_asset_context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
class TextAssetContext < Radius::Context

def initialize(text_asset)
@show_errors = true
super()
globals.text_asset = text_asset
text_asset.tags.each do |name|
define_tag(name) { |tag_binding| text_asset.render_tag(name, tag_binding) }
end
end


def show_errors=(value)
@show_errors = value
end


def render_tag(name, attributes = {}, &block)
super
rescue Exception => e
raise e if raise_errors?
@tag_binding_stack.pop unless @tag_binding_stack.last == binding
e.message
end


def tag_missing(name, attributes = {}, &block)
super
rescue Radius::UndefinedTagError => e
raise StandardTags::TagError.new(e.message)
end


private

def raise_errors?
RAILS_ENV != 'production' && @show_errors
end

end
74 changes: 74 additions & 0 deletions app/models/text_asset_tags.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
module TextAssetTags

include Radiant::Taggable
class TagError < StandardError; end

# [ { :name => 'stylesheet',
# :asset_class => Stylesheet,
# :inline_tag => 'style',
# :sample_file => 'my_file.css' },
#
# { :name => 'javascript',
# :asset_class => Javascript,
# :inline_tag => 'script',
# :sample_file => 'my_file.js' }
# ].each do |current_tag|
# desc %{
# Renders the content from or url for a #{current_tag[:name]} asset. The
# @name@ attribute is required to identify the desired #{current_tag[:name]}.
# file. Additionally, the @as@ attribute can be used to choose whether to
# render the tag as one of the following:
#
# * @content@ - the content of the #{current_tag[:name]} is rendered (this
# is the default).
#
# * @url@ - the url of the #{current_tag[:name]} file (relative to the
# web root).
#
# * @inline@ - same as @content@ but wraps the content in an (X)HTML
# &lt;#{current_tag[:inline_tag]}> element. By default, the @type@
# attribute of the &lt;#{current_tag[:inline_tag]}> element will
# automatically match the default #{current_tag[:name]} content-type
# (but you can override this -- see Additional Options below).
#
# *Additional Options:*
# When rendering &lt;r:#{current_tag[:name]} @as@="inline" />, any
# additional attributes you provide will be passed on directly to the
# &lt;#{current_tag[:inline_tag]}> element, like:
# <pre><code><r:#{current_tag[:name]} name="#{current_tag[:sample_file]}" as="inline" id="my_id" type="text/custom" />
# produces:
# <#{current_tag[:inline_tag]} type="text/custom" id="my_id">
# <!--
# Your #{current_tag[:name]}'s content here...
# -->
# </#{current_tag[:inline_tag]}></code></pre>
#
# *Usage:*
# <pre><code><r:#{current_tag[:name]} name="#{current_tag[:sample_file]}" [as="content | inline | url"] /></code></pre>
# }
# tag(current_tag[:name]) do |tag|
# if name = tag.attr['name']
# if text_asset = current_tag[:asset_class].find_by_filename(tag.attr['name'].strip)
# case tag.attr['as']
# when 'content', nil
# text_asset.render
# when 'url'
# text_asset.url
# when 'inline'
# mime_type = tag.attr['type'] || StylesNScripts::Config["#{current_tag[:name]}_mime_type"]
# optional_attribs = tag.attr.except('name', 'as', 'type').inject('') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip
# optional_attribs = " #{optional_attribs}" unless optional_attribs.empty?
# %{<#{current_tag[:inline_tag]} type="#{mime_type}"#{optional_attribs}>\n<!--\n#{text_asset.render}\n-->\n</#{current_tag[:inline_tag]}>}
# end
# else
# raise TagError.new("#{current_tag[:name]} not found")
# end
# else
# raise TagError.new("`#{current_tag[:name]}' tag must contain a `name' attribute.") unless tag.attr.has_key?('name')
# end
# end
#
# end
#

end
4 changes: 2 additions & 2 deletions app/views/admin/text_asset/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<h1>Edit <%= proper_model_name %></h1>
<% end -%>
<form method="post" action="">
<% form_tag do %>
<%= hidden_field(model_name, "lock_version") %>
<div class="form-area">
<p class="title">
Expand All @@ -25,6 +25,6 @@
<p class="buttons">
<%= submit_tag(@text_asset.new_record? ? "Create #{proper_model_name}" : 'Save Changes', :class => 'button') %> <%= save_model_and_continue_editing_button(@text_asset) %> or <%= link_to("Cancel", send("#{model_name}_index_url") ) %>
</p>
</form>
<% end %>
<%= focus("#{model_name}_filename") %>
16 changes: 16 additions & 0 deletions db/migrate/002_add_dependencies_to_text_assets.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class AddDependenciesToTextAssets < ActiveRecord::Migration
def self.up
add_column :text_assets, :dependencies, :string
# iterate through Javascripts and Stylesheets and save! each to trigger
# update_dependencies and save the results to the new dependencies field
TextAsset.find(:all).each do |text_asset|
text_asset.class.record_timestamps = false
text_asset.save!
text_asset.class.record_timestamps = true
end
end

def self.down
remove_column :text_assets, :dependencies
end
end
71 changes: 71 additions & 0 deletions lib/extended_page_tags.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
module ExtendedPageTags
include Radiant::Taggable
class TagError < StandardError; end

[ { :name => 'stylesheet',
:asset_class => Stylesheet,
:inline_tag => 'style',
:sample_file => 'my_file.css' },

{ :name => 'javascript',
:asset_class => Javascript,
:inline_tag => 'script',
:sample_file => 'my_file.js' }
].each do |current_tag|
desc %{
Renders the content from or url for a #{current_tag[:name]} asset. The
@name@ attribute is required to identify the desired #{current_tag[:name]}.
file. Additionally, the @as@ attribute can be used to choose whether to
render the tag as one of the following:
* @content@ - the content of the #{current_tag[:name]} is rendered (this
is the default).
* @url@ - the url of the #{current_tag[:name]} file (relative to the
web root).
* @inline@ - same as @content@ but wraps the content in an (X)HTML
&lt;#{current_tag[:inline_tag]}> element. By default, the @type@
attribute of the &lt;#{current_tag[:inline_tag]}> element will
automatically match the default #{current_tag[:name]} content-type
(but you can override this -- see Additional Options below).
*Additional Options:*
When rendering &lt;r:#{current_tag[:name]} @as@="inline" />, any
additional attributes you provide will be passed on directly to the
&lt;#{current_tag[:inline_tag]}> element, like:
<pre><code><r:#{current_tag[:name]} name="#{current_tag[:sample_file]}" as="inline" id="my_id" type="text/custom" />
produces:
<#{current_tag[:inline_tag]} type="text/custom" id="my_id">
<!--
Your #{current_tag[:name]}'s content here...
-->
</#{current_tag[:inline_tag]}></code></pre>
*Usage:*
<pre><code><r:#{current_tag[:name]} name="#{current_tag[:sample_file]}" [as="content | inline | url"] /></code></pre>
}
tag(current_tag[:name]) do |tag|
if name = tag.attr['name']
if text_asset = current_tag[:asset_class].find_by_filename(tag.attr['name'].strip)
case tag.attr['as']
when 'content', nil
text_asset.render
when 'url'
text_asset.url
when 'inline'
mime_type = tag.attr['type'] || StylesNScripts::Config["#{current_tag[:name]}_mime_type"]
optional_attribs = tag.attr.except('name', 'as', 'type').inject('') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip
optional_attribs = " #{optional_attribs}" unless optional_attribs.empty?
%{<#{current_tag[:inline_tag]} type="#{mime_type}"#{optional_attribs}>\n<!--\n#{text_asset.render}\n-->\n</#{current_tag[:inline_tag]}>}
end
else
raise TagError.new("#{current_tag[:name]} not found")
end
else
raise TagError.new("`#{current_tag[:name]}' tag must contain a `name' attribute.") unless tag.attr.has_key?('name')
end
end

end
end
37 changes: 37 additions & 0 deletions spec/models/user_action_observer_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require File.dirname(__FILE__) + '/../spec_helper'

describe UserActionObserver do
scenario :users, :stylesheets, :javascripts

before(:each) do
@user = users(:existing)
UserActionObserver.current_user = @user
end


it 'should observe stylesheet creation' do
Stylesheet.create!(stylesheet_params).created_by.should == @user
end


it 'should observe javascript creation' do
Javascript.create!(javascript_params).created_by.should == @user
end


it 'should observe stylesheet update' do
model = Stylesheet.find_by_filename('main.css')
model.attributes = model.attributes.dup
model.save.should == true
model.updated_by.should == @user
end


it 'should observe javascript update' do
model = Javascript.find_by_filename('main.js')
model.attributes = model.attributes.dup
model.save.should == true
model.updated_by.should == @user
end

end
Loading

0 comments on commit 14befc8

Please sign in to comment.