moonrope 1.4.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +9 -0
  3. data/Gemfile.lock +47 -0
  4. data/MIT-LICENCE +20 -0
  5. data/README.md +24 -0
  6. data/bin/moonrope +28 -0
  7. data/docs/authentication.md +114 -0
  8. data/docs/controllers.md +106 -0
  9. data/docs/exceptions.md +27 -0
  10. data/docs/introduction.md +29 -0
  11. data/docs/structures.md +214 -0
  12. data/example/authentication.rb +50 -0
  13. data/example/controllers/meta_controller.rb +14 -0
  14. data/example/controllers/users_controller.rb +92 -0
  15. data/example/structures/pet_structure.rb +12 -0
  16. data/example/structures/user_structure.rb +35 -0
  17. data/html/assets/lock.svg +3 -0
  18. data/html/assets/reset.css +101 -0
  19. data/html/assets/style.css +348 -0
  20. data/html/assets/tool.svg +4 -0
  21. data/html/assets/try.js +151 -0
  22. data/html/authenticators/default.html +191 -0
  23. data/html/controllers/meta/version.html +144 -0
  24. data/html/controllers/meta.html +73 -0
  25. data/html/controllers/users/create.html +341 -0
  26. data/html/controllers/users/list.html +348 -0
  27. data/html/controllers/users/show.html +261 -0
  28. data/html/controllers/users/update.html +387 -0
  29. data/html/controllers/users.html +93 -0
  30. data/html/index.html +166 -0
  31. data/html/moonrope.txt +0 -0
  32. data/html/structures/pet.html +176 -0
  33. data/html/structures/user.html +338 -0
  34. data/lib/moonrope/action.rb +165 -37
  35. data/lib/moonrope/authenticator.rb +39 -0
  36. data/lib/moonrope/base.rb +24 -6
  37. data/lib/moonrope/controller.rb +4 -2
  38. data/lib/moonrope/doc_context.rb +94 -0
  39. data/lib/moonrope/doc_server.rb +123 -0
  40. data/lib/moonrope/dsl/action_dsl.rb +159 -9
  41. data/lib/moonrope/dsl/authenticator_dsl.rb +31 -0
  42. data/lib/moonrope/dsl/base_dsl.rb +21 -18
  43. data/lib/moonrope/dsl/controller_dsl.rb +60 -9
  44. data/lib/moonrope/dsl/filterable_dsl.rb +27 -0
  45. data/lib/moonrope/dsl/structure_dsl.rb +27 -2
  46. data/lib/moonrope/errors.rb +3 -0
  47. data/lib/moonrope/eval_environment.rb +82 -3
  48. data/lib/moonrope/eval_helpers/filter_helper.rb +82 -0
  49. data/lib/moonrope/eval_helpers.rb +28 -5
  50. data/lib/moonrope/guard.rb +35 -0
  51. data/lib/moonrope/html_generator.rb +65 -0
  52. data/lib/moonrope/param_set.rb +11 -1
  53. data/lib/moonrope/rack_middleware.rb +1 -1
  54. data/lib/moonrope/railtie.rb +31 -14
  55. data/lib/moonrope/request.rb +25 -14
  56. data/lib/moonrope/structure.rb +74 -11
  57. data/lib/moonrope/structure_attribute.rb +15 -0
  58. data/lib/moonrope/version.rb +1 -1
  59. data/lib/moonrope.rb +5 -4
  60. data/moonrope.gemspec +21 -0
  61. data/spec/spec_helper.rb +32 -0
  62. data/spec/specs/action_spec.rb +455 -0
  63. data/spec/specs/base_spec.rb +29 -0
  64. data/spec/specs/controller_spec.rb +31 -0
  65. data/spec/specs/param_set_spec.rb +31 -0
  66. data/templates/basic/_action_form.erb +77 -0
  67. data/templates/basic/_errors_table.erb +32 -0
  68. data/templates/basic/_structure_attributes_list.erb +55 -0
  69. data/templates/basic/action.erb +168 -0
  70. data/templates/basic/assets/lock.svg +3 -0
  71. data/templates/basic/assets/reset.css +101 -0
  72. data/templates/basic/assets/style.css +348 -0
  73. data/templates/basic/assets/tool.svg +4 -0
  74. data/templates/basic/assets/try.js +151 -0
  75. data/templates/basic/authenticator.erb +51 -0
  76. data/templates/basic/controller.erb +20 -0
  77. data/templates/basic/index.erb +114 -0
  78. data/templates/basic/layout.erb +46 -0
  79. data/templates/basic/structure.erb +23 -0
  80. data/test/test_helper.rb +81 -0
  81. data/test/tests/action_access_test.rb +63 -0
  82. data/test/tests/actions_test.rb +524 -0
  83. data/test/tests/authenticators_test.rb +87 -0
  84. data/test/tests/base_test.rb +35 -0
  85. data/test/tests/controllers_test.rb +49 -0
  86. data/test/tests/eval_environment_test.rb +136 -0
  87. data/test/tests/evel_helpers_test.rb +60 -0
  88. data/test/tests/examples_test.rb +11 -0
  89. data/test/tests/helpers_test.rb +97 -0
  90. data/test/tests/param_set_test.rb +44 -0
  91. data/test/tests/rack_middleware_test.rb +109 -0
  92. data/test/tests/request_test.rb +232 -0
  93. data/test/tests/structures_param_extensions_test.rb +159 -0
  94. data/test/tests/structures_test.rb +335 -0
  95. metadata +82 -48
