locomotive_plugins 1.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +26 -0
- data/README.md +229 -0
- data/Rakefile +13 -0
- data/lib/locomotive/plugin.rb +162 -0
- data/lib/locomotive/plugin/config_ui.rb +26 -0
- data/lib/locomotive/plugin/db_model.rb +10 -0
- data/lib/locomotive/plugin/db_model_container.rb +10 -0
- data/lib/locomotive/plugin/db_models.rb +86 -0
- data/lib/locomotive/plugin/liquid.rb +87 -0
- data/lib/locomotive/plugin/liquid/prefixed_filter_module.rb +24 -0
- data/lib/locomotive/plugin/liquid/tag_subclass_methods.rb +22 -0
- data/lib/locomotive_plugins.rb +44 -0
- data/lib/version.rb +3 -0
- data/spec/fixtures/config_template.haml +2 -0
- data/spec/fixtures/config_template.html +3 -0
- data/spec/lib/locomotive/plugin/config_ui_spec.rb +41 -0
- data/spec/lib/locomotive/plugin/db_models_spec.rb +56 -0
- data/spec/lib/locomotive/plugin/liquid_spec.rb +125 -0
- data/spec/lib/locomotive/plugin_spec.rb +46 -0
- data/spec/lib/locomotive_plugins_spec.rb +29 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/support/factories.rb +3 -0
- data/spec/support/plugins/my_other_plugin.rb +16 -0
- data/spec/support/plugins/my_plugin.rb +42 -0
- data/spec/support/plugins/plugin_with_db_model.rb +19 -0
- data/spec/support/plugins/plugin_with_filter.rb +21 -0
- data/spec/support/plugins/plugin_with_many_filter_modules.rb +23 -0
- data/spec/support/plugins/plugin_with_non_string_path.rb +23 -0
- data/spec/support/plugins/plugin_with_tags.rb +31 -0
- data/spec/support/plugins/useless_plugin.rb +6 -0
- metadata +171 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
|
2
|
+
module Locomotive
|
3
|
+
module Plugin
|
4
|
+
|
5
|
+
# Utility methods for dealing with DB Models
|
6
|
+
module DBModels
|
7
|
+
|
8
|
+
# @private
|
9
|
+
def self.included(base)
|
10
|
+
base.extend ClassMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
# @private
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
def add_db_model_class_methods(base)
|
17
|
+
base.class_eval <<-CODE
|
18
|
+
class DBModelContainer < ::Locomotive::Plugin::DBModelContainer
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.db_model_container_class
|
22
|
+
DBModelContainer
|
23
|
+
end
|
24
|
+
CODE
|
25
|
+
base.extend DBModelClassMethods
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
# @private
|
31
|
+
module DBModelClassMethods
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def create_has_many_relationship(name, klass)
|
36
|
+
self.db_model_container_class.embeds_many(name,
|
37
|
+
class_name: klass.to_s, inverse_of: :db_model_container)
|
38
|
+
klass.embedded_in(:db_model_container,
|
39
|
+
class_name: db_model_container_class.to_s, inverse_of: name)
|
40
|
+
|
41
|
+
self.define_passthrough_methods_to_container(name, "#{name}=")
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_has_one_relationship(name, klass)
|
45
|
+
self.db_model_container_class.embeds_one(name,
|
46
|
+
class_name: klass.to_s, inverse_of: :db_model_container)
|
47
|
+
klass.embedded_in(:db_model_container,
|
48
|
+
class_name: db_model_container_class.to_s, inverse_of: name)
|
49
|
+
|
50
|
+
self.define_passthrough_methods_to_container(name, "#{name}=",
|
51
|
+
"build_#{name}", "create_#{name}")
|
52
|
+
end
|
53
|
+
|
54
|
+
def define_passthrough_methods_to_container(*methods)
|
55
|
+
class_eval <<-EOF
|
56
|
+
%w{#{methods.join(' ')}}.each do |meth|
|
57
|
+
define_method(meth) do |*args|
|
58
|
+
@db_model_container.send(meth, *args)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
EOF
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
# Save the DB Model container
|
67
|
+
def save_db_model_container
|
68
|
+
self.db_model_container.save
|
69
|
+
end
|
70
|
+
|
71
|
+
# Get the DB Model container
|
72
|
+
def db_model_container
|
73
|
+
@db_model_container || load_or_create_db_model_container!
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
def load_or_create_db_model_container!
|
79
|
+
@db_model_container = self.class.db_model_container_class.first \
|
80
|
+
|| self.class.db_model_container_class.new
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
|
2
|
+
module Locomotive
|
3
|
+
module Plugin
|
4
|
+
module Liquid
|
5
|
+
|
6
|
+
# @private
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @private
|
12
|
+
module ClassMethods
|
13
|
+
def add_liquid_tag_methods(base)
|
14
|
+
base.extend(LiquidTagMethods)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module LiquidTagMethods
|
19
|
+
|
20
|
+
# Returns a hash of tag names and tag classes to be registered in the
|
21
|
+
# liquid environment. The tag names are prefixed by the given prefix,
|
22
|
+
# and the tag classes are modified so that they check the liquid
|
23
|
+
# context to determine whether they are enabled and should render
|
24
|
+
# normally
|
25
|
+
def prefixed_liquid_tags(prefix)
|
26
|
+
self.liquid_tags.inject({}) do |hash, (tag_name, tag_class)|
|
27
|
+
hash["#{prefix}_#{tag_name}"] = tag_subclass(tag_class)
|
28
|
+
hash
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
# Creates a nested subclass to handle rendering this tag
|
35
|
+
def tag_subclass(tag_class)
|
36
|
+
tag_class.class_eval <<-CODE
|
37
|
+
class TagSubclass < #{tag_class.to_s}
|
38
|
+
include ::Locomotive::Plugin::TagSubclassMethods
|
39
|
+
end
|
40
|
+
CODE
|
41
|
+
tag_class::TagSubclass
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
# Gets the module to include as a filter in liquid. It prefixes the
|
47
|
+
# filter methods with the given string
|
48
|
+
def prefixed_liquid_filter_module(prefix)
|
49
|
+
# Build up a string to eval into the module so we only need to reopen
|
50
|
+
# it once
|
51
|
+
strings_to_eval = []
|
52
|
+
|
53
|
+
raw_filter_modules = [self.class.liquid_filters].flatten.compact
|
54
|
+
raw_filter_modules.each do |mod|
|
55
|
+
mod.public_instance_methods.each do |meth|
|
56
|
+
strings_to_eval << <<-CODE
|
57
|
+
def #{prefix}_#{meth}(input)
|
58
|
+
self._passthrough_filter_call_for_#{prefix}('#{meth}', input)
|
59
|
+
end
|
60
|
+
CODE
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
strings_to_eval << <<-CODE
|
65
|
+
protected
|
66
|
+
|
67
|
+
def _passthrough_object_for_#{prefix}
|
68
|
+
@_passthrough_object_for_#{prefix} ||= \
|
69
|
+
self._build_passthrough_object([#{raw_filter_modules.join(',')}])
|
70
|
+
end
|
71
|
+
|
72
|
+
def _passthrough_filter_call_for_#{prefix}(meth, input)
|
73
|
+
self._passthrough_object_for_#{prefix}.public_send(meth, input)
|
74
|
+
end
|
75
|
+
CODE
|
76
|
+
|
77
|
+
# Eval the dynamic methods in
|
78
|
+
@prefixed_liquid_filter_module = Module.new do
|
79
|
+
include ::Locomotive::Plugin::Liquid::PrefixedFilterModule
|
80
|
+
end
|
81
|
+
@prefixed_liquid_filter_module.class_eval strings_to_eval.join("\n")
|
82
|
+
@prefixed_liquid_filter_module
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
module Locomotive
|
3
|
+
module Plugin
|
4
|
+
module Liquid
|
5
|
+
# @private
|
6
|
+
# This module provides functionality for the module which aggregates all
|
7
|
+
# the prefixed filter methods. See
|
8
|
+
# <tt>Locomotive::Plugin::Liquid#prefixed_liquid_filter_module</tt>
|
9
|
+
module PrefixedFilterModule
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def _build_passthrough_object(modules_to_extend)
|
14
|
+
Object.new.tap do |obj|
|
15
|
+
modules_to_extend.each do |mod|
|
16
|
+
obj.extend(mod)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
module Locomotive
|
3
|
+
module Plugin
|
4
|
+
module Liquid
|
5
|
+
# @private
|
6
|
+
module TagSubclassMethods
|
7
|
+
# Check to see if this tag is enabled in the liquid context and render
|
8
|
+
# accordingly
|
9
|
+
def render(context)
|
10
|
+
enabled = context.registers[:enabled_plugin_tags]
|
11
|
+
if enabled && enabled.include?(self.class)
|
12
|
+
super
|
13
|
+
elsif self.respond_to?(:render_disabled)
|
14
|
+
self.render_disabled(context)
|
15
|
+
else
|
16
|
+
''
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'liquid'
|
5
|
+
require 'haml'
|
6
|
+
require 'mongoid'
|
7
|
+
|
8
|
+
require 'locomotive/plugin'
|
9
|
+
|
10
|
+
# The overall module for registering plugins
|
11
|
+
module LocomotivePlugins
|
12
|
+
|
13
|
+
# Get the default ID for the given plugin class
|
14
|
+
#
|
15
|
+
# @param plugin_class[Class] the class of the plugin object
|
16
|
+
def self.default_id(plugin_class)
|
17
|
+
plugin_class.to_s.split('::').last.underscore
|
18
|
+
end
|
19
|
+
|
20
|
+
# Register a plugin class with a given ID. If no ID is given, the default ID
|
21
|
+
# is obtained by calling <tt>default_id(plugin_class)</tt>
|
22
|
+
#
|
23
|
+
# @param plugin_class[Class] the class pf the plugin to register
|
24
|
+
# @param plugin_id[String] the plugin ID to use
|
25
|
+
def self.register_plugin(plugin_class, plugin_id = nil)
|
26
|
+
@@registered_plugins ||= {}
|
27
|
+
plugin_id ||= self.default_id(plugin_class)
|
28
|
+
@@registered_plugins[plugin_id] = plugin_class
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get the hash of registered plugin classes, where the keys are the IDs which
|
32
|
+
# were used to register the plugins
|
33
|
+
#
|
34
|
+
# @return [Hash<String, Class>] a hash of plugin IDs to plugin classes
|
35
|
+
def self.registered_plugins
|
36
|
+
@@registered_plugins ||= {}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Remove all plugins from the registered list
|
40
|
+
def self.clear_registered_plugins
|
41
|
+
@@registered_plugins = {}
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
data/lib/version.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
module Locomotive
|
5
|
+
module Plugin
|
6
|
+
describe ConfigUI do
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
@config = {}
|
10
|
+
@plugin = MyPlugin.new(@config)
|
11
|
+
@plugin_with_non_string_path = PluginWithNonStringPath.new(@config)
|
12
|
+
@another_plugin = MyOtherPlugin.new(@config)
|
13
|
+
@useless_plugin = UselessPlugin.new(@config)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should return the template string of an HTML file' do
|
17
|
+
@plugin = MyPlugin.new({})
|
18
|
+
filepath = @plugin.config_template_file
|
19
|
+
@plugin.config_template_string.should == IO.read(filepath)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should handle non-string paths' do
|
23
|
+
filepath = @plugin_with_non_string_path.config_template_file
|
24
|
+
template = @plugin_with_non_string_path.config_template_string
|
25
|
+
template.should == IO.read(filepath.to_s)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should return nil for the template string if no file is specified' do
|
29
|
+
@useless_plugin.config_template_string.should be_nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should compile the template string for a HAML file' do
|
33
|
+
filepath = @another_plugin.config_template_file
|
34
|
+
haml = IO.read(filepath)
|
35
|
+
html = Haml::Engine.new(haml).render
|
36
|
+
@another_plugin.config_template_string.should == html
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
module Locomotive
|
3
|
+
module Plugin
|
4
|
+
describe DBModels do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
plugin = PluginWithDBModel.new({})
|
8
|
+
plugin.build_visit_count(count: 5)
|
9
|
+
plugin.items.build(name: 'First Item')
|
10
|
+
plugin.items.build(name: 'Second Item')
|
11
|
+
plugin.save_db_model_container.should be_true
|
12
|
+
|
13
|
+
# Reload from the database
|
14
|
+
@plugin_with_db_model = PluginWithDBModel.new({})
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should persist DBModel items' do
|
18
|
+
@plugin_with_db_model.visit_count.count.should == 5
|
19
|
+
|
20
|
+
@plugin_with_db_model.items.count.should == 2
|
21
|
+
@plugin_with_db_model.items[0].name.should == 'First Item'
|
22
|
+
@plugin_with_db_model.items[1].name.should == 'Second Item'
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should allow mongoid queries on persisted DBModel items' do
|
26
|
+
@plugin_with_db_model.items.where(name: /First/).count.should == 1
|
27
|
+
@plugin_with_db_model.items.where(name: /First/).first.name.should == 'First Item'
|
28
|
+
|
29
|
+
@plugin_with_db_model.items.where(name: /Item/).count.should == 2
|
30
|
+
@plugin_with_db_model.items.where(name: /Item/)[0].name.should == 'First Item'
|
31
|
+
@plugin_with_db_model.items.where(name: /Item/)[1].name.should == 'Second Item'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should embed DBModel items in a document for the plugin class' do
|
35
|
+
@plugin_with_db_model.visit_count.db_model_container.kind_of?(
|
36
|
+
PluginWithDBModel::DBModelContainer).should be_true
|
37
|
+
@plugin_with_db_model.items.first.db_model_container.kind_of?(
|
38
|
+
PluginWithDBModel::DBModelContainer).should be_true
|
39
|
+
|
40
|
+
@plugin_with_db_model.visit_count.relations[
|
41
|
+
'db_model_container'].relation.should \
|
42
|
+
== Mongoid::Relations::Embedded::In
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should run all validations for DBModel items' do
|
46
|
+
@plugin_with_db_model.items.build(name: '')
|
47
|
+
@plugin_with_db_model.save_db_model_container.should be_false
|
48
|
+
|
49
|
+
@plugin_with_db_model.db_model_container.errors.messages.should == {
|
50
|
+
:items => [ 'is invalid' ]
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
module Locomotive
|
5
|
+
module Plugin
|
6
|
+
describe Liquid do
|
7
|
+
|
8
|
+
describe '#prefixed_liquid_filter_module' do
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
@plugin_with_filter = PluginWithFilter.new({})
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should contain all prefixed methods for provided filter modules' do
|
15
|
+
mod = @plugin_with_filter.prefixed_liquid_filter_module('prefix')
|
16
|
+
mod.public_instance_methods.should include(:prefix_add_http)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should not contain any of the original methods' do
|
20
|
+
mod = @plugin_with_filter.prefixed_liquid_filter_module('prefix')
|
21
|
+
mod.public_instance_methods.should_not include(:add_http)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'the prefixed methods should pass through to the original methods' do
|
25
|
+
obj = Object.new
|
26
|
+
obj.extend(@plugin_with_filter.prefixed_liquid_filter_module('prefix'))
|
27
|
+
obj.prefix_add_http('google.com').should == 'http://google.com'
|
28
|
+
obj.prefix_add_http('http://google.com').should == 'http://google.com'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'includes multiple filter modules for one plugin' do
|
32
|
+
@plugin_with_many_filter_modules = PluginWithManyFilterModules.new({})
|
33
|
+
mod = @plugin_with_many_filter_modules.prefixed_liquid_filter_module('prefix')
|
34
|
+
mod.public_instance_methods.should include(:prefix_add_newline)
|
35
|
+
mod.public_instance_methods.should include(:prefix_remove_http)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'works if multiple prefixed modules are mixed into the same object' do
|
39
|
+
@plugin_with_many_filter_modules = PluginWithManyFilterModules.new({})
|
40
|
+
|
41
|
+
obj = Object.new
|
42
|
+
obj.extend(@plugin_with_filter.prefixed_liquid_filter_module('prefix1'))
|
43
|
+
obj.extend(@plugin_with_many_filter_modules.prefixed_liquid_filter_module('prefix2'))
|
44
|
+
|
45
|
+
obj.prefix1_add_http('google.com').should == 'http://google.com'
|
46
|
+
obj.prefix2_add_newline('google.com').should == "google.com\n"
|
47
|
+
obj.prefix2_remove_http('http://google.com').should == 'google.com'
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'liquid tags' do
|
53
|
+
|
54
|
+
before(:each) do
|
55
|
+
@plugin_class = PluginWithTags
|
56
|
+
@prefixed_tags = @plugin_class.prefixed_liquid_tags('prefix')
|
57
|
+
|
58
|
+
@enabled_tags = []
|
59
|
+
@context = ::Liquid::Context.new
|
60
|
+
@context.registers[:enabled_plugin_tags] = @enabled_tags
|
61
|
+
|
62
|
+
@raw_template = <<-TEMPLATE
|
63
|
+
{% prefix_paragraph %}Some Text{% endprefix_paragraph %}
|
64
|
+
Some Text{% prefix_newline %}
|
65
|
+
TEMPLATE
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'supplies the prefixed tag names along with subclasses of the tag classes' do
|
69
|
+
@prefixed_tags.size.should == 2
|
70
|
+
@prefixed_tags['prefix_paragraph'].should be < PluginWithTags::Paragraph
|
71
|
+
@prefixed_tags['prefix_newline'].should be < PluginWithTags::Newline
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'only renders a tag if it is enabled in the liquid context' do
|
75
|
+
expected_output = <<-TEMPLATE
|
76
|
+
<p>Some Text</p>
|
77
|
+
Some Text<br />
|
78
|
+
TEMPLATE
|
79
|
+
|
80
|
+
register_tags(@prefixed_tags)
|
81
|
+
template = ::Liquid::Template.parse(@raw_template)
|
82
|
+
template.render(@context).should_not == expected_output
|
83
|
+
|
84
|
+
@enabled_tags << @prefixed_tags['prefix_paragraph']
|
85
|
+
@enabled_tags << @prefixed_tags['prefix_newline']
|
86
|
+
template.render(@context).should == expected_output
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'uses render_disabled or empty string if the plugin is not enabled' do
|
90
|
+
expected_output = <<-TEMPLATE
|
91
|
+
Some Text
|
92
|
+
Some Text
|
93
|
+
TEMPLATE
|
94
|
+
|
95
|
+
register_tags(@prefixed_tags)
|
96
|
+
template = ::Liquid::Template.parse(@raw_template)
|
97
|
+
template.render(@context).should == expected_output
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'uses render_disabled or empty string if no plugin is enabled' do
|
101
|
+
@context.registers.delete(:enabled_plugin_tags)
|
102
|
+
|
103
|
+
expected_output = <<-TEMPLATE
|
104
|
+
Some Text
|
105
|
+
Some Text
|
106
|
+
TEMPLATE
|
107
|
+
|
108
|
+
register_tags(@prefixed_tags)
|
109
|
+
template = ::Liquid::Template.parse(@raw_template)
|
110
|
+
template.render(@context).should == expected_output
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
|
115
|
+
def register_tags(tags)
|
116
|
+
tags.each do |name, klass|
|
117
|
+
::Liquid::Template.register_tag(name, klass)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|