locomotive_plugins 1.0.0.beta2
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.
- 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
|