@@ -1,7 +1,9 @@
1
+ require 'moonrope/dsl/controller_dsl'
2
+
1
3
  module Moonrope
2
4
  class Controller
3
5
 
4
- attr_accessor :name, :actions, :access, :befores
6
+ attr_accessor :name, :actions, :befores, :friendly_name, :description, :doc, :authenticator, :access_rule, :shared_actions
5
7
  attr_reader :base, :dsl
6
8
 
7
9
  #
@@ -15,7 +17,7 @@ module Moonrope
15
17
  @base = base
16
18
  @name = name
17
19
  @actions = {}
18
- @access = nil
20
+ @shared_actions = {}
19
21
  @befores = []
20
22
  @dsl = Moonrope::DSL::ControllerDSL.new(self)
21
23
  @dsl.instance_eval(&block) if block_given?
@@ -0,0 +1,94 @@
1
+ module Moonrope
2
+ class DocContext
3
+
4
+ attr_reader :vars
5
+
6
+ def initialize(generator, options = {})
7
+ @generator = generator
8
+ @vars = options.delete(:vars) || {}
9
+ @options = options
10
+ end
11
+
12
+ def set_page_title(title)
13
+ @vars[:page_title] = title
14
+ end
15
+
16
+ def set_active_nav(nav)
17
+ @vars[:active_nav] = nav
18
+ end
19
+
20
+ def base
21
+ @generator.base
22
+ end
23
+
24
+ def host
25
+ @generator.host
26
+ end
27
+
28
+ def prefix
29
+ @generator.prefix
30
+ end
31
+
32
+ def version
33
+ @generator.version
34
+ end
35
+
36
+ def method_missing(name)
37
+ if @vars.has_key?(name.to_sym)
38
+ @vars[name.to_sym]
39
+ else
40
+ super
41
+ end
42
+ end
43
+
44
+ def git_version
45
+ ENV["VDT_VERSION"] ||
46
+ (`git rev-parse HEAD`.strip rescue nil)
47
+ end
48
+
49
+ def asset_path(file)
50
+ path("assets/" + file)
51
+ end
52
+
53
+ def full_prefix
54
+ "#{host}/#{prefix}/#{version}"
55
+ end
56
+
57
+ def path(file)
58
+ depth = ((@options[:output_file] || '').split('/').size - 1).times.map { "../" }.join
59
+ if file == :root
60
+ file = depth + (@options[:welcome_file] || "welcome")
61
+ else
62
+ file = depth + file
63
+ end
64
+
65
+ if @options[:html_extensions] && !(file =~ /\.[a-z]+\z/)
66
+ file = "#{file}.html"
67
+ end
68
+
69
+ file
70
+ end
71
+
72
+ def render(template_file)
73
+ ERB.new(File.read(template_file), nil, '-').result(binding)
74
+ end
75
+
76
+ def partial(name, attributes = {})
77
+ erb = self.class.new(@generator, @options.merge(:vars => attributes))
78
+ erb.render(File.join(@generator.template_root_path, "_#{name}.erb"))
79
+ end
80
+
81
+ def friendly_type(type)
82
+ if type.is_a?(Symbol)
83
+ type.to_s.capitalize
84
+ else
85
+ type.to_s
86
+ end
87
+ end
88
+
89
+ def humanize(string)
90
+ string.to_s.gsub(/\_/, ' ')
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,123 @@
1
+ require 'moonrope/doc_context'
2
+
3
+ module Moonrope
4
+ class DocServer
5
+
6
+ CONTENT_TYPES = {
7
+ 'css' => 'text/css',
8
+ 'js' => 'text/javascript',
9
+ 'svg' => 'image/svg+xml'
10
+ }
11
+
12
+ class << self
13
+ #
14
+ # Set the default path regex which should be matched for requests for
15
+ # API docmentation. By default, this is /api/docs/.
16
+ #
17
+ def path_regex
18
+ @path_regex ||= /\A\/#{Moonrope::Request.path_prefix}docs\/([\w\.]+)\/?([\w\/\-\.]+)?/
19
+ end
20
+ attr_writer :path_regex
21
+ end
22
+
23
+ def initialize(app, base, options = {})
24
+ @app = app
25
+ @base = base
26
+ @options = options
27
+ end
28
+
29
+ attr_reader :base
30
+
31
+ class Generator
32
+ def initialize(base, options = {})
33
+ @base = base
34
+ @options = options
35
+ end
36
+
37
+ attr_reader :base
38
+
39
+ def template_root_path
40
+ File.expand_path("../../../templates/basic", __FILE__)
41
+ end
42
+
43
+ def host
44
+ @options[:host]
45
+ end
46
+
47
+ def prefix
48
+ @options[:prefix]
49
+ end
50
+
51
+ def version
52
+ @options[:version]
53
+ end
54
+
55
+ def generate_file(output_file, template_file, variables = {})
56
+ # Generate the page for the requested template with the given variables
57
+ file = DocContext.new(self, :output_file => output_file, :vars => variables)
58
+ file_string = file.render(File.join(template_root_path, "#{template_file}.erb"))
59
+ # Generate the final page within the layout
60
+ DocContext.new(self, :output_file => output_file, :vars => {:page_title => file.vars[:page_title], :active_nav =>file.vars[:active_nav], :body => file_string}).render(File.join(template_root_path, "layout.erb"))
61
+ end
62
+ end
63
+
64
+ def call(env)
65
+ if env['PATH_INFO'] =~ self.class.path_regex
66
+ version = $1
67
+ doc_path = $2
68
+ request = Rack::Request.new(env)
69
+ generator = Generator.new(@base, :host => "#{request.scheme}://#{request.host_with_port}", :version => version, :prefix => env['PATH_INFO'].split('/')[1])
70
+
71
+ if @options[:reload_on_each_request]
72
+ @base.load
73
+ end
74
+
75
+ file = nil
76
+ content_type = nil
77
+
78
+ case doc_path
79
+ when nil, ""
80
+ return [302, {'Location' => "#{env['PATH_INFO']}/welcome"}, ['']]
81
+ when /\Awelcome\z/, /\Aindex\.html\z/
82
+ file = generator.generate_file(doc_path, 'index')
83
+ when /\Acontrollers\/(\w+)(\.html)?\z/
84
+ if controller = @base.controller($1.to_sym)
85
+ file = generator.generate_file(doc_path, 'controller', :controller => controller)
86
+ end
87
+ when /\Acontrollers\/(\w+)\/(\w+)(\.html)?\z/
88
+ if controller = @base.controller($1.to_sym)
89
+ if action = controller.action($2.to_sym)
90
+ file = generator.generate_file(doc_path, 'action', :controller => controller, :action => action)
91
+ end
92
+ end
93
+ when /\Astructures\/(\w+)(\.html)?\z/
94
+ if structure = @base.structure($1.to_sym)
95
+ file = generator.generate_file(doc_path, 'structure', :structure => structure)
96
+ end
97
+ when /\Aauthenticators\/(\w+)(\.html)?\z/
98
+ if authenticator = @base.authenticators[$1.to_sym]
99
+ file = generator.generate_file(doc_path, 'authenticator', :authenticator => authenticator)
100
+ end
101
+ when /\Aassets\/([\w]+)\.([a-z]+)\z/
102
+ path = File.join(generator.template_root_path, 'assets', "#{$1}.#{$2}")
103
+ if File.exist?(path)
104
+ file = File.read(path)
105
+ content_type = CONTENT_TYPES[$2] || 'text/plain'
106
+ end
107
+ end
108
+
109
+ if file
110
+ [200, {
111
+ 'Content-Type' => content_type || 'text/html',
112
+ 'Content-Length' => file.bytesize.to_s},
113
+ [file]]
114
+ else
115
+ [404, {}, ['Not found']]
116
+ end
117
+ else
118
+ return @app.call(env)
119
+ end
120
+ end
121
+
122
+ end
123
+ end
@@ -1,3 +1,6 @@
1
+ require 'moonrope/errors'
2
+ require 'moonrope/dsl/filterable_dsl'
3
+
1
4
  module Moonrope
