locomotive_plugins 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+
2
+ module Locomotive
3
+ module Plugin
4
+ # All classes to be persisted by a plugin should inherit from this class
5
+ # (see Locomotive::Plugin::ClassMethods #has_many or #has_one)
6
+ class DBModel
7
+ include ::Mongoid::Document
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+
2
+ module Locomotive
3
+ module Plugin
4
+ # The superclass of the DBModelContainer object for each plugin. This
5
+ # container has the actual relationships to the database objects
6
+ class DBModelContainer
7
+ include Mongoid::Document
8
+ end
9
+ end
10
+ end
@@ -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,3 @@
1
+ module LocomotivePlugins
2
+ VERSION = '1.0.0.beta2'
3
+ end
@@ -0,0 +1,2 @@
1
+ %li
2
+ %p This is my template!
@@ -0,0 +1,3 @@
1
+ <li>
2
+ <p>This is my template!</p>
3
+ </li>
@@ -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