perspectives 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +13 -0
- data/lib/generators/perspectives/install.rb +38 -0
- data/lib/generators/perspectives/scaffold/scaffold_generator.rb +54 -0
- data/lib/generators/perspectives/scaffold/templates/edit.mustache +6 -0
- data/lib/generators/perspectives/scaffold/templates/edit.rb +8 -0
- data/lib/generators/perspectives/scaffold/templates/form.mustache +25 -0
- data/lib/generators/perspectives/scaffold/templates/form.rb +27 -0
- data/lib/generators/perspectives/scaffold/templates/index.mustache +22 -0
- data/lib/generators/perspectives/scaffold/templates/index.rb +9 -0
- data/lib/generators/perspectives/scaffold/templates/new.mustache +5 -0
- data/lib/generators/perspectives/scaffold/templates/new.rb +7 -0
- data/lib/generators/perspectives/scaffold/templates/show.mustache +10 -0
- data/lib/generators/perspectives/scaffold/templates/show.rb +8 -0
- data/lib/generators/perspectives/scaffold/templates/tiny.mustache +8 -0
- data/lib/generators/perspectives/scaffold/templates/tiny.rb +8 -0
- data/lib/generators/perspectives/templates/application.js +13 -0
- data/lib/generators/perspectives/templates/rails/scaffold_controller/controller.rb +82 -0
- data/lib/perspectives/active_record.rb +17 -0
- data/lib/perspectives/base.rb +30 -0
- data/lib/perspectives/caching.rb +82 -0
- data/lib/perspectives/collection.rb +21 -0
- data/lib/perspectives/configuration.rb +14 -0
- data/lib/perspectives/context.rb +18 -0
- data/lib/perspectives/controller_additions.rb +142 -0
- data/lib/perspectives/forms/base.rb +5 -0
- data/lib/perspectives/forms/text_field.rb +15 -0
- data/lib/perspectives/forms.rb +7 -0
- data/lib/perspectives/memoization.rb +30 -0
- data/lib/perspectives/mustache_compiler.rb +33 -0
- data/lib/perspectives/params.rb +48 -0
- data/lib/perspectives/properties.rb +130 -0
- data/lib/perspectives/railtie.rb +42 -0
- data/lib/perspectives/rendering.rb +15 -0
- data/lib/perspectives/responder.rb +15 -0
- data/lib/perspectives/templating.rb +39 -0
- data/lib/perspectives/version.rb +3 -0
- data/lib/perspectives.rb +48 -0
- data/lib/rails/projections.json +12 -0
- data/lib/tasks/perspectives_tasks.rake +4 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +23 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +29 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +56 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/log/test.log +0 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/lib/perspectives/properties_spec.rb +31 -0
- data/spec/lib/perspectives/templating_spec.rb +18 -0
- data/spec/lib/perspectives_spec.rb +5 -0
- data/spec/mustaches/users/simple_info.mustache +1 -0
- data/spec/rails_helper.rb +11 -0
- data/spec/spec_helper.rb +16 -0
- data/vendor/assets/javascripts/mustache-0.8.1.js +570 -0
- data/vendor/assets/javascripts/perspectives/forms/text_field.mustache +3 -0
- data/vendor/assets/javascripts/perspectives.js +152 -0
- data/vendor/assets/javascripts/perspectives_views.js +1 -0
- metadata +233 -0
@@ -0,0 +1,142 @@
|
|
1
|
+
module Perspectives
|
2
|
+
module ControllerAdditions
|
3
|
+
def self.included(base)
|
4
|
+
base.before_filter :set_perspectives_version
|
5
|
+
base.helper_method :assets_meta_tag
|
6
|
+
base.class_attribute :perspectives_enabled_actions
|
7
|
+
delegate :perspectives_enabled_actions, to: 'self.class'
|
8
|
+
base.helper_method :perspective
|
9
|
+
|
10
|
+
base.class_attribute :perspectives_wrapping
|
11
|
+
base.perspectives_wrapping = []
|
12
|
+
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
|
15
|
+
delegate 'resolve_perspective_class_name', to: 'self.class'
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
unless defined?(ActionController::Responder)
|
21
|
+
def respond_to(*mimes, &block)
|
22
|
+
return super if block_given? || mimes.many? || !mimes.first.is_a?(Perspectives::Base)
|
23
|
+
|
24
|
+
perspectives_object = mimes.first
|
25
|
+
perspectives_object = wrap_perspective(perspectives_object) if wrap_perspective?
|
26
|
+
|
27
|
+
super() do |format|
|
28
|
+
format.html { render text: perspectives_object.to_html, layout: :default }
|
29
|
+
format.json { render json: perspectives_object }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def perspective(name, params_or_options = {})
|
35
|
+
if params_or_options.key?(:context) || params_or_options.key?(:params)
|
36
|
+
params = params_or_options.fetch(:params, {})
|
37
|
+
context = params_or_options.fetch(:context, default_context)
|
38
|
+
else
|
39
|
+
context = default_context
|
40
|
+
params = params_or_options
|
41
|
+
end
|
42
|
+
|
43
|
+
resolve_perspective_class_name(name).new(context, params)
|
44
|
+
end
|
45
|
+
|
46
|
+
def respond_with(*resources, &block)
|
47
|
+
return super unless wrap_perspective? && resources.first.is_a?(Perspectives::Base)
|
48
|
+
|
49
|
+
wrapped = wrap_perspective(resources.shift)
|
50
|
+
|
51
|
+
super(*resources.unshift(wrapped), &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def default_context
|
55
|
+
{}
|
56
|
+
end
|
57
|
+
|
58
|
+
def assets_version
|
59
|
+
Rails.application.assets.index.each_file.to_a.map { |f| File.new(f).mtime }.max.to_i
|
60
|
+
end
|
61
|
+
|
62
|
+
def assets_meta_tag
|
63
|
+
view_context.content_tag(:meta, nil, :'http-equiv' => 'x-perspectives-version', content: assets_version)
|
64
|
+
end
|
65
|
+
|
66
|
+
def set_perspectives_version
|
67
|
+
response.headers['X-Perspectives-Version'] = assets_version.to_s
|
68
|
+
end
|
69
|
+
|
70
|
+
def perspectives_enabled_action?
|
71
|
+
action_enabled_by?(perspectives_enabled_actions)
|
72
|
+
end
|
73
|
+
|
74
|
+
def perspectives_wrapper
|
75
|
+
return unless perspectives_enabled_action? && (request.headers['X-Perspectives-Full-Page'].to_s == 'true' || !request.xhr?)
|
76
|
+
|
77
|
+
perspectives_wrapping.find do |_, options|
|
78
|
+
next unless action_enabled_by?(options)
|
79
|
+
|
80
|
+
if options[:unless].present?
|
81
|
+
!options[:unless].call(self)
|
82
|
+
elsif options[:if].present?
|
83
|
+
options[:if].call(self)
|
84
|
+
else
|
85
|
+
true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
alias_method :wrap_perspective?, :perspectives_wrapper
|
90
|
+
|
91
|
+
def wrap_perspective(unwrapped_perspective)
|
92
|
+
perspective_klass, options = *perspectives_wrapper
|
93
|
+
perspective_klass.new(unwrapped_perspective.context, options[:args].call(self, unwrapped_perspective))
|
94
|
+
end
|
95
|
+
|
96
|
+
def action_enabled_by?(options)
|
97
|
+
return false if options.nil?
|
98
|
+
|
99
|
+
action = action_name.to_s
|
100
|
+
|
101
|
+
if options[:except]
|
102
|
+
!options[:except].include?(action)
|
103
|
+
elsif options[:only]
|
104
|
+
options[:only].include?(action)
|
105
|
+
else
|
106
|
+
true
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
module ClassMethods
|
111
|
+
def perspectives_actions(options = {})
|
112
|
+
self.perspectives_enabled_actions = options.slice(:only, :except).each_with_object({}) do |(k, v), h|
|
113
|
+
h[k] = Array(v).map(&:to_s)
|
114
|
+
end
|
115
|
+
|
116
|
+
respond_to :html, :json, options
|
117
|
+
self.responder = Perspectives::Responder
|
118
|
+
end
|
119
|
+
|
120
|
+
def wrapped_with(perspective, options = {})
|
121
|
+
perspective_klass = resolve_perspective_class_name(perspective)
|
122
|
+
|
123
|
+
options[:only] = Array(options[:only]).map(&:to_s) if options[:only]
|
124
|
+
options[:except] = Array(options[:except]).map(&:to_s) if options[:except]
|
125
|
+
|
126
|
+
options[:if] ||= lambda { |c| c.params[perspective_klass.id_param].present? }
|
127
|
+
options[:args] ||= lambda do |controller, perspective|
|
128
|
+
{
|
129
|
+
perspective_klass.active_record_klass.name.underscore => perspective_klass.active_record_klass.find(controller.params[perspective_klass.id_param]),
|
130
|
+
options.fetch(:as, controller_name.underscore.singularize) => perspective
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
self.perspectives_wrapping += [[perspective_klass, options]]
|
135
|
+
end
|
136
|
+
|
137
|
+
def resolve_perspective_class_name(name)
|
138
|
+
Perspectives.resolve_partial_class_name(controller_name.camelize, name)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Perspectives::Forms
|
2
|
+
class TextField < Base
|
3
|
+
param :object, :field
|
4
|
+
|
5
|
+
property(:param_key) { object.class.model_name.param_key }
|
6
|
+
property(:human_name) { object.class.name.humanize }
|
7
|
+
property(:field_id) { "#{param_key}_#{field}" }
|
8
|
+
property(:field_param) do
|
9
|
+
"#{param_key}[#{field.sub(/\?$/, '')}]"
|
10
|
+
end
|
11
|
+
|
12
|
+
property(:name) { object.class.human_attribute_name(field) }
|
13
|
+
property(:value) { object.__send__(field) }
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Perspectives
|
2
|
+
module Memoization
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def property(name, *names, &block)
|
9
|
+
super.tap { memoize_property(name) if names.empty? }
|
10
|
+
end
|
11
|
+
|
12
|
+
def memoize_property(prop_name)
|
13
|
+
raise ArgumentError, "No method #{prop_name}" unless method_defined?(prop_name)
|
14
|
+
|
15
|
+
original_property_method = "_unmemoized_#{prop_name}"
|
16
|
+
raise ArgumentError, "Already memoized property #{prop_name.inspect}" if method_defined?(original_property_method)
|
17
|
+
|
18
|
+
ivar = "@_memoized_#{prop_name.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}"
|
19
|
+
alias_method original_property_method, prop_name
|
20
|
+
|
21
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
22
|
+
def #{prop_name} # def name
|
23
|
+
return #{ivar} if defined?(#{ivar}) # return @_memoized_name if defined?(@_memoized_name)
|
24
|
+
#{ivar} = #{original_property_method} # @_memoized_name = _unmemoized_name
|
25
|
+
end
|
26
|
+
CODE
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# somewhat ganked from https://github.com/railsware/smt_rails/blob/7d63a3d5c838881690d365f41f45b9082c2611c8/lib/smt_rails/tilt.rb
|
2
|
+
|
3
|
+
require 'tilt'
|
4
|
+
|
5
|
+
module Perspectives
|
6
|
+
class MustacheCompiler < Tilt::Template
|
7
|
+
self.default_mime_type = 'application/javascript'
|
8
|
+
|
9
|
+
def prepare
|
10
|
+
end
|
11
|
+
|
12
|
+
def evaluate(scope, locals, &block)
|
13
|
+
namespace = "this.#{Perspectives.template_namespace}"
|
14
|
+
|
15
|
+
<<-MustacheTemplate
|
16
|
+
(function() {
|
17
|
+
#{namespace} || (#{namespace} = {});
|
18
|
+
#{namespace}.views || (#{namespace}.views = {})
|
19
|
+
|
20
|
+
var data = #{data.inspect}
|
21
|
+
|
22
|
+
Mustache.parse(data)
|
23
|
+
|
24
|
+
#{namespace}.views[#{scope.logical_path.inspect}] = function(object) {
|
25
|
+
if (!object){ object = {}; }
|
26
|
+
return Mustache.render(data, object)
|
27
|
+
};
|
28
|
+
|
29
|
+
}).call(this);
|
30
|
+
MustacheTemplate
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Perspectives
|
2
|
+
module Params
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
|
7
|
+
class_attribute :_required_params, :_optional_params
|
8
|
+
self._required_params = []
|
9
|
+
self._optional_params = []
|
10
|
+
attr_reader :_params, :context
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(context = {}, params = {})
|
15
|
+
raise ArgumentError, "Params is not a hash!" unless params.is_a?(Hash)
|
16
|
+
@_params = params.symbolize_keys
|
17
|
+
@context = context
|
18
|
+
assert_valid_params!
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def assert_valid_params!
|
24
|
+
missing = _required_params.select { |l| !_params.key?(l) }
|
25
|
+
unknown = _params.keys - (_required_params + _optional_params)
|
26
|
+
|
27
|
+
if missing.any?
|
28
|
+
raise ArgumentError, "Missing #{missing.join(', ').inspect} while initializing #{self.class}!"
|
29
|
+
elsif unknown.any?
|
30
|
+
raise ArgumentError, "Unrecognized params #{unknown.join(', ').inspect} while initializing #{self.class}!"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
def param(*param_names)
|
36
|
+
options = param_names.extract_options!
|
37
|
+
|
38
|
+
if options[:allow_nil]
|
39
|
+
self._optional_params += param_names
|
40
|
+
else
|
41
|
+
self._required_params += param_names
|
42
|
+
end
|
43
|
+
|
44
|
+
param_names.each { |n| define_method(n) { _params[n] } }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Perspectives
|
2
|
+
module Properties
|
3
|
+
CantUseLambdas = Class.new(StandardError)
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
extend ClassMethods
|
8
|
+
class_attribute :_properties, :_nested_perspectives
|
9
|
+
|
10
|
+
self._properties = []
|
11
|
+
self._nested_perspectives = []
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def _property_map
|
18
|
+
_properties.each_with_object({}) do |p, h|
|
19
|
+
h[p] = __send__(p)
|
20
|
+
|
21
|
+
if h[p].is_a?(Proc)
|
22
|
+
raise CantUseLambdas, "You cannot use the lambda mustache behavior if you want to render on the client...it's not portable!"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def _resolve_partial_class_name(name)
|
28
|
+
Perspectives.resolve_partial_class_name(self.class.to_s.split('::').first, name)
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
def property(name, *names, &block)
|
33
|
+
unless names.empty?
|
34
|
+
raise ArgumentError, "Can't define multiple properties and pass a block" if block_given?
|
35
|
+
return names.push(name).each(&public_method(:property))
|
36
|
+
end
|
37
|
+
|
38
|
+
self._properties += [name]
|
39
|
+
|
40
|
+
unless method_defined?(name)
|
41
|
+
raise ArgumentError, "No method #{name} and no block given" unless block_given?
|
42
|
+
|
43
|
+
define_method(name, &block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def nested(name, args = {}, &block)
|
48
|
+
locals, options = args, {}
|
49
|
+
|
50
|
+
if args[:locals]
|
51
|
+
locals = args[:locals]
|
52
|
+
options = args.except(:locals)
|
53
|
+
end
|
54
|
+
|
55
|
+
_setup_nested(name, locals, options, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def nested_collection(name, *args, &block)
|
59
|
+
options = args.extract_options!
|
60
|
+
collection = options.fetch(:collection, args.first)
|
61
|
+
raise ArgumentError, "You must either pass in a collection, or pass a collection option" unless collection
|
62
|
+
|
63
|
+
_setup_nested(name, options.fetch(:locals, {}), options.merge!(:collection => collection), &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
def delegate_property(*props)
|
67
|
+
delegate *props
|
68
|
+
opts = props.pop
|
69
|
+
|
70
|
+
prop_names = props
|
71
|
+
|
72
|
+
if opts[:prefix]
|
73
|
+
prefix = opts[:prefix] == true ? opts[:to] : opts[:prefix]
|
74
|
+
prop_names = prop_names.map { |n| "#{prefix}_#{n}" }
|
75
|
+
end
|
76
|
+
|
77
|
+
prop_names.each(&public_method(:property))
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def _setup_nested(name, locals, options, &block)
|
83
|
+
name_str, name_sym = name.to_s, name.to_sym
|
84
|
+
|
85
|
+
prop_name = options.fetch(:property, _default_property_name(name_str, options)).to_sym
|
86
|
+
|
87
|
+
unless block_given? || method_defined?(prop_name)
|
88
|
+
local_procs = locals.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.respond_to?(:to_proc) ? v.to_proc : proc { v } }
|
89
|
+
nested_klass_ivar = :"@_#{name_str.underscore.gsub('/', '__')}_klass"
|
90
|
+
|
91
|
+
define_method(prop_name) do
|
92
|
+
klass =
|
93
|
+
if self.class.instance_variable_defined?(nested_klass_ivar)
|
94
|
+
self.class.instance_variable_get(nested_klass_ivar)
|
95
|
+
else
|
96
|
+
self.class.instance_variable_set(nested_klass_ivar, _resolve_partial_class_name(name))
|
97
|
+
end
|
98
|
+
|
99
|
+
if options[:unless]
|
100
|
+
return if instance_exec(self, &options[:unless])
|
101
|
+
elsif options[:if]
|
102
|
+
return unless instance_exec(self, &options[:if])
|
103
|
+
end
|
104
|
+
|
105
|
+
realized_locals = local_procs.each_with_object({}) { |(k, v), h| h[k] = instance_exec(self, &v) }
|
106
|
+
|
107
|
+
if options.key?(:collection)
|
108
|
+
collection = instance_exec(self, &options[:collection])
|
109
|
+
return unless collection.present?
|
110
|
+
|
111
|
+
as = options.fetch(:as, collection.first.class.base_class.name.downcase).to_sym
|
112
|
+
Collection.new(collection.map { |o| klass.new(context, realized_locals.merge(as => o)) })
|
113
|
+
else
|
114
|
+
klass.new(context, realized_locals)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
property(prop_name, &block)
|
120
|
+
self._nested_perspectives += [prop_name]
|
121
|
+
end
|
122
|
+
|
123
|
+
def _default_property_name(name_str, options)
|
124
|
+
name = name_str.split('/').last
|
125
|
+
name = name.pluralize if options.key?(:collection)
|
126
|
+
name
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'perspectives/controller_additions'
|
2
|
+
require 'perspectives/responder'
|
3
|
+
require 'perspectives/active_record'
|
4
|
+
require 'generators/perspectives/install.rb'
|
5
|
+
require 'generators/perspectives/scaffold/scaffold_generator.rb'
|
6
|
+
|
7
|
+
module Perspectives
|
8
|
+
class Railtie < Rails::Railtie
|
9
|
+
if ::Rails.version.to_s >= "3.1"
|
10
|
+
config.app_generators.template_engine :perspectives
|
11
|
+
config.app_generators.templates << File.expand_path('../../generators/perspectives/templates', __FILE__)
|
12
|
+
else
|
13
|
+
config.generators.template_engine :perspectives
|
14
|
+
config.generators.templates << File.expand_path('../../generators/perspectives/templates', __FILE__)
|
15
|
+
end
|
16
|
+
|
17
|
+
initializer 'perspectives.railtie' do |app|
|
18
|
+
app.config.autoload_paths += ['app/perspectives']
|
19
|
+
app.config.watchable_dirs['app/mustaches'] = [:mustache]
|
20
|
+
|
21
|
+
app.config.assets.paths << File.expand_path('../../../vendor/assets/javascripts', __FILE__)
|
22
|
+
|
23
|
+
Perspectives::Base.class_eval do
|
24
|
+
include ActionView::Helpers
|
25
|
+
include app.routes.url_helpers
|
26
|
+
include ERB::Util
|
27
|
+
include Perspectives::ActiveRecord
|
28
|
+
end
|
29
|
+
|
30
|
+
Perspectives.configure do |c|
|
31
|
+
c.template_path = app.root.join('app', 'mustaches')
|
32
|
+
end
|
33
|
+
|
34
|
+
app.assets.register_engine '.mustache', Perspectives::MustacheCompiler
|
35
|
+
app.config.assets.paths << Perspectives.template_path
|
36
|
+
|
37
|
+
# TODO: probably bail if we're not in rails3/sprockets land...
|
38
|
+
# TODO: probably cache asset version in prod?
|
39
|
+
ActionController::Base.send(:include, Perspectives::ControllerAdditions)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Perspectives
|
2
|
+
module Rendering
|
3
|
+
def as_json(options = {})
|
4
|
+
_property_map.merge(_template_key: _template_key)
|
5
|
+
end
|
6
|
+
|
7
|
+
def render_html
|
8
|
+
_mustache.render(_property_map).html_safe
|
9
|
+
end
|
10
|
+
|
11
|
+
def render; render_html; end
|
12
|
+
def to_html; render_html; end
|
13
|
+
def to_s; render_html; end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Perspectives
|
2
|
+
class Responder < ActionController::Responder
|
3
|
+
def to_html
|
4
|
+
return super unless controller.__send__(:perspectives_enabled_action?)
|
5
|
+
|
6
|
+
render text: resource.to_html, layout: :default
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_json
|
10
|
+
return super unless controller.__send__(:perspectives_enabled_action?)
|
11
|
+
|
12
|
+
render json: resource
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Perspectives
|
2
|
+
module Templating
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
|
7
|
+
delegate :_mustache, :_template_key, to: 'self.class'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def raise_on_context_miss?
|
13
|
+
Perspectives.raise_on_context_miss?
|
14
|
+
end
|
15
|
+
|
16
|
+
def template_path
|
17
|
+
Perspectives.template_path
|
18
|
+
end
|
19
|
+
|
20
|
+
def _mustache
|
21
|
+
return @_mustache if defined?(@_mustache)
|
22
|
+
|
23
|
+
klass = self
|
24
|
+
@_mustache = Class.new(Mustache) do
|
25
|
+
self.template_name = klass.to_s.underscore
|
26
|
+
self.raise_on_context_miss = klass.raise_on_context_miss?
|
27
|
+
self.template_path = klass.template_path
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def _template_key
|
32
|
+
@_template_key ||=
|
33
|
+
_mustache.template_file.
|
34
|
+
sub(/^#{Regexp.escape(_mustache.template_path)}\//, '').
|
35
|
+
chomp(".#{_mustache.template_extension}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/perspectives.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'mustache'
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
4
|
+
require 'active_support/core_ext/module/delegation'
|
5
|
+
require 'active_support/core_ext/array/extract_options'
|
6
|
+
require 'active_support/core_ext/hash/keys'
|
7
|
+
require 'active_support/core_ext/class/attribute'
|
8
|
+
require 'perspectives/collection'
|
9
|
+
require 'perspectives/base'
|
10
|
+
require 'perspectives/forms'
|
11
|
+
require 'perspectives/configuration'
|
12
|
+
require 'perspectives/mustache_compiler'
|
13
|
+
require 'perspectives/railtie' if defined?(Rails) # TODO: older rails support!
|
14
|
+
|
15
|
+
module Perspectives
|
16
|
+
class << self
|
17
|
+
def template_namespace
|
18
|
+
'Perspectives'
|
19
|
+
end
|
20
|
+
|
21
|
+
def configure
|
22
|
+
yield(configuration)
|
23
|
+
end
|
24
|
+
|
25
|
+
delegate :cache, :caching?, :template_path, :raise_on_context_miss?, to: :configuration
|
26
|
+
delegate :expand_cache_key, to: 'ActiveSupport::Cache'
|
27
|
+
|
28
|
+
def resolve_partial_class_name(top_level_view_namespace, name)
|
29
|
+
return name if name.is_a?(Class) && name < Perspectives::Base
|
30
|
+
|
31
|
+
camelized = name.to_s.camelize
|
32
|
+
|
33
|
+
[top_level_view_namespace, camelized].join('::').constantize
|
34
|
+
rescue NameError
|
35
|
+
camelized.constantize
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def configuration
|
41
|
+
@configuration ||= Configuration.new
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
configure do |c|
|
46
|
+
c.raise_on_context_miss = true
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
{
|
2
|
+
"config/projections.json": {"command": "projections"},
|
3
|
+
"config/application.rb": {"command": "application"},
|
4
|
+
"app/perspectives/*.rb": {
|
5
|
+
"command": "perspective",
|
6
|
+
"alternate": "app/mustaches/%s.mustache"
|
7
|
+
},
|
8
|
+
"app/mustaches/*.mustache": {
|
9
|
+
"command": "mview",
|
10
|
+
"alternate": "app/perspectives/%s.rb"
|
11
|
+
}
|
12
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
== README
|
2
|
+
|
3
|
+
This README would normally document whatever steps are necessary to get the
|
4
|
+
application up and running.
|
5
|
+
|
6
|
+
Things you may want to cover:
|
7
|
+
|
8
|
+
* Ruby version
|
9
|
+
|
10
|
+
* System dependencies
|
11
|
+
|
12
|
+
* Configuration
|
13
|
+
|
14
|
+
* Database creation
|
15
|
+
|
16
|
+
* Database initialization
|
17
|
+
|
18
|
+
* How to run the test suite
|
19
|
+
|
20
|
+
* Services (job queues, cache servers, search engines, etc.)
|
21
|
+
|
22
|
+
* Deployment instructions
|
23
|
+
|
24
|
+
* ...
|
25
|
+
|
26
|
+
|
27
|
+
Please feel free to use a different markup language if you do not plan to run
|
28
|
+
<tt>rake doc:app</tt>.
|
data/spec/dummy/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
10
|
+
*
|
11
|
+
*= require_self
|
12
|
+
*= require_tree .
|
13
|
+
*/
|