2
5
  module DSL
3
6
  class ActionDSL
@@ -11,6 +14,18 @@ module Moonrope
11
14
  @action = action
12
15
  end
13
16
 
17
+ #
18
+ # Set the title for the action
19
+ #
20
+ # title "List all users"
21
+ #
22
+ # @param value [String]
23
+ # @return [void]
24
+ #
25
+ def title(value)
26
+ @action.title = value
27
+ end
28
+
14
29
  #
15
30
  # Set the description for the action
16
31
  #
@@ -23,6 +38,14 @@ module Moonrope
23
38
  @action.description = value
24
39
  end
25
40
 
41
+
42
+ #
43
+ # Set this action so that it isn't documented
44
+ #
45
+ def no_doc!
46
+ @action.doc = false
47
+ end
48
+
26
49
  #
27
50
  # Add a new param to the action's param set.
28
51
  #
@@ -33,27 +56,94 @@ module Moonrope
33
56
  # @param options_if_description [Hash] a hash of additional options if a description was provided
34
57
  # @return [void]
35
58
  #
36
- def param(name, description_or_options = {}, options_if_description = {})
59
+ def param(name, description_or_options = {}, options_if_description = {}, &block)
37
60
  if description_or_options.is_a?(String)
38
61
  options = options_if_description.merge(:description => description_or_options)
39
62
  else
40
63
  options = description_or_options
41
64
  end
65
+
66
+ options[:from_structure] ||= @from_structure if @from_structure
67
+
68
+ if structure = options[:from_structure]
69
+ if @action.controller && structure = @action.controller.base.structure(structure)
70
+ if attribute = structure.attribute(name)
71
+ options[:description] ||= attribute.description
72
+ options[:type] ||= attribute.value_type
73
+ end
74
+ end
75
+ end
76
+
77
+ options[:apply] = block if block_given?
78
+ options[:from_shared_action] = @within_shared_action.dup if @within_shared_action
42
79
  @action.params[name] = options
43
80
  end
44
81
 
45
82
  #
46
- # Set the access condition for the action.
83
+ # Specifies that all params within this block should be marked as being from
84
+ # a given structure
47
85
  #
48
- # access do
49
- # auth.is_a?(User)
50
- # end
86
+ #  from_structure :user do
87
+ # param :username
88
+ # end
89
+ #
90
+ # @param name [Symbol] the name of the structure
51
91
  #
52
- # @yield the contents of the yield will be saved as the access condition
92
+ def from_structure(name, &block)
93
+ @from_structure = name
94
+ self.instance_eval(&block)
95
+ ensure
96
+ @from_structure = nil
97
+ end
98
+
99
+ #
100
+ # Add a new error to the actions' errors
101
+ #
102
+ # error "NoUnitFound", "The unit with given {{id}} could not be found"
103
+ #
104
+ # @param name [String] the name of the error
105
+ # @param description [String] a description of the error
53
106
  # @return [void]
