moonrope 1.4.1 → 2.0.0

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.
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