Class: Flipper::UI::Action

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/flipper/ui/action.rb

Defined Under Namespace

Modules: FeatureNameFromRoute Classes: Breadcrumb

Constant Summary collapse

VALID_REQUEST_METHOD_NAMES =
Set.new([
  'get'.freeze,
  'post'.freeze,
  'put'.freeze,
  'delete'.freeze,
]).freeze
SOURCES =
JSON.parse(File.read(File.expand_path('sources.json', __dir__))).freeze
CONTENT_SECURITY_POLICY =
<<-CSP.delete("\n")
  default-src 'none';
  img-src 'self';
  font-src 'self';
  script-src 'report-sample' 'self';
  style-src 'self' 'unsafe-inline';
  style-src-attr 'unsafe-inline' ;
  style-src-elem 'self';
  connect-src https://www.flippercloud.io;
CSP

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(flipper, request) ⇒ Action

Returns a new instance of Action.



91
92
93
94
95
96
# File 'lib/flipper/ui/action.rb', line 91

def initialize(flipper, request)
  @flipper = flipper
  @request = request
  @code = 200
  @headers = {Rack::CONTENT_TYPE => 'text/plain'}
end

Instance Attribute Details

#flipperObject (readonly)

Public: The instance of the Flipper::DSL the middleware was initialized with.



83
84
85
# File 'lib/flipper/ui/action.rb', line 83

def flipper
  @flipper
end

#requestObject (readonly)

Public: The Rack::Request to provide a response for.



86
87
88
# File 'lib/flipper/ui/action.rb', line 86

def request
  @request
end

Class Method Details

.public_pathObject

Private: The path to the public folder.



77
78
79
# File 'lib/flipper/ui/action.rb', line 77

def self.public_path
  @public_path ||= Flipper::UI.root.join('public')
end

.route(regex) ⇒ Object

Public: Call this in subclasses so the action knows its route.

regex - The Regexp that this action should run for.

Returns nothing.



47
48
49
# File 'lib/flipper/ui/action.rb', line 47

def self.route(regex)
  @route_regex = regex
end

.route_match?(path) ⇒ Boolean

Internal: Does this action’s route match the path.

Returns:

  • (Boolean)


52
53
54
# File 'lib/flipper/ui/action.rb', line 52

def self.route_match?(path)
  path.match(route_regex)
end

.route_regexObject

Internal: The regex that matches which routes this action will work for.



57
58
59
# File 'lib/flipper/ui/action.rb', line 57

def self.route_regex
  @route_regex || raise("#{name}.route is not set")
end

.run(flipper, request) ⇒ Object

Internal: Initializes and runs an action for a given request.

flipper - The Flipper::DSL instance. request - The Rack::Request that was sent.

Returns result of Action#run.



67
68
69
# File 'lib/flipper/ui/action.rb', line 67

def self.run(flipper, request)
  new(flipper, request).run
end

.views_pathObject

Private: The path to the views folder.



72
73
74
# File 'lib/flipper/ui/action.rb', line 72

def self.views_path
  @views_path ||= Flipper::UI.root.join('views')
end

Instance Method Details

#asset_hash(src) ⇒ Object



282
283
284
285
286
287
288
# File 'lib/flipper/ui/action.rb', line 282

def asset_hash(src)
  v = ENV["RACK_ENV"] == "development" ? Time.now.to_i : Flipper::VERSION
  {
    src: "#{src}?v=#{v}",
    hash: SOURCES[src]
  }
end

#bootstrap_cssObject



270
271
272
# File 'lib/flipper/ui/action.rb', line 270

def bootstrap_css
  asset_hash "/css/bootstrap.min.css"
end

#bootstrap_jsObject



274
275
276
# File 'lib/flipper/ui/action.rb', line 274

def bootstrap_js
  asset_hash "/js/bootstrap.min.js"
end

#csrf_input_tagObject



247
248
249
# File 'lib/flipper/ui/action.rb', line 247

def csrf_input_tag
  %(<input type="hidden" name="authenticity_token" value="#{@request.session[:csrf]}">)
end

#halt(response) ⇒ Object

Public: Call this with a response to immediately stop the current action and respond however you want.

response - The response you would like to return.



129
130
131
# File 'lib/flipper/ui/action.rb', line 129

def halt(response)
  throw :halt, response
end

#header(name, value) ⇒ Object

Public: Set a header.

name - The String name of the header. value - The value of the header.



182
183
184
# File 'lib/flipper/ui/action.rb', line 182

def header(name, value)
  @headers[name] = value
end

#json_response(object) ⇒ Object

Public: Dumps an object as json and returns rack response with that as the body. Automatically sets content-type to “application/json”.

object - The Object that should be dumped as json.

Returns a response.



151
152
153
154
155
156
157
158
159
160
# File 'lib/flipper/ui/action.rb', line 151