54
107
  #
55
- def access(value = nil, &block)
56
- @action.access = block_given? ? block : value
108
+ def error(name, description, options = {})
109
+ @action.errors[name] = options.merge(:description => description, :from_share => @within_share)
110
+ end
111
+
112
+ #
113
+ # Sets the type of return value that is expected from a successful call
114
+ # to this API action.
115
+ #
116
+ # returns :array, :structure => :user
117
+ #
118
+ # @param type [Symbol] the type of object that will be returend
119
+ # @param options [Hash] further options about the returned value
120
+ # @return [void]
121
+ #
122
+ def returns(type, options = {})
123
+ @action.returns = options.merge(:type => type)
124
+ end
125
+
126
+ #
127
+ # Sets the name of the authenticator to use for this action
128
+ #
129
+ # @param name [Symbol] the name of the authenticator
130
+ #
131
+ def authenticator(name)
132
+ @action.authenticator = name
133
+ end
134
+
135
+ #
136
+ # Sets the name of the access rule to use for this action
137
+ #
138
+ # @param name [Symbol] the name of the authenticator
139
+ #
140
+ def access_rule(name)
141
+ if name.is_a?(Hash)
142
+ authenticator name.first[0]
143
+ access_rule name.first[1]
144
+ else
145
+ @action.access_rule = name
146
+ end
57
147
  end
58
148
 
59
149
  #
@@ -67,7 +157,67 @@ module Moonrope
67
157
  # @return [void]
68
158
  #
69
159
  def action(&block)
70
- @action.action = block
160
+ @action.actions << block
161
+ end
162
+
163
+ #
164
+ # Specify that this action will be returning paginated data. Sets up the
165
+ # parameters for the action as appropriate.
166
+ #
167
+ def paginated(options = {})
168
+ @action.traits << :paginated
169
+ param :page, "The page number", :type => Integer, :required => true, :default => options[:page] || 1
170
+ param :per_page, "The number of items to return per page", :type => Integer, :required => true, :default => options[:per_page] || 30
171
+ end
172
+
173
+ #
174
+ # Specify that this action will return data sorted by user provided data.
175
+ #
176
+ def sortable(*fields)
177
+ if fields.empty?
178
+ raise Moonrope::Errors::Error, "You must specify at least one field when calling 'sortable'"
179
+ else
180
+ if fields.first.is_a?(Hash)
181
+ default_order = fields.first.first[1].to_s
182
+ fields[0] = fields.first.first[0]
183
+ else
184
+ default_order = 'asc'
185
+ end
186
+ @action.traits << :sortable
187
+ param :sort_by, "The field to sort by", :type => String, :required => true, :default => fields[0].to_s, :options => fields.map(&:to_s)
188
+ param :order, "The direction to order units by", :type => String, :required => true, :default => default_order, :options => ["asc", "desc"]
189
+ end
190
+ end
191
+
192
+ #
193
+ # Specify that this action will return data which can be filtered by specifying
194
+ # certain parameters on a filter parameter
195
+ #
196
+ def filterable(&block)
197
+ if @action.errors['FilterError'].nil?
198
+ error 'FilterError', "An error has occurred while processing filters for this action", :attributes => {:issue_code => "A more specific issue code", :issue_message => "A more specific message about the issue"}
199
+ end
200
+
201
+ if @action.params[:filters].nil?
202
+ param :filters, "A hash of filters to apply to results", :type => Hash, :default => {}
203
+ end
204
+ dsl = FilterableDSL.new(@action)
205
+ dsl.instance_eval(&block)
206
+ end
207
+
208
+ #
209
+ # Include any block from the controller shares
210
+ #
211
+ def use(name, options = {})
212
+ if block = (@action.controller.shared_actions[name] || @action.controller.base.shared_actions[name])
213
+ @within_shared_action ||= []
214
+ @within_shared_action << name
215
+ self.instance_exec(options, &block)
216
+ else
217
+ raise Moonrope::Errors::InvalidSharedAction, "Invalid share name #{name}"
218
+ end
219
+ ensure
220
+ @within_shared_action.delete(name) if @within_shared_action
71
221
  end
