om 1.8.1 → 1.9.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/container_spec.rb +14 -14
- data/lib/om.rb +12 -9
- data/lib/om/samples/mods_article.rb +9 -9
- data/lib/om/tree_node.rb +6 -6
- data/lib/om/version.rb +1 -1
- data/lib/om/xml.rb +33 -31
- data/lib/om/xml/container.rb +12 -12
- data/lib/om/xml/document.rb +19 -18
- data/lib/om/xml/dynamic_node.rb +50 -45
- data/lib/om/xml/named_term_proxy.rb +13 -13
- data/lib/om/xml/node_generator.rb +3 -3
- data/lib/om/xml/template_registry.rb +26 -18
- data/lib/om/xml/term.rb +46 -30
- data/lib/om/xml/term_value_operators.rb +56 -52
- data/lib/om/xml/term_xpath_generator.rb +57 -51
- data/lib/om/xml/terminology.rb +10 -8
- data/lib/om/xml/terminology_based_solrizer.rb +90 -0
- data/lib/om/xml/validation.rb +19 -19
- data/lib/om/xml/vocabulary.rb +4 -4
- data/lib/tasks/om.rake +6 -4
- data/om.gemspec +2 -1
- data/spec/fixtures/mods_article.rb +90 -0
- data/spec/fixtures/mods_articles/hydrangea_article1.xml +2 -2
- data/spec/integration/differentiated_elements_spec.rb +2 -2
- data/spec/integration/element_value_spec.rb +13 -13
- data/spec/integration/proxies_and_ref_spec.rb +15 -15
- data/spec/integration/querying_documents_spec.rb +18 -24
- data/spec/integration/rights_metadata_integration_example_spec.rb +18 -18
- data/spec/integration/selective_querying_spec.rb +1 -1
- data/spec/integration/serialization_spec.rb +13 -13
- data/spec/integration/set_reentrant_terminology_spec.rb +10 -10
- data/spec/integration/xpathy_stuff_spec.rb +16 -16
- data/spec/spec_helper.rb +2 -2
- data/spec/unit/container_spec.rb +29 -28
- data/spec/unit/document_spec.rb +50 -49
- data/spec/unit/dynamic_node_spec.rb +45 -57
- data/spec/unit/named_term_proxy_spec.rb +16 -16
- data/spec/unit/node_generator_spec.rb +7 -7
- data/spec/unit/nokogiri_sanity_spec.rb +30 -30
- data/spec/unit/om_spec.rb +5 -5
- data/spec/unit/template_registry_spec.rb +69 -69
- data/spec/unit/term_builder_spec.rb +77 -77
- data/spec/unit/term_spec.rb +73 -79
- data/spec/unit/term_value_operators_spec.rb +191 -186
- data/spec/unit/term_xpath_generator_spec.rb +43 -37
- data/spec/unit/terminology_builder_spec.rb +85 -85
- data/spec/unit/terminology_spec.rb +98 -98
- data/spec/unit/validation_spec.rb +22 -22
- data/spec/unit/xml_serialization_spec.rb +22 -21
- data/spec/unit/xml_spec.rb +7 -7
- data/spec/unit/xml_terminology_based_solrizer_spec.rb +109 -0
- metadata +57 -17
- checksums.yaml +0 -7
- data/.rspec +0 -1
- data/.rubocop.yml +0 -1
- data/.rubocop_todo.yml +0 -382
- data/.travis.yml +0 -10
- data/gemfiles/gemfile.rails3 +0 -11
- data/gemfiles/gemfile.rails4 +0 -10
data/Rakefile
CHANGED
data/container_spec.rb
CHANGED
@@ -3,34 +3,34 @@ require "nokogiri"
|
|
3
3
|
require "om"
|
4
4
|
|
5
5
|
describe "OM::XML::Container" do
|
6
|
-
|
6
|
+
|
7
7
|
before(:all) do
|
8
8
|
class ContainerTest
|
9
9
|
include OM::XML::Container
|
10
10
|
end
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
before(:each) do
|
14
14
|
@container = ContainerTest.from_xml("<foo><bar>1</bar></foo>")
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
it "should add .ng_xml accessor" do
|
18
18
|
@container.should respond_to(:ng_xml)
|
19
|
-
@container.should respond_to(:ng_xml=)
|
19
|
+
@container.should respond_to(:ng_xml=)
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
describe "new" do
|
23
23
|
it "should populate ng_xml with an instance of Nokogiri::XML::Document" do
|
24
24
|
@container.ng_xml.class.should == Nokogiri::XML::Document
|
25
25
|
end
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
describe '#xml_template' do
|
29
29
|
it "should return an empty xml document" do
|
30
30
|
ContainerTest.xml_template.to_xml.should == "<?xml version=\"1.0\"?>\n"
|
31
31
|
end
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
describe "#from_xml" do
|
35
35
|
it "should accept a String, parse it and store it in .ng_xml" do
|
36
36
|
Nokogiri::XML::Document.expects(:parse).returns("parsed xml")
|
@@ -53,36 +53,36 @@ describe "OM::XML::Container" do
|
|
53
53
|
ContainerTest.from_xml.ng_xml.should == "fake template"
|
54
54
|
end
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
describe ".to_xml" do
|
58
58
|
it "should call .ng_xml.to_xml" do
|
59
59
|
@container.ng_xml.expects(:to_xml).returns("ng xml")
|
60
60
|
@container.to_xml.should == "ng xml"
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
it 'should accept an optional Nokogiri::XML Document as an argument and insert its fields into that (mocked test)' do
|
64
64
|
doc = Nokogiri::XML::Document.parse("<test_xml/>")
|
65
65
|
mock_new_node = mock("new node")
|
66
66
|
doc.root.expects(:add_child).with(@container.ng_xml.root).returns(mock_new_node)
|
67
67
|
result = @container.to_xml(doc)
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
it 'should accept an optional Nokogiri::XML Document as an argument and insert its fields into that (functional test)' do
|
71
71
|
doc = Nokogiri::XML::Document.parse("<test_xml/>")
|
72
72
|
@container.to_xml(doc).should == "<?xml version=\"1.0\"?>\n<test_xml>\n <foo>\n <bar>1</bar>\n </foo>\n</test_xml>\n"
|
73
73
|
end
|
74
|
-
|
74
|
+
|
75
75
|
it 'should add to root of Nokogiri::XML::Documents, but add directly to the elements if a Nokogiri::XML::Node is passed in' do
|
76
76
|
mock_new_node = mock("new node")
|
77
77
|
mock_new_node.stubs(:to_xml).returns("foo")
|
78
|
-
|
78
|
+
|
79
79
|
doc = Nokogiri::XML::Document.parse("<test_document/>")
|
80
80
|
el = Nokogiri::XML::Node.new("test_element", Nokogiri::XML::Document.new)
|
81
81
|
doc.root.expects(:add_child).with(@container.ng_xml.root).returns(mock_new_node)
|
82
82
|
el.expects(:add_child).with(@container.ng_xml.root).returns(mock_new_node)
|
83
|
-
@container.to_xml(doc).should
|
83
|
+
@container.to_xml(doc).should
|
84
84
|
@container.to_xml(el)
|
85
85
|
end
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
end
|
data/lib/om.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require '
|
2
|
+
require 'active_support/concern'
|
3
|
+
require 'active_model/dirty'
|
3
4
|
require 'deprecation'
|
4
5
|
require 'nokogiri'
|
5
6
|
|
@@ -9,7 +10,7 @@ module OM
|
|
9
10
|
# Converts [{":person"=>"0"}, ":last_name"] to [{:person=>0}, :last_name]
|
10
11
|
def self.destringify(params)
|
11
12
|
case params
|
12
|
-
when String
|
13
|
+
when String
|
13
14
|
if params == "0" || params.to_i != 0
|
14
15
|
result = params.to_i
|
15
16
|
elsif params[0,1] == ":"
|
@@ -18,13 +19,13 @@ module OM
|
|
18
19
|
result = params.to_sym
|
19
20
|
end
|
20
21
|
return result
|
21
|
-
when Hash
|
22
|
+
when Hash
|
22
23
|
result = {}
|
23
24
|
params.each_pair do |k,v|
|
24
25
|
result[ destringify(k) ] = destringify(v)
|
25
26
|
end
|
26
27
|
return result
|
27
|
-
when Array
|
28
|
+
when Array
|
28
29
|
result = []
|
29
30
|
params.each do |x|
|
30
31
|
result << destringify(x)
|
@@ -34,29 +35,31 @@ module OM
|
|
34
35
|
return params
|
35
36
|
end
|
36
37
|
end
|
37
|
-
|
38
|
+
|
38
39
|
# Convert a Term pointer into a flat array without Hashes.
|
39
40
|
# If include_indices is set to false, node indices will be removed.
|
40
41
|
#
|
41
42
|
# @param [Array] pointers array that you would pass into other Accessor methods
|
42
43
|
# @param [Boolean] include_indices (default: true) if set to false, parent indices will be excluded from the array
|
43
44
|
# @example Turn a pointer into a flat array with node indices preserved
|
44
|
-
# OM.pointers_to_flat_array( [{:conference=>0}, {:role=>1}, :text] )
|
45
|
+
# OM.pointers_to_flat_array( [{:conference=>0}, {:role=>1}, :text] )
|
45
46
|
# => [:conference, 0, :role, 1, :text]
|
46
47
|
# @example Remove node indices by setting include_indices to false
|
47
|
-
# OM.pointers_to_flat_array( [{:conference=>0}, {:role=>1}, :text], false )
|
48
|
+
# OM.pointers_to_flat_array( [{:conference=>0}, {:role=>1}, :text], false )
|
48
49
|
# => [:conference, :role, :text]
|
49
50
|
def self.pointers_to_flat_array(pointers, include_indices=true)
|
50
51
|
flat_array = []
|
51
52
|
pointers.each do |pointer|
|
52
53
|
if pointer.kind_of?(Hash)
|
53
54
|
flat_array << pointer.keys.first
|
54
|
-
|
55
|
+
if include_indices
|
56
|
+
flat_array << pointer.values.first
|
57
|
+
end
|
55
58
|
else
|
56
59
|
flat_array << pointer
|
57
60
|
end
|
58
61
|
end
|
59
|
-
flat_array
|
62
|
+
return flat_array
|
60
63
|
end
|
61
64
|
|
62
65
|
def self.version
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class OM::Samples::ModsArticle
|
2
|
-
|
2
|
+
|
3
3
|
include OM::XML::Document
|
4
|
-
|
4
|
+
|
5
5
|
set_terminology do |t|
|
6
6
|
t.root(:path=>"mods", :xmlns=>"http://www.loc.gov/mods/v3", :schema=>"http://www.loc.gov/standards/mods/v3/mods-3-2.xsd", "xmlns:foo"=>"http://my.custom.namespace")
|
7
7
|
|
@@ -11,17 +11,17 @@ class OM::Samples::ModsArticle
|
|
11
11
|
t.main_title_lang(:path=>{:attribute=> "xml:lang"})
|
12
12
|
}
|
13
13
|
t.french_title(:ref=>[:title_info,:main_title], :attributes=>{"xml:lang"=>"fre"})
|
14
|
-
|
14
|
+
|
15
15
|
t.language(:index_as=>[:facetable],:path=>{:attribute=>"lang"})
|
16
|
-
}
|
16
|
+
}
|
17
17
|
t.language{
|
18
18
|
t.lang_code(:index_as=>[:facetable], :path=>"languageTerm", :attributes=>{:type=>"code"})
|
19
19
|
}
|
20
|
-
t.abstract
|
20
|
+
t.abstract
|
21
21
|
t.subject {
|
22
22
|
t.topic(:index_as=>[:facetable])
|
23
|
-
}
|
24
|
-
t.topic_tag(:proxy=>[:subject, :topic])
|
23
|
+
}
|
24
|
+
t.topic_tag(:proxy=>[:subject, :topic])
|
25
25
|
# t.topic_tag(:index_as=>[:facetable],:path=>"subject", :default_content_path=>"topic")
|
26
26
|
# This is a mods:name. The underscore is purely to avoid namespace conflicts.
|
27
27
|
t.name_ {
|
@@ -40,7 +40,7 @@ class OM::Samples::ModsArticle
|
|
40
40
|
t.computing_id
|
41
41
|
t.name_content(:path=>"text()")
|
42
42
|
}
|
43
|
-
# lookup :person, :first_name
|
43
|
+
# lookup :person, :first_name
|
44
44
|
t.person(:ref=>:name, :attributes=>{:type=>"personal"}, :index_as=>[:facetable])
|
45
45
|
t.department(:proxy=>[:person,:description],:index_as=>[:facetable])
|
46
46
|
t.organization(:ref=>:name, :attributes=>{:type=>"corporate"}, :index_as=>[:facetable])
|
@@ -78,7 +78,7 @@ class OM::Samples::ModsArticle
|
|
78
78
|
t.title(:proxy=>[:title_info, :main_title])
|
79
79
|
t.journal_title(:proxy=>[:journal, :title_info, :main_title])
|
80
80
|
end
|
81
|
-
|
81
|
+
|
82
82
|
# Changes from OM::Properties implementation
|
83
83
|
# renamed family_name => last_name
|
84
84
|
# start_page & end_page now accessible as [:journal, :issue, :pages, :start] (etc.)
|
data/lib/om/tree_node.rb
CHANGED
@@ -1,23 +1,23 @@
|
|
1
1
|
module OM::TreeNode
|
2
|
-
|
2
|
+
|
3
3
|
attr_accessor :ancestors
|
4
|
-
|
4
|
+
|
5
5
|
# insert the mapper into the given parent
|
6
6
|
def set_parent(parent_mapper)
|
7
7
|
parent_mapper.children[@name] = self
|
8
8
|
@ancestors << parent_mapper
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
# insert the given mapper into the current mappers children
|
12
12
|
def add_child(child_mapper)
|
13
13
|
child_mapper.ancestors << self
|
14
|
-
@children[child_mapper.name.to_sym] = child_mapper
|
14
|
+
@children[child_mapper.name.to_sym] = child_mapper
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def retrieve_child(child_name)
|
18
18
|
child = @children.fetch(child_name, nil)
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def parent
|
22
22
|
ancestors.last
|
23
23
|
end
|
data/lib/om/version.rb
CHANGED
data/lib/om/xml.rb
CHANGED
@@ -1,56 +1,58 @@
|
|
1
|
-
require
|
2
|
-
require "om/xml/validation"
|
3
|
-
|
4
|
-
require "om/xml/terminology"
|
5
|
-
require "om/xml/term"
|
6
|
-
require "om/xml/term_xpath_generator"
|
7
|
-
require "om/xml/node_generator"
|
8
|
-
require "om/xml/term_value_operators"
|
9
|
-
require "om/xml/named_term_proxy"
|
10
|
-
require "om/xml/document"
|
11
|
-
require "om/xml/dynamic_node"
|
12
|
-
|
13
|
-
require "om/xml/template_registry"
|
14
|
-
|
1
|
+
require 'active_support'
|
15
2
|
module OM::XML
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
autoload :Container
|
5
|
+
autoload :Validation
|
6
|
+
autoload :Terminology
|
7
|
+
autoload :Term
|
8
|
+
autoload :TerminologyBasedSolrizer
|
9
|
+
autoload :TermXpathGenerator
|
10
|
+
autoload :NodeGenerator
|
11
|
+
autoload :TermValueOperators
|
12
|
+
autoload :NamedTermProxy
|
13
|
+
autoload :Document
|
14
|
+
autoload :DynamicNode
|
15
|
+
autoload :TemplateRegistry
|
16
16
|
|
17
17
|
# Raised when the XML document or XML template can't be found during an operation
|
18
18
|
class TemplateMissingException < StandardError; end
|
19
|
-
|
19
|
+
|
20
20
|
attr_accessor :ng_xml
|
21
|
-
|
21
|
+
|
22
22
|
# Module Methods -- These methods can be called directly on the Module itself
|
23
|
-
|
23
|
+
|
24
24
|
# Transforms an array of values into a string delimited by +delimiter+
|
25
25
|
def self.delimited_list( values_array, delimiter=", ")
|
26
26
|
values_array.join(delimiter)
|
27
27
|
end
|
28
|
-
|
29
|
-
# Class Methods -- These methods will be available on classes that include this Module
|
30
|
-
|
28
|
+
|
29
|
+
# Class Methods -- These methods will be available on classes that include this Module
|
30
|
+
|
31
31
|
module ClassMethods
|
32
|
-
|
32
|
+
|
33
33
|
# @pointer accessor or property info pointer
|
34
|
-
#
|
34
|
+
#
|
35
35
|
# ex. [[:person,1],:role] will be converted to [{:person=>1},:role]
|
36
|
-
def sanitize_pointer(pointer)
|
37
|
-
if pointer.kind_of?(Array)
|
36
|
+
def sanitize_pointer(pointer)
|
37
|
+
if pointer.kind_of?(Array)
|
38
38
|
pointer.each do |x|
|
39
|
-
|
39
|
+
if x.kind_of?(Array)
|
40
|
+
pointer[pointer.index(x)] = Hash[x[0],x[1]]
|
41
|
+
end
|
40
42
|
end
|
41
43
|
end
|
42
|
-
pointer
|
44
|
+
return pointer
|
43
45
|
end
|
44
|
-
|
46
|
+
|
45
47
|
end
|
46
|
-
|
48
|
+
|
47
49
|
# Instance Methods -- These methods will be available on instances of classes that include this module
|
48
|
-
|
50
|
+
|
49
51
|
def self.included(klass)
|
50
52
|
klass.extend(ClassMethods)
|
51
53
|
klass.send(:include, OM::XML::Container)
|
52
54
|
klass.send(:include, OM::XML::Validation)
|
53
55
|
end
|
54
|
-
|
55
|
-
|
56
|
+
|
57
|
+
|
56
58
|
end
|
data/lib/om/xml/container.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
module OM::XML::Container
|
2
2
|
extend ActiveSupport::Concern
|
3
|
-
|
3
|
+
|
4
4
|
attr_accessor :ng_xml
|
5
|
-
|
6
|
-
# Class Methods -- These methods will be available on classes that include this Module
|
7
|
-
|
5
|
+
|
6
|
+
# Class Methods -- These methods will be available on classes that include this Module
|
7
|
+
|
8
8
|
module ClassMethods
|
9
|
-
|
9
|
+
|
10
10
|
# @xml String, File or Nokogiri::XML::Node
|
11
11
|
# @tmpl ActiveFedora::MetadataDatastream
|
12
12
|
# Careful! If you call this from a constructor, be sure to provide something 'ie. self' as the @tmpl. Otherwise, you will get an infinite loop!
|
@@ -18,25 +18,25 @@ module OM::XML::Container
|
|
18
18
|
else
|
19
19
|
tmpl.ng_xml = Nokogiri::XML::Document.parse(xml)
|
20
20
|
end
|
21
|
-
tmpl
|
21
|
+
return tmpl
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
# By default, new OM Document instances will create an empty xml document, but if you override self.xml_template to return a different object (e.g. Nokogiri::XML::Document), that will be created instead.
|
25
25
|
# You can make this method create the documents however you want as long as it returns a Nokogiri::XML::Document.
|
26
|
-
# In the tutorials, we use Nokogiri::XML::Builder in this mehtod and call its .doc method at the end of xml_template in order to return the Nokogiri::XML::Document object. Instead of using Nokogiri::XML::Builder, you could put your template into an actual xml file and have xml_template use Nokogiri::XML::Document.parse to load it. That’s up to you.
|
26
|
+
# In the tutorials, we use Nokogiri::XML::Builder in this mehtod and call its .doc method at the end of xml_template in order to return the Nokogiri::XML::Document object. Instead of using Nokogiri::XML::Builder, you could put your template into an actual xml file and have xml_template use Nokogiri::XML::Document.parse to load it. That’s up to you.
|
27
27
|
# @return Nokogiri::XML::Document
|
28
28
|
def xml_template
|
29
29
|
Nokogiri::XML::Document.parse("")
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
def ng_xml
|
35
35
|
@ng_xml ||= self.class.xml_template
|
36
36
|
end
|
37
37
|
|
38
38
|
# Instance Methods -- These methods will be available on instances of classes that include this module
|
39
|
-
|
39
|
+
|
40
40
|
def to_xml(xml = ng_xml)
|
41
41
|
if xml == ng_xml
|
42
42
|
return xml.to_xml
|
@@ -52,5 +52,5 @@ module OM::XML::Container
|
|
52
52
|
raise "You can only pass instances of Nokogiri::XML::Node into this method. You passed in #{xml}"
|
53
53
|
end
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
end
|
data/lib/om/xml/document.rb
CHANGED
@@ -35,7 +35,7 @@ module OM::XML::Document
|
|
35
35
|
self.terminology_builder.extend_terminology(&block)
|
36
36
|
rebuild_terminology!
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
# (Explicitly) inherit terminology from upstream classes
|
40
40
|
def use_terminology klass
|
41
41
|
self.terminology_builder = klass.terminology_builder.dup
|
@@ -69,8 +69,6 @@ module OM::XML::Document
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def ng_xml_will_change!
|
72
|
-
self.dirty = true if self.respond_to?(:dirty=)
|
73
|
-
|
74
72
|
# throw away older version.
|
75
73
|
changed_attributes['ng_xml'] = nil
|
76
74
|
end
|
@@ -79,23 +77,24 @@ module OM::XML::Document
|
|
79
77
|
changed.include?('ng_xml')
|
80
78
|
end
|
81
79
|
|
82
|
-
def method_missing(
|
83
|
-
if matches = /([^=]
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
super
|
91
|
-
end
|
92
|
-
else
|
80
|
+
def method_missing(method_name, *args)
|
81
|
+
if matches = /([^=]+)=$/.match(method_name.to_s)
|
82
|
+
name = matches[1].to_sym
|
83
|
+
end
|
84
|
+
|
85
|
+
name ||= method_name
|
86
|
+
|
87
|
+
begin
|
93
88
|
term = self.class.terminology.retrieve_term(name)
|
94
|
-
|
95
|
-
|
89
|
+
|
90
|
+
if /=$/.match(method_name.to_s)
|
91
|
+
node = OM::XML::DynamicNode.new(name, nil, self, term)
|
92
|
+
return node.val=args
|
96
93
|
else
|
97
|
-
|
94
|
+
return OM::XML::DynamicNode.new(name, args.first, self, term)
|
98
95
|
end
|
96
|
+
rescue OM::XML::Terminology::BadPointerError
|
97
|
+
super
|
99
98
|
end
|
100
99
|
end
|
101
100
|
|
@@ -194,7 +193,9 @@ module OM::XML::Document
|
|
194
193
|
|
195
194
|
private
|
196
195
|
def manipulate_node(method, target, *args, &block)
|
197
|
-
|
196
|
+
if target.is_a?(Array)
|
197
|
+
target = self.find_by_terms(*target)
|
198
|
+
end
|
198
199
|
raise "You must call define_template before calling #{method}" if template_registry.nil?
|
199
200
|
ng_xml_will_change!
|
200
201
|
template_registry.send(method, target, *args, &block)
|