def json_response(object)
  header Rack::CONTENT_TYPE, 'application/json'
  body = case object
  when String
    object
  else
    Typecast.to_json(object)
  end
  halt [@code, @headers, [body]]
end

#popper_jsObject



278
279
280
# File 'lib/flipper/ui/action.rb', line 278

def popper_js
  asset_hash "/js/popper.min.js"
end

#public_pathObject

Private



238
239
240
# File 'lib/flipper/ui/action.rb', line 238

def public_path
  self.class.public_path
end

#read_only?Boolean

Returns:

  • (Boolean)


262
263
264
# File 'lib/flipper/ui/action.rb', line 262

def read_only?
  Flipper::UI.configuration.read_only || flipper.read_only?
end

#redirect_to(location) ⇒ Object

Public: Redirect to a new location.

location - The String location to set the Location header to.



165
166
167
168
169
# File 'lib/flipper/ui/action.rb', line 165

def redirect_to(location)
  status 302
  header 'location', "#{script_name}#{Rack::Utils.escape_path(location)}"
  halt [@code, @headers, ['']]
end

#render_read_onlyObject

Internal: Method to call when the UI is in read only mode and you want to inform people of that fact.



257
258
259
260
# File 'lib/flipper/ui/action.rb', line 257

def render_read_only
  status 403
  halt view_response(:read_only)
end

#request_method_nameObject

Private: Returns the request method converted to an action method.



243
244
245
# File 'lib/flipper/ui/action.rb', line 243

def request_method_name
  @request_method_name ||= @request.request_method.downcase
end

#runObject

Public: Runs the request method for the provided request.

Returns whatever the request method returns in the action.



101
102
103
104
105
106
107
108
# File 'lib/flipper/ui/action.rb', line 101

def run
  if valid_request_method? && respond_to?(request_method_name)
    catch(:halt) { send(request_method_name) }
  else
    raise UI::RequestMethodNotSupported,
          "#{self.class} does not support request method #{request_method_name.inspect}"
  end
end

#run_other_action(action_class) ⇒ Object

Public: Runs another action from within the request method of a different action.

action_class - The class of the other action to run.

Examples

run_other_action Home
# => result of running Home action

Returns result of other action.



121
122
123
# File 'lib/flipper/ui/action.rb', line 121

def run_other_action(action_class)
  action_class.new(flipper, request).run
end

#script_nameObject

Internal: The path the app is mounted at.



218
219
220
# File 'lib/flipper/ui/action.rb', line 218

def script_name
  request.env['SCRIPT_NAME']
end

#status(code) ⇒ Object

Public: Set the status code for the response.

code - The Integer code you would like the response to return.



174
175
176
# File 'lib/flipper/ui/action.rb', line 174

def status(code)
  @code = code.to_i
end

#url_for(*parts) ⇒ Object

Internal: Generate urls relative to the app’s script name.

url_for("feature")             # => "http://localhost:9292/flipper/feature"
url_for("/thing")              # => "http://localhost:9292/thing"
url_for("https://example.com") # => "https://example.com"


228
229
230
# File 'lib/flipper/ui/action.rb', line 228

def url_for(*parts)
  URI.join(request.base_url, script_name + '/', *parts).to_s
end

#valid_request_method?Boolean

Returns:

  • (Boolean)


251
252
253
# File 'lib/flipper/ui/action.rb', line 251

def valid_request_method?
  VALID_REQUEST_METHOD_NAMES.include?(request_method_name)
end

#view(name) ⇒ Object

Private



210
211
212
213
214
215
# File 'lib/flipper/ui/action.rb', line 210

def view(name)
  path = views_path.join("#{name}.erb")
  raise "Template does not exist: #{path}" unless path.exist?

  eval(Erubi::Engine.new(path.read, escape: true).src)
end

#view_response(name) ⇒ Object

Public: Compiles a view and returns rack response with that as the body.

name - The Symbol name of the view.

Returns a response.



138
139
140
141
142
143
# File 'lib/flipper/ui/action.rb', line 138

def view_response(name)
  header Rack::CONTENT_TYPE, 'text/html'
  header 'content-security-policy', CONTENT_SECURITY_POLICY
  body = view_with_layout { view_without_layout name }
  halt [@code, @headers, [body]]
end

#view_with_layout(&block) ⇒ Object

Private



200
201
202
# File 'lib/flipper/ui/action.rb', line 200

def view_with_layout(&block)
  view :layout, &block
end

#view_without_layout(name) ⇒ Object

Private



205
206
207
# File 'lib/flipper/ui/action.rb', line 205

def view_without_layout(name)
  view name
end

#views_pathObject

Private



233
234
235
# File 'lib/flipper/ui/action.rb', line 233

def views_path
  self.class.views_path
end

#write_allowed?Boolean

Returns:

  • (Boolean)


266
267
268
# File 'lib/flipper/ui/action.rb', line 266

def write_allowed?
  !read_only?
end