72
222
 
73
223
  end
@@ -0,0 +1,31 @@
1
+ module Moonrope
2
+ module DSL
3
+ class AuthenticatorDSL
4
+
5
+ def initialize(authenticator)
6
+ @authenticator = authenticator
7
+ end
8
+
9
+ def description(value)
10
+ @authenticator.description = value
11
+ end
12
+
13
+ def header(name, description = nil, options = {})
14
+ @authenticator.headers[name] = options.merge(:name => name, :description => description)
15
+ end
16
+
17
+ def error(name, description = nil, options = {})
18
+ @authenticator.errors[name] = options.merge(:name => name, :description => description)
19
+ end
20
+
21
+ def lookup(&block)
22
+ @authenticator.lookup = block
23
+ end
24
+
25
+ def rule(name, error_code, description = nil, &block)
26
+ @authenticator.rules[name] = {:name => name, :error_code => error_code, :description => description, :block => block}
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -1,3 +1,7 @@
1
+ require 'moonrope/structure'
2
+ require 'moonrope/controller'
3
+ require 'moonrope/authenticator'
4
+
1
5
  module Moonrope
2
6
  module DSL
3
7
  class BaseDSL
@@ -47,24 +51,6 @@ module Moonrope
47
51
  controller
48
52
  end
49
53
 
50
- #
51
- # Set the authenticator for the API.
52
- #
53
- # @yield stores the block as the authenticator
54
- #
55
- def authenticator(&block)
56
- @base.authenticator = block
57
- end
58
-
59
- #
60
- # Set the default access check block.
61
- #
62
- # @yield stores the block as the access check
63
- #
64
- def default_access(value = nil, &block)
65
- @base.default_access = block_given? ? block : value
66
- end
67
-
68
54
  #
69
55
  # Define a new helper in the global namespace
70
56
  #
@@ -81,6 +67,23 @@ module Moonrope
81
67
  helper_instance
82
68
  end
83
69
 
70
+ #
71
+ # Define a new authenticator
72
+ #
73
+ def authenticator(name, &block)
74
+ authenticator = Moonrope::Authenticator.new(name)
75
+ dsl = Moonrope::DSL::AuthenticatorDSL.new(authenticator)
76
+ dsl.instance_eval(&block) if block_given?
77
+ @base.authenticators[name] = authenticator
78
+ end
79
+
80
+ #
81
+ # Define a new global shared action
82
+ #
83
+ def shared_action(name, &block)
84
+ @base.shared_actions[name] = block
85
+ end
86
+
84
87
  end
85
88
  end
86
89
  end
@@ -1,3 +1,6 @@
1
+ require 'moonrope/action'
2
+ require 'moonrope/before_action'
3
+
1
4
  module Moonrope
