liquor 0.1.1 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -9
- data/Gemfile +7 -0
- data/Guardfile +11 -0
- data/MIT-LICENSE +6 -2
- data/README.md +4 -122
- data/Rakefile +20 -23
- data/doc/language-spec.html +768 -0
- data/doc/language-spec.md +698 -0
- data/lib/liquor.rb +39 -68
- data/lib/liquor/ast_tools.rb +28 -0
- data/lib/liquor/compiler.rb +110 -0
- data/lib/liquor/context.rb +76 -254
- data/lib/liquor/diagnostics.rb +151 -0
- data/lib/liquor/drop/drop.rb +168 -0
- data/lib/liquor/drop/drop_delegation.rb +24 -0
- data/lib/liquor/drop/drop_scope.rb +118 -0
- data/lib/liquor/drop/dropable.rb +17 -0
- data/lib/liquor/emitter.rb +313 -0
- data/lib/liquor/extensions/kaminari.rb +14 -0
- data/lib/liquor/extensions/pagination.rb +235 -0
- data/lib/liquor/extensions/rails.rb +97 -0
- data/lib/liquor/extensions/thinking_sphinx.rb +14 -0
- data/lib/liquor/extensions/tire.rb +30 -0
- data/lib/liquor/external.rb +79 -0
- data/lib/liquor/function.rb +94 -0
- data/lib/liquor/grammar/lexer.rb +1223 -0
- data/lib/liquor/grammar/lexer.rl +297 -0
- data/lib/liquor/grammar/parser.racc +288 -0
- data/lib/liquor/grammar/parser.rb +885 -0
- data/lib/liquor/library.rb +41 -0
- data/lib/liquor/manager.rb +146 -0
- data/lib/liquor/runtime.rb +167 -0
- data/lib/liquor/stdlib/builtin_functions.rb +315 -0
- data/lib/liquor/stdlib/builtin_tags.rb +228 -0
- data/lib/liquor/stdlib/html_truncater.rb +162 -0
- data/lib/liquor/stdlib/partial_tags.rb +76 -0
- data/lib/liquor/tag.rb +83 -14
- data/lib/liquor/version.rb +1 -1
- data/liquor.gemspec +29 -6
- data/spec/builtins_spec.rb +264 -0
- data/spec/compiler_spec.rb +136 -0
- data/spec/context_spec.rb +49 -0
- data/spec/drop_delegation_spec.rb +21 -0
- data/spec/drop_spec.rb +222 -0
- data/spec/errors_spec.rb +40 -0
- data/spec/external_spec.rb +207 -0
- data/spec/function_spec.rb +80 -0
- data/spec/lexer_spec.rb +173 -0
- data/spec/library_spec.rb +18 -0
- data/spec/manager_spec.rb +84 -0
- data/spec/parser_spec.rb +381 -0
- data/spec/partials_spec.rb +74 -0
- data/spec/runtime_spec.rb +97 -0
- data/spec/spec_helper.rb +94 -0
- data/spec/tag_spec.rb +7 -0
- metadata +216 -173
- data/AUTHORS +0 -2
- data/CHANGELOG +0 -48
- data/Gemfile.lock +0 -91
- data/History.txt +0 -44
- data/LICENSE +0 -23
- data/example/server/example_servlet.rb +0 -37
- data/example/server/liquid_servlet.rb +0 -28
- data/example/server/liquor_servlet.rb +0 -28
- data/example/server/server.rb +0 -12
- data/example/server/templates/index.liquid +0 -6
- data/example/server/templates/index.liquor +0 -6
- data/example/server/templates/products.liquid +0 -45
- data/example/server/templates/products.liquor +0 -45
- data/init.rb +0 -8
- data/lib/extras/liquid_view.rb +0 -51
- data/lib/extras/liquor_view.rb +0 -51
- data/lib/liquor/block.rb +0 -101
- data/lib/liquor/condition.rb +0 -120
- data/lib/liquor/document.rb +0 -17
- data/lib/liquor/drop.rb +0 -256
- data/lib/liquor/errors.rb +0 -11
- data/lib/liquor/extensions.rb +0 -72
- data/lib/liquor/file_system.rb +0 -62
- data/lib/liquor/htmltags.rb +0 -74
- data/lib/liquor/module_ex.rb +0 -60
- data/lib/liquor/standardfilters.rb +0 -315
- data/lib/liquor/strainer.rb +0 -58
- data/lib/liquor/tags/assign.rb +0 -33
- data/lib/liquor/tags/capture.rb +0 -35
- data/lib/liquor/tags/case.rb +0 -83
- data/lib/liquor/tags/comment.rb +0 -9
- data/lib/liquor/tags/content_for.rb +0 -54
- data/lib/liquor/tags/cycle.rb +0 -59
- data/lib/liquor/tags/for.rb +0 -136
- data/lib/liquor/tags/if.rb +0 -80
- data/lib/liquor/tags/ifchanged.rb +0 -20
- data/lib/liquor/tags/include.rb +0 -56
- data/lib/liquor/tags/unless.rb +0 -33
- data/lib/liquor/tags/yield.rb +0 -49
- data/lib/liquor/template.rb +0 -181
- data/lib/liquor/variable.rb +0 -52
- data/performance/shopify.rb +0 -92
- data/performance/shopify/comment_form.rb +0 -33
- data/performance/shopify/database.rb +0 -45
- data/performance/shopify/json_filter.rb +0 -7
- data/performance/shopify/liquid.rb +0 -18
- data/performance/shopify/liquor.rb +0 -18
- data/performance/shopify/money_filter.rb +0 -18
- data/performance/shopify/paginate.rb +0 -93
- data/performance/shopify/shop_filter.rb +0 -98
- data/performance/shopify/tag_filter.rb +0 -25
- data/performance/shopify/vision.database.yml +0 -945
- data/performance/shopify/weight_filter.rb +0 -11
- data/performance/tests/dropify/article.liquid +0 -74
- data/performance/tests/dropify/blog.liquid +0 -33
- data/performance/tests/dropify/cart.liquid +0 -66
- data/performance/tests/dropify/collection.liquid +0 -22
- data/performance/tests/dropify/index.liquid +0 -47
- data/performance/tests/dropify/page.liquid +0 -8
- data/performance/tests/dropify/product.liquid +0 -68
- data/performance/tests/dropify/theme.liquid +0 -105
- data/performance/tests/ripen/article.liquid +0 -74
- data/performance/tests/ripen/blog.liquid +0 -13
- data/performance/tests/ripen/cart.liquid +0 -54
- data/performance/tests/ripen/collection.liquid +0 -29
- data/performance/tests/ripen/index.liquid +0 -32
- data/performance/tests/ripen/page.liquid +0 -4
- data/performance/tests/ripen/product.liquid +0 -75
- data/performance/tests/ripen/theme.liquid +0 -85
- data/performance/tests/tribble/404.liquid +0 -56
- data/performance/tests/tribble/article.liquid +0 -98
- data/performance/tests/tribble/blog.liquid +0 -41
- data/performance/tests/tribble/cart.liquid +0 -134
- data/performance/tests/tribble/collection.liquid +0 -70
- data/performance/tests/tribble/index.liquid +0 -94
- data/performance/tests/tribble/page.liquid +0 -56
- data/performance/tests/tribble/product.liquid +0 -116
- data/performance/tests/tribble/search.liquid +0 -51
- data/performance/tests/tribble/theme.liquid +0 -90
- data/performance/tests/vogue/article.liquid +0 -66
- data/performance/tests/vogue/blog.liquid +0 -32
- data/performance/tests/vogue/cart.liquid +0 -58
- data/performance/tests/vogue/collection.liquid +0 -19
- data/performance/tests/vogue/index.liquid +0 -22
- data/performance/tests/vogue/page.liquid +0 -3
- data/performance/tests/vogue/product.liquid +0 -62
- data/performance/tests/vogue/theme.liquid +0 -122
- data/test/assign_test.rb +0 -11
- data/test/block_test.rb +0 -58
- data/test/capture_test.rb +0 -41
- data/test/condition_test.rb +0 -115
- data/test/content_for_test.rb +0 -15
- data/test/context_test.rb +0 -479
- data/test/drop_test.rb +0 -162
- data/test/error_handling_test.rb +0 -89
- data/test/extra/breakpoint.rb +0 -547
- data/test/extra/caller.rb +0 -80
- data/test/file_system_test.rb +0 -30
- data/test/filter_test.rb +0 -147
- data/test/helper.rb +0 -24
- data/test/html_tag_test.rb +0 -31
- data/test/if_else_test.rb +0 -139
- data/test/include_tag_test.rb +0 -129
- data/test/module_ex_test.rb +0 -89
- data/test/output_test.rb +0 -121
- data/test/parsing_quirks_test.rb +0 -54
- data/test/regexp_test.rb +0 -45
- data/test/security_test.rb +0 -41
- data/test/standard_filter_test.rb +0 -170
- data/test/standard_tag_test.rb +0 -405
- data/test/statements_test.rb +0 -137
- data/test/strainer_test.rb +0 -27
- data/test/template_test.rb +0 -82
- data/test/test_helper.rb +0 -28
- data/test/unless_else_test.rb +0 -27
- data/test/variable_test.rb +0 -173
- data/test/yield_test.rb +0 -24
data/lib/extras/liquor_view.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
# LiquorView is a action view extension class. You can register it with rails
|
2
|
-
# and use liquor as an template system for .liquor files
|
3
|
-
#
|
4
|
-
# Example
|
5
|
-
#
|
6
|
-
# ActionView::Base::register_template_handler :liquor, LiquorView
|
7
|
-
class LiquorView
|
8
|
-
PROTECTED_ASSIGNS = %w( template_root response _session template_class action_name request_origin session template
|
9
|
-
_response url _request _cookies variables_added _flash params _headers request cookies
|
10
|
-
ignore_missing_templates flash _params logger before_filter_chain_aborted headers )
|
11
|
-
PROTECTED_INSTANCE_VARIABLES = %w( @_request @controller @_first_render @_memoized__pick_template @view_paths
|
12
|
-
@helpers @assigns_added @template @_render_stack @template_format @assigns )
|
13
|
-
|
14
|
-
def self.call(template)
|
15
|
-
"LiquorView.new(self).render(template, local_assigns)"
|
16
|
-
end
|
17
|
-
|
18
|
-
def initialize(view)
|
19
|
-
@view = view
|
20
|
-
end
|
21
|
-
|
22
|
-
def render(template, local_assigns = nil)
|
23
|
-
@view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
|
24
|
-
|
25
|
-
# Rails 2.2 Template has source, but not locals
|
26
|
-
if template.respond_to?(:source) && !template.respond_to?(:locals)
|
27
|
-
assigns = (@view.instance_variables - PROTECTED_INSTANCE_VARIABLES).inject({}) do |hash, ivar|
|
28
|
-
hash[ivar[1..-1]] = @view.instance_variable_get(ivar)
|
29
|
-
hash
|
30
|
-
end
|
31
|
-
else
|
32
|
-
assigns = @view.assigns.reject{ |k,v| PROTECTED_ASSIGNS.include?(k) }
|
33
|
-
end
|
34
|
-
|
35
|
-
source = template.respond_to?(:source) ? template.source : template
|
36
|
-
local_assigns = (template.respond_to?(:locals) ? template.locals : local_assigns) || {}
|
37
|
-
|
38
|
-
if content_for_layout = @view.instance_variable_get("@content_for_layout")
|
39
|
-
assigns['content_for_layout'] = content_for_layout
|
40
|
-
end
|
41
|
-
assigns.merge!(local_assigns.stringify_keys)
|
42
|
-
|
43
|
-
liquor = Liquor::Template.parse(source)
|
44
|
-
liquor.render(assigns, :filters => [@view.controller.master_helper_module], :registers => {:action_view => @view, :controller => @view.controller})
|
45
|
-
end
|
46
|
-
|
47
|
-
def compilable?
|
48
|
-
false
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
data/lib/liquor/block.rb
DELETED
@@ -1,101 +0,0 @@
|
|
1
|
-
module Liquor
|
2
|
-
|
3
|
-
class Block < Tag
|
4
|
-
IsTag = /^#{TagStart}/
|
5
|
-
IsVariable = /^#{VariableStart}/
|
6
|
-
FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/
|
7
|
-
ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/
|
8
|
-
|
9
|
-
def parse(tokens)
|
10
|
-
@nodelist ||= []
|
11
|
-
@nodelist.clear
|
12
|
-
|
13
|
-
while token = tokens.shift
|
14
|
-
|
15
|
-
case token
|
16
|
-
when IsTag
|
17
|
-
if token =~ FullToken
|
18
|
-
|
19
|
-
# if we found the proper block delimitor just end parsing here and let the outer block
|
20
|
-
# proceed
|
21
|
-
if block_delimiter == $1
|
22
|
-
end_tag
|
23
|
-
return
|
24
|
-
end
|
25
|
-
|
26
|
-
# fetch the tag from registered blocks
|
27
|
-
if tag = Template.tags[$1]
|
28
|
-
@nodelist << tag.new($1, $2, tokens)
|
29
|
-
else
|
30
|
-
# this tag is not registered with the system
|
31
|
-
# pass it to the current block for special handling or error reporting
|
32
|
-
unknown_tag($1, $2, tokens)
|
33
|
-
end
|
34
|
-
else
|
35
|
-
raise SyntaxError, "Tag '#{token}' was not properly terminated with regexp: #{TagEnd.inspect} "
|
36
|
-
end
|
37
|
-
when IsVariable
|
38
|
-
@nodelist << create_variable(token)
|
39
|
-
when ''
|
40
|
-
# pass
|
41
|
-
else
|
42
|
-
@nodelist << token
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
# Make sure that its ok to end parsing in the current block.
|
47
|
-
# Effectively this method will throw and exception unless the current block is
|
48
|
-
# of type Document
|
49
|
-
assert_missing_delimitation!
|
50
|
-
end
|
51
|
-
|
52
|
-
def end_tag
|
53
|
-
end
|
54
|
-
|
55
|
-
def unknown_tag(tag, params, tokens)
|
56
|
-
case tag
|
57
|
-
when 'else'
|
58
|
-
raise SyntaxError, "#{block_name} tag does not expect else tag"
|
59
|
-
when 'end'
|
60
|
-
raise SyntaxError, "'end' is not a valid delimiter for #{block_name} tags. use #{block_delimiter}"
|
61
|
-
else
|
62
|
-
raise SyntaxError, "Unknown tag '#{tag}'"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def block_delimiter
|
67
|
-
"end#{block_name}"
|
68
|
-
end
|
69
|
-
|
70
|
-
def block_name
|
71
|
-
@tag_name
|
72
|
-
end
|
73
|
-
|
74
|
-
def create_variable(token)
|
75
|
-
token.scan(ContentOfVariable) do |content|
|
76
|
-
return Variable.new(content.first)
|
77
|
-
end
|
78
|
-
raise SyntaxError.new("Variable '#{token}' was not properly terminated with regexp: #{VariableEnd.inspect} ")
|
79
|
-
end
|
80
|
-
|
81
|
-
def render(context)
|
82
|
-
render_all(@nodelist, context)
|
83
|
-
end
|
84
|
-
|
85
|
-
protected
|
86
|
-
|
87
|
-
def assert_missing_delimitation!
|
88
|
-
raise SyntaxError.new("#{block_name} tag was never closed")
|
89
|
-
end
|
90
|
-
|
91
|
-
def render_all(list, context)
|
92
|
-
list.collect do |token|
|
93
|
-
begin
|
94
|
-
token.respond_to?(:render) ? token.render(context) : token
|
95
|
-
rescue Exception => e
|
96
|
-
context.handle_error(e)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
data/lib/liquor/condition.rb
DELETED
@@ -1,120 +0,0 @@
|
|
1
|
-
module Liquor
|
2
|
-
# Container for liquor nodes which conveniently wraps decision making logic
|
3
|
-
#
|
4
|
-
# Example:
|
5
|
-
#
|
6
|
-
# c = Condition.new('1', '==', '1')
|
7
|
-
# c.evaluate #=> true
|
8
|
-
#
|
9
|
-
class Condition #:nodoc:
|
10
|
-
@@operators = {
|
11
|
-
'==' => lambda { |cond, left, right| cond.send(:equal_variables, left, right) },
|
12
|
-
'!=' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
13
|
-
'<>' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
14
|
-
'<' => :<,
|
15
|
-
'>' => :>,
|
16
|
-
'>=' => :>=,
|
17
|
-
'<=' => :<=,
|
18
|
-
'contains' => lambda { |cond, left, right| left && right ? left.include?(right) : false }
|
19
|
-
}
|
20
|
-
|
21
|
-
def self.operators
|
22
|
-
@@operators
|
23
|
-
end
|
24
|
-
|
25
|
-
attr_reader :attachment
|
26
|
-
attr_accessor :left, :operator, :right
|
27
|
-
|
28
|
-
def initialize(left = nil, operator = nil, right = nil)
|
29
|
-
@left, @operator, @right = left, operator, right
|
30
|
-
@child_relation = nil
|
31
|
-
@child_condition = nil
|
32
|
-
end
|
33
|
-
|
34
|
-
def evaluate(context = Context.new)
|
35
|
-
result = interpret_condition(left, right, operator, context)
|
36
|
-
|
37
|
-
case @child_relation
|
38
|
-
when :or
|
39
|
-
result || @child_condition.evaluate(context)
|
40
|
-
when :and
|
41
|
-
result && @child_condition.evaluate(context)
|
42
|
-
else
|
43
|
-
result
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def or(condition)
|
48
|
-
@child_relation, @child_condition = :or, condition
|
49
|
-
end
|
50
|
-
|
51
|
-
def and(condition)
|
52
|
-
@child_relation, @child_condition = :and, condition
|
53
|
-
end
|
54
|
-
|
55
|
-
def attach(attachment)
|
56
|
-
@attachment = attachment
|
57
|
-
end
|
58
|
-
|
59
|
-
def else?
|
60
|
-
false
|
61
|
-
end
|
62
|
-
|
63
|
-
def inspect
|
64
|
-
"#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
|
65
|
-
end
|
66
|
-
|
67
|
-
private
|
68
|
-
|
69
|
-
def equal_variables(left, right)
|
70
|
-
if left.is_a?(Symbol)
|
71
|
-
if right.respond_to?(left)
|
72
|
-
return right.send(left.to_s)
|
73
|
-
else
|
74
|
-
return nil
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
if right.is_a?(Symbol)
|
79
|
-
if left.respond_to?(right)
|
80
|
-
return left.send(right.to_s)
|
81
|
-
else
|
82
|
-
return nil
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
left == right
|
87
|
-
end
|
88
|
-
|
89
|
-
def interpret_condition(left, right, op, context)
|
90
|
-
# If the operator is empty this means that the decision statement is just
|
91
|
-
# a single variable. We can just poll this variable from the context and
|
92
|
-
# return this as the result.
|
93
|
-
return context[left] if op == nil
|
94
|
-
|
95
|
-
left, right = context[left], context[right]
|
96
|
-
|
97
|
-
operation = self.class.operators[op] || raise(ArgumentError.new("Unknown operator #{op}"))
|
98
|
-
|
99
|
-
if operation.respond_to?(:call)
|
100
|
-
operation.call(self, left, right)
|
101
|
-
elsif left.respond_to?(operation) and right.respond_to?(operation)
|
102
|
-
left.send(operation, right)
|
103
|
-
else
|
104
|
-
nil
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
|
110
|
-
class ElseCondition < Condition
|
111
|
-
def else?
|
112
|
-
true
|
113
|
-
end
|
114
|
-
|
115
|
-
def evaluate(context)
|
116
|
-
true
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
end
|
data/lib/liquor/document.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
module Liquor
|
2
|
-
class Document < Block
|
3
|
-
# we don't need markup to open this block
|
4
|
-
def initialize(tokens)
|
5
|
-
parse(tokens)
|
6
|
-
end
|
7
|
-
|
8
|
-
# There isn't a real delimter
|
9
|
-
def block_delimiter
|
10
|
-
[]
|
11
|
-
end
|
12
|
-
|
13
|
-
# Document blocks don't need to be terminated since they are not actually opened
|
14
|
-
def assert_missing_delimitation!
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
data/lib/liquor/drop.rb
DELETED
@@ -1,256 +0,0 @@
|
|
1
|
-
module Liquor
|
2
|
-
|
3
|
-
# A drop in liquor is a class which allows you to to export DOM like things to liquor
|
4
|
-
# Methods of drops are callable.
|
5
|
-
# The main use for liquor drops is the implement lazy loaded objects.
|
6
|
-
# If you would like to make data available to the web designers which you don't want loaded unless needed then
|
7
|
-
# a drop is a great way to do that
|
8
|
-
#
|
9
|
-
# Example:
|
10
|
-
#
|
11
|
-
# class ProductDrop < Liquor::Drop
|
12
|
-
# def top_sales
|
13
|
-
# Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
|
14
|
-
# end
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# tmpl = Liquor::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} ' )
|
18
|
-
# tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query.
|
19
|
-
#
|
20
|
-
# Your drop can either implement the methods sans any parameters or implement the before_method(name) method which is a
|
21
|
-
# catch all
|
22
|
-
class Drop
|
23
|
-
attr_writer :context
|
24
|
-
|
25
|
-
class_attribute :liquor_attributes
|
26
|
-
self.liquor_attributes = []
|
27
|
-
|
28
|
-
class_attribute :liquor_scopes
|
29
|
-
self.liquor_scopes = []
|
30
|
-
attr_reader :source
|
31
|
-
|
32
|
-
class_attribute :has_manies
|
33
|
-
self.has_manies = []
|
34
|
-
|
35
|
-
def logger
|
36
|
-
Rails.logger
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.allow_all_methods(allow = true)
|
40
|
-
if allow
|
41
|
-
class_eval <<-"END"
|
42
|
-
def before_method(method)
|
43
|
-
@source.send(method.to_sym)
|
44
|
-
end
|
45
|
-
|
46
|
-
def respond_to?(val)
|
47
|
-
return true if @source.respond_to?(val.to_sym)
|
48
|
-
return true if liquor_attributes.include? val.to_sym
|
49
|
-
super
|
50
|
-
end
|
51
|
-
END
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def self.has_one(name)
|
56
|
-
self.instance_eval do
|
57
|
-
define_method(name) do
|
58
|
-
(@source.send(name)).to_liquor
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def self.belongs_to(name)
|
64
|
-
self.instance_eval do
|
65
|
-
define_method(name) do
|
66
|
-
(@source.send(name)).to_liquor
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def respond_to?(val)
|
72
|
-
return true if liquor_attributes.include? val.to_sym
|
73
|
-
super
|
74
|
-
end
|
75
|
-
|
76
|
-
def self.has_many(name, options={})
|
77
|
-
options = options.reverse_merge({ :class_name => name.to_s.classify + "Drop", :sort_scope => :recent, :source_class_name => name.to_s.classify })
|
78
|
-
|
79
|
-
klass = options[:class_name].constantize
|
80
|
-
source_klass = options[:source_class_name].constantize
|
81
|
-
|
82
|
-
self.class_eval do
|
83
|
-
self.has_manies << name
|
84
|
-
|
85
|
-
define_method name do
|
86
|
-
proxy = DropProxy.new(self, name, klass)
|
87
|
-
proxy = proxy.send(options[:with_scope]) if options[:with_scope].present? && source_klass.respond_to?(options[:with_scope])
|
88
|
-
proxy = proxy.send(options[:sort_scope]) if options[:sort_scope].present? && source_klass.respond_to?(options[:sort_scope])
|
89
|
-
proxy
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def initialize(source = nil)
|
95
|
-
@source = source
|
96
|
-
@liquor = liquor_attributes.inject({}) { |h, k| h.update k.to_s => @source.send(k) }
|
97
|
-
end
|
98
|
-
|
99
|
-
def drop_name
|
100
|
-
self.class.name
|
101
|
-
end
|
102
|
-
|
103
|
-
def source_id
|
104
|
-
@source.id
|
105
|
-
end
|
106
|
-
|
107
|
-
def has_scope?(meth)
|
108
|
-
liquor_scopes.include?(meth.to_sym)
|
109
|
-
end
|
110
|
-
|
111
|
-
def has_many?(meth)
|
112
|
-
has_manies.include?(meth.to_sym)
|
113
|
-
end
|
114
|
-
|
115
|
-
# Catch all for the method
|
116
|
-
def before_method(method)
|
117
|
-
@liquor[method.to_s]
|
118
|
-
end
|
119
|
-
|
120
|
-
def ==(comparison_object)
|
121
|
-
our_self = self.source ? self.source : source
|
122
|
-
our_self == (comparison_object.is_a?(self.class) ? comparison_object.source : comparison_object)
|
123
|
-
end
|
124
|
-
|
125
|
-
# called by liquor to invoke a drop
|
126
|
-
def invoke_drop(method)
|
127
|
-
# for backward compatibility with Ruby 1.8
|
128
|
-
methods = self.class.public_instance_methods.map { |m| m.to_s }
|
129
|
-
if methods.include?(method.to_s)
|
130
|
-
send(method.to_sym)
|
131
|
-
else
|
132
|
-
before_method(method)
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def has_key?(name)
|
137
|
-
true
|
138
|
-
end
|
139
|
-
|
140
|
-
def to_liquor
|
141
|
-
self
|
142
|
-
end
|
143
|
-
|
144
|
-
def as_json(options = {})
|
145
|
-
options = options.duplicable? ? options.dup : {}
|
146
|
-
|
147
|
-
only = liquor_attributes
|
148
|
-
|
149
|
-
if options[:include].present?
|
150
|
-
klass_name = options[:include].to_s.singularize.classify + "Drop"
|
151
|
-
only += klass_name.constantize.liquor_attributes
|
152
|
-
end
|
153
|
-
|
154
|
-
options = options.reverse_merge(:only => only)
|
155
|
-
@source.as_json(options)
|
156
|
-
end
|
157
|
-
|
158
|
-
alias :[] :invoke_drop
|
159
|
-
|
160
|
-
# converts an array of records to an array of liquor drops, and assigns the given context to each of them
|
161
|
-
def self.liquify(current_context, *records, &block)
|
162
|
-
i = -1
|
163
|
-
records =
|
164
|
-
records.inject [] do |all, r|
|
165
|
-
i+=1
|
166
|
-
attrs = (block && block.arity == 1) ? [r] : [r, i]
|
167
|
-
all << (block ? block.call(*attrs) : r.to_liquor)
|
168
|
-
all.last.context = current_context if all.last.is_a?(Drop)
|
169
|
-
all
|
170
|
-
end
|
171
|
-
records.compact!
|
172
|
-
records
|
173
|
-
end
|
174
|
-
|
175
|
-
protected
|
176
|
-
def liquify(*records, &block)
|
177
|
-
self.class.liquify(@context, *records, &block)
|
178
|
-
end
|
179
|
-
|
180
|
-
class DropProxy
|
181
|
-
attr_reader :current, :parent
|
182
|
-
|
183
|
-
def initialize(parent, name, klass)
|
184
|
-
@parent = parent
|
185
|
-
@klass = klass
|
186
|
-
|
187
|
-
if @parent.class == DropProxy
|
188
|
-
@current = @parent.current
|
189
|
-
elsif name.present?
|
190
|
-
@current = @parent.source.send name
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def liquify(*collection)
|
195
|
-
@parent.send :liquify, *collection
|
196
|
-
end
|
197
|
-
|
198
|
-
def self.liquor_scopes
|
199
|
-
@parent.class.liquor_scopes
|
200
|
-
end
|
201
|
-
|
202
|
-
#
|
203
|
-
# It is very important that we do not delegate the 'to_liquor' method and it returns 'self'.
|
204
|
-
NON_DELEGATE_METHODS = ['class', 'send', 'to_liquor', 'respond_to?']
|
205
|
-
[].methods.each do |m|
|
206
|
-
unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s)
|
207
|
-
delegate m, :to => :proxy_found
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
def to_liquor
|
212
|
-
self
|
213
|
-
end
|
214
|
-
|
215
|
-
def proxy_found
|
216
|
-
@collection ||= get_collection
|
217
|
-
@parent.send :liquify, *@collection
|
218
|
-
end
|
219
|
-
|
220
|
-
def has_scope?(meth)
|
221
|
-
@klass.liquor_scopes.include?(meth.to_sym)
|
222
|
-
end
|
223
|
-
|
224
|
-
private
|
225
|
-
def method_missing(meth, *args)
|
226
|
-
if has_scope?(meth)
|
227
|
-
converted_args = args.collect{|arg|
|
228
|
-
if arg.is_a? DropProxy
|
229
|
-
arg.current
|
230
|
-
elsif arg.is_a?(Drop)
|
231
|
-
arg.source
|
232
|
-
else
|
233
|
-
if arg.is_a?(Array)
|
234
|
-
arg = arg.collect{|x| x.source }
|
235
|
-
end
|
236
|
-
arg
|
237
|
-
end
|
238
|
-
}
|
239
|
-
@current = @current.send(meth, *converted_args)
|
240
|
-
DropProxy.new(self, meth, @klass)
|
241
|
-
else
|
242
|
-
super
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
def get_collection
|
247
|
-
@collection = @current
|
248
|
-
end
|
249
|
-
|
250
|
-
# HACK
|
251
|
-
def set_current(val)
|
252
|
-
@current = val
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
256
|
-
end
|