2
5
  module DSL
3
6
  class ControllerDSL
@@ -14,6 +17,31 @@ module Moonrope
14
17
  # @return [Moonrope::Controller] the associated controller
15
18
  attr_reader :controller
16
19
 
20
+ #
21
+ # Stop this controller frmo being documented
22
+ #
23
+ def no_doc!
24
+ @controller.doc = false
25
+ end
26
+
27
+ #
28
+ # Set the friendly name for the controller
29
+ #
30
+ # @param name [String]
31
+ #
32
+ def friendly_name(string)
33
+ @controller.friendly_name = string
34
+ end
35
+
36
+ #
37
+ # Set the description for the controller
38
+ #
39
+ # @param description [String]
40
+ #
41
+ def description(description)
42
+ @controller.description = description
43
+ end
44
+
17
45
  #
18
46
  # Defines a new action within the controller.
19
47
  #
@@ -28,6 +56,29 @@ module Moonrope
28
56
  action
29
57
  end
30
58
 
59
+ #
60
+ # Set the name of the authenticator to use for all actions in this controller
61
+ #
62
+ # @param name [Symbol]
63
+ #
64
+ def authenticator(name)
65
+ @controller.authenticator = name
66
+ end
67
+
68
+ #
69
+ # Set the name of the access rule to use for all actions in this controller
70
+ #
71
+ # @param name [Symbol]
72
+ #
73
+ def access_rule(name)
74
+ if name.is_a?(Hash)
75
+ authenticator name.first[0]
76
+ access_rule name.first[1]
77
+ else
78
+ @controller.access_rule = name
79
+ end
80
+ end
81
+
31
82
  #
32
83
  # Defines a new before action within the controller.
33
84
  #
@@ -43,14 +94,6 @@ module Moonrope
43
94
  before_action
44
95
  end
45
96
 
46
- #
47
- # Defines the access required for controller methods which do not
48
- # define their own access.
49
- #
50
- def access(value = nil, &block)
51
- @controller.access = block_given? ? block : value
52
- end
53
-
54
97
  #
55
98
  # Defines a new helper for this controller.
56
99
  #
@@ -61,9 +104,17 @@ module Moonrope
61
104
  if @controller.base.helper(name, @controller)
62
105
  raise Moonrope::Errors::HelperAlreadyDefined, "Helper has already been defined with name `#{name}`"
63
106
  end
64
-
65
107
  @controller.base.helpers << Moonrope::Helper.new(name, @controller, options, &block)
66
108
  end
109
+
110
+ #
111
+ # Define a shared action which can be used by any action
112
+ #
113
+ # @param name[Symbol] the name of the shared action
114
+ #
115
+ def shared_action(name, &block)
116
+ @controller.shared_actions[name] = block
117
+ end
67
118
  end
68
119
  end
69
120
  end
@@ -0,0 +1,27 @@
1
+ module Moonrope
2
+ module DSL
3
+ class FilterableDSL
4
+
5
+ def initialize(action)
6
+ @action = action
7
+ end
8
+
9
+ def attribute(name, options = {}, &block)
10
+ if options[:type] == Integer || options[:type] == Float
11
+ # Numbers
12
+ options[:operators] ||= [:eq, :not_eq, :gt, :gte, :lt, :lte, :in, :not_in]
13
+ elsif options[:type] == String
14
+ # Strings
15
+ options[:operators] ||= [:eq, :not_eq, :starts_with, :ends_with, :in, :not_in]
16
+ elsif options[:type] == :timestamp
17
+ # Times
18
+ options[:operators] ||= [:eq, :not_eq, :gt, :gte, :lt, :lte]
19
+ else
20
+ # Everything else
21
+ options[:operators] ||= [:eq, :not_eq]
22
+ end
23
+ @action.filters[name] = options.merge(:name => name, :block => block)
24
+ end
25
+ end
26
+ end
27
+ end