Class: Card::Format

Inherits:
Object show all
Includes:
Location
Defined in:
lib/card/format.rb

Constant Summary collapse

DEPRECATED_VIEWS =
{ view: :open, card: :open, line: :closed,
bare: :core, naked: :core }
INCLUSION_MODES =
{ closed: :closed, closed_content: :closed, edit: :edit,
layout: :layout, new: :edit, setup: :edit,
normal: :normal, template: :template }
@@registered =
[]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Location

#card_path, #card_url, #page_path

Constructor Details

#initialize(card, opts = {}) ⇒ Format

~~~~~ INSTANCE METHODS



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/card/format.rb', line 86

def initialize card, opts={}
  unless (@card = card)
    raise Card::Error, 'format initialized without card'
  end
  opts.each do |key, value|
    instance_variable_set "@#{key}", value
  end

  @mode ||= :normal
  @root ||= self
  @depth ||= 0

  @context_names = get_context_names
  include_set_format_modules
  self
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *opts, &proc) ⇒ Object



185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/card/format.rb', line 185

def method_missing method, *opts, &proc
  if method =~ /(_)?(optional_)?render(_(\w+))?/
    view = $3 ? $4 : opts.shift
    args = opts[0] ? opts.shift.clone : {}
    args.merge!(optional: true, default_visibility: opts.shift) if $2
    args[:skip_permissions] = true if $1
    render view, args
  else
    proc = proc { |*a| raw yield *a } if proc
    response = root.template.send method, *opts, &proc
    response.is_a?(String) ? root.template.raw(response) : response
  end
end

Instance Attribute Details

#cardObject (readonly)

Returns the value of attribute card.



21
22
23
# File 'lib/card/format.rb', line 21

def card
  @card
end

#error_statusObject

Returns the value of attribute error_status.



22
23
24
# File 'lib/card/format.rb', line 22

def error_status
  @error_status
end

#formObject

Returns the value of attribute form.



22
23
24
# File 'lib/card/format.rb', line 22

def form
  @form
end

#inclusion_optsObject

Returns the value of attribute inclusion_opts.



22
23
24
# File 'lib/card/format.rb', line 22

def inclusion_opts
  @inclusion_opts
end

#main_optsObject (readonly)

Returns the value of attribute main_opts.



21
22
23
# File 'lib/card/format.rb', line 21

def main_opts
  @main_opts
end

#parentObject (readonly)

Returns the value of attribute parent.



21
22
23
# File 'lib/card/format.rb', line 21

def parent
  @parent
end

#rootObject (readonly)

Returns the value of attribute root.



21
22
23
# File 'lib/card/format.rb', line 21

def root
  @root
end

Class Method Details

.extract_class_vars(view, opts) ⇒ Object



38
39
40
41
42
43
44
45
# File 'lib/card/format.rb', line 38

def extract_class_vars view, opts
  return unless opts.present?
  [:perms, :error_code, :denial, :closed].each do |varname|
    class_var = send varname
    class_var[view] = opts.delete(varname) if opts[varname]
  end
  extract_view_tags view, opts
end

.extract_view_tags(view, opts) ⇒ Object



47
48
49
50
51
52
53
54
# File 'lib/card/format.rb', line 47

def extract_view_tags view, opts
  tags = opts.delete :tags
  return unless tags
  Array.wrap(tags).each do |tag|
    view_tags[view] ||= {}
    view_tags[view][tag] = true
  end
end

.format_ancestryObject



71
72
73
74
75
76
77
# File 'lib/card/format.rb', line 71

def format_ancestry
  ancestry = [self]
  unless self == Card::Format
    ancestry = ancestry + superclass.format_ancestry
  end
  ancestry
end

.format_class_name(format) ⇒ Object



31
32
33
34
35
36
# File 'lib/card/format.rb', line 31

def format_class_name format
  format = format.to_s
  format = '' if format == 'base'
  format = @@aliases[format] if @@aliases[format]
  "#{format.camelize}Format"
end

.max_depthObject



79
80
81
# File 'lib/card/format.rb', line 79

def max_depth
  Card.config.max_depth
end

.new(card, opts = {}) ⇒ Object



56
57
58
59
60
61
62
63
64
# File 'lib/card/format.rb', line 56

def new card, opts={}
  if self != Format
    super
  else
    format = opts[:format] || :html
    klass = Card.const_get format_class_name(format)
    self == klass ? super : klass.new(card, opts)
  end
end

.register(format) ⇒ Object



27
28
29
# File 'lib/card/format.rb', line 27

def register format
  @@registered << format.to_s
end

.tagged(view, tag) ⇒ Object



66
67
68
69
# File 'lib/card/format.rb', line 66

def tagged view, tag
  return unless view && tag && (view_tags = @@view_tags[view.to_sym])
  view_tags[tag.to_sym]
end

Instance Method Details

#add_class(options, klass) ⇒ Object

———— LINKS —————



562
563
564
# File 'lib/card/format.rb', line 562

def add_class options, klass
  options[:class] = [options[:class], klass].flatten.compact * ' '
end

#add_name_context(name = nil) ⇒ Object



581
582
583
584
585
# File 'lib/card/format.rb', line 581

def add_name_context name=nil
  name ||= card.name
  @context_names += name.to_name.part_names
  @context_names.uniq!
end

#approved_view(view, args = {}) ⇒ Object



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/card/format.rb', line 358

def approved_view view, args={}
  case
  when @depth >= Card.config.max_depth
    # prevent recursion. @depth tracks subformats
    :too_deep
  when @@perms[view] == :none
    # permission skipping specified in view definition
    view
  when args.delete(:skip_permissions)
    # permission skipping specified in args
    view
  when !card.known? && !tagged(view, :unknown_ok)
    # handle unknown cards (where view not exempt)
    view_for_unknown view, args
  else
    # run explicit permission checks
    permitted_view view, args
  end
end

#canonicalize_view(view) ⇒ Object



410
411
412
413
414
# File 'lib/card/format.rb', line 410

def canonicalize_view view
  return if view.blank?
  view_key = view.to_viewname.key.to_sym
  DEPRECATED_VIEWS[view_key] || view_key
end

#controllerObject



140
141
142
# File 'lib/card/format.rb', line 140

def controller
  Env[:controller] ||= CardController.new
end

#current_view(view) ⇒ Object



297
298
299
300
301
302
303
# File 'lib/card/format.rb', line 297

def current_view view
  old_view = @current_view
  @current_view = view
  yield
ensure
  @current_view = old_view
end

#default_item_viewObject



554
555
556
# File 'lib/card/format.rb', line 554

def default_item_view
  :name
end

#default_render_args(view, a = nil) ⇒ Object



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/card/format.rb', line 265

def default_render_args view, a=nil
  args =
    case a
    when nil   then {}
    when Hash  then a.clone
    when Array then a[0].merge a[1]
    else       raise Card::Error, "bad render args: #{a}"
    end

  default_method = "default_#{view}_args"
  if respond_to? default_method
    send default_method, args
  end
  args
end

#error_cardnameObject



305
306
307
# File 'lib/card/format.rb', line 305

def error_cardname
  card && card.name.present? ? card.name : 'unknown card'
end

#expand_main(opts) ⇒ Object



451
452
453
454
455
456
457
458
459
460
461
# File 'lib/card/format.rb', line 451

def expand_main opts
  opts.merge! root.main_opts if root.main_opts
  legacy_main_opts_tweaks! opts

  with_inclusion_mode :normal do
    @mainline = true
    result = wrap_main nest(root.card, opts)
    @mainline = false
    result
  end
end

#fetch_nested_card(options) ⇒ Object



538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
# File 'lib/card/format.rb', line 538

def fetch_nested_card options
  args = { name: options[:inc_name], type: options[:type], supercard: card }
  args.delete(:supercard) if options[:inc_name].strip.blank?
  # special case.  gets absolutized incorrectly. fix in smartname?
  if options[:inc_name] =~ /^_main\+/
    # FIXME: this is a rather hacky (and untested) way to get @superleft
    # to work on new cards named _main+whatever
    args[:name] = args[:name].gsub /^_main\+/, '+'
    args[:supercard] = root.card
  end
  if (content = get_inclusion_content options[:inc_name])
    args[:content] = content
  end
  Card.fetch options[:inc_name], new: args
end

#focal?Boolean

meaning the current card is the requested card

Returns:

  • (Boolean)


168
169
170
171
172
173
174
# File 'lib/card/format.rb', line 168

def focal? # meaning the current card is the requested card
  if Env.ajax?
    @depth == 0
  else
    main?
  end
end

#format_date(date, include_time = true) ⇒ Object



570
571
572
573
574
575
576
577
578
579
# File 'lib/card/format.rb', line 570

def format_date date, include_time=true
  # using DateTime because Time doesn't support %e on some platforms
  if include_time
    DateTime.new(
      date.year, date.mon, date.day, date.hour, date.min, date.sec
    ).strftime('%B %e, %Y %H:%M:%S')
  else
    DateTime.new(date.year, date.mon, date.day).strftime('%B %e, %Y')
  end
end

#get_content_object(content, opts) ⇒ Object



340
341
342
343
344
345
346
# File 'lib/card/format.rb', line 340

def get_content_object content, opts
  if content.is_a? Content
    content
  else
    Content.new content, self, opts.delete(:content_opts)
  end
end

#get_context_namesObject



103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/card/format.rb', line 103

def get_context_names
  case
  when @context_names
    part_keys = @card.cardname.part_names.map &:key
    @context_names.reject { |n| !part_keys.include? n.key }
  when params[:slot]
    context_name_list = params[:slot][:name_context].to_s
    context_name_list.split(',').map &:to_name
  else
    []
  end
end

#get_inclusion_content(cardname) ⇒ Object



527
528
529
530
531
532
533
534
535
536
# File 'lib/card/format.rb', line 527

def get_inclusion_content cardname
  content = params[cardname.to_s.tr('+', '_')]

  # CLEANME This is a hack so plus cards re-populate on failed signups
  p = params['subcards']
  if p && (card_params = p[cardname.to_s])
    content = card_params['content']
  end
  content if content.present? # returns nil for empty string
end

#get_inclusion_defaults(_nested_card) ⇒ Object



132
133
134
# File 'lib/card/format.rb', line 132

def get_inclusion_defaults _nested_card
  { view: :name }
end

#hidden_view?(view, args) ⇒ Boolean

Returns:

  • (Boolean)


227
228
229
# File 'lib/card/format.rb', line 227

def hidden_view? view, args
  args.delete(:optional) && !show_view?(view, args)
end

#include_set_format_modulesObject



116
117
118
119
120
121
122
# File 'lib/card/format.rb', line 116

def include_set_format_modules
  self.class.format_ancestry.reverse_each do |klass|
    card.set_format_modules(klass).each do |m|
      singleton_class.send :include, m
    end
  end
end

#inclusion_defaults(nested_card) ⇒ Object



124
125
126
127
128
129
130
# File 'lib/card/format.rb', line 124

def inclusion_defaults nested_card
  @inclusion_defaults ||= begin
    defaults = get_inclusion_defaults(nested_card).clone
    defaults.merge! @inclusion_opts if @inclusion_opts
    defaults
  end
end

#legacy_main_opts_tweaks!(opts) ⇒ Object



463
464
465
466
467
468
469
470
471
# File 'lib/card/format.rb', line 463

def legacy_main_opts_tweaks! opts
  if (val = params[:size]) && val.present?
    opts[:size] = val.to_sym
  end

  if (val = params[:item]) && val.present?
    opts[:items] = (opts[:items] || {}).reverse_merge view: val.to_sym
  end
end

#main?Boolean

Returns:

  • (Boolean)


164
165
166
# File 'lib/card/format.rb', line 164

def main?
  @depth == 0
end

#nest(nested_card, opts = {}) ⇒ Object



477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
# File 'lib/card/format.rb', line 477

def nest nested_card, opts={}
  # ActiveSupport::Notifications.instrument('card', message:
  # "nest: #{nested_card.name}, #{opts}") do
  opts.delete_if { |_k, v| v.nil? }
  opts.reverse_merge! inclusion_defaults(nested_card)

  sub = nil
  if opts[:inc_name] =~ /^_(self)?$/
    sub = self
  else
    sub = subformat nested_card
    sub.inclusion_opts = opts[:items] ? opts[:items].clone : {}
  end

  view = canonicalize_view opts.delete :view
  opts[:home_view] = [:closed, :edit].member?(view) ? :open : view
  # FIXME: special views should be represented in view definitions

  view =
    case @mode
    when :edit     then view_in_edit_mode(view, nested_card)
    when :template then :template_rule
    when :closed   then view_in_closed_mode(view, nested_card)
    else                view
    end

  sub.optional_render view, opts
  # end
end

#nest_arg_visibility(view, args) ⇒ Object



249
250
251
252
253
254
# File 'lib/card/format.rb', line 249

def nest_arg_visibility view, args
  [:show, :hide].each do |setting|
    return setting if parse_view_visibility(args[setting]).member?(view)
  end
  false
end

#ok?(task) ⇒ Boolean

Returns:

  • (Boolean)


398
399
400
401
402
403
# File 'lib/card/format.rb', line 398

def ok? task
  task = :create if task == :update && card.new_card?
  @ok ||= {}
  @ok[task] = card.ok? task if @ok[task].nil?
  @ok[task]
end

#ok_view(view, args = {}) ⇒ Object



348
349
350
351
352
353
354
355
356
# File 'lib/card/format.rb', line 348

def ok_view view, args={}
  return view if args.delete :skip_permissions
  approved_view = approved_view view, args
  args[:denied_view] = view if approved_view != view
  if focal? && (error_code = @@error_code[approved_view])
    root.error_status = error_code
  end
  approved_view
end

#paramsObject



136
137
138
# File 'lib/card/format.rb', line 136

def params
  Env.params
end

#parse_view_visibility(val) ⇒ Object



256
257
258
259
260
261
262
263
# File 'lib/card/format.rb', line 256

def parse_view_visibility val
  case val
  when NilClass then []
  when Array    then val
  when String   then val.split(/[\s,]+/)
  else raise Card::Error, "bad show/hide argument: #{val}"
  end.map { |view| canonicalize_view view }
end

#permitted_view(view, args) ⇒ Object



382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/card/format.rb', line 382

def permitted_view view, args
  perms_required = @@perms[view] || :read
  args[:denied_task] =
    if Proc === perms_required
      :read if !(perms_required.call self)  # read isn't quite right
    else
      [perms_required].flatten.find { |task| !ok? task }
    end

  if args[:denied_task]
    @@denial[view] || :denial
  else
    view
  end
end

#prepare_nest(opts) ⇒ Object



429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/card/format.rb', line 429

def prepare_nest opts
  @char_count ||= 0
  opts ||= {}

  case
  when opts.has_key?(:comment)
    # commented nest
    opts[:comment]
  when @mode == :closed && @char_count > Card.config.max_char_count
    # move on; content out of view
    ''
  when opts[:inc_name] == '_main' && show_layout? && @depth == 0
    # the main card within a layout
    expand_main opts
  else
    # standard nest
    result = nest fetch_nested_card(opts), opts
    @char_count += result.length if @mode == :closed && result
    result
  end
end

#process_content(override_content = nil, opts = {}) ⇒ Object



328
329
330
# File 'lib/card/format.rb', line 328

def process_content override_content=nil, opts={}
  process_content_object(override_content, opts).to_s
end

#process_content_object(override_content = nil, opts = {}) ⇒ Object



332
333
334
335
336
337
338
# File 'lib/card/format.rb', line 332

def process_content_object override_content=nil, opts={}
  content = override_content || render_raw || ''
  content_object = get_content_object content, opts
  content_object.process_each_chunk do |chunk_opts|
    prepare_nest chunk_opts.merge(opts) { yield }
  end
end

#render(view, args = {}) ⇒ Object

———- Rendering ————



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/card/format.rb', line 203

def render view, args={}
  view = canonicalize_view view
  return if hidden_view? view, args
  view = ok_view view, args
  current_view(view) do
    args = default_render_args view, args
    with_inclusion_mode view do
      Card::ViewCache.fetch(self, view, args) do
        method = view_method view, args
        method.arity == 0 ? method.call : method.call(args)
      end
    end
  end
rescue => e
  rescue_view e, view
end

#rendering_error(_exception, view) ⇒ Object



309
310
311
# File 'lib/card/format.rb', line 309

def rendering_error _exception, view
  "Error rendering: #{error_cardname} (#{view} view)"
end

#rescue_view(e, view) ⇒ Object



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/card/format.rb', line 281

def rescue_view e, view
  if Rails.env =~ /^cucumber|test$/
    raise e
  else
    Rails.logger.info "\nError rendering #{error_cardname} / #{view}: "\
                      "#{e.class} : #{e.message}"
    Card::Error.current = e
    card.notable_exception_raised
    if (debug = Card[:debugger]) && debug.content == 'on'
      raise e
    else
      rendering_error e, view
    end
  end
end

#sessionObject



144
145
146
# File 'lib/card/format.rb', line 144

def session
  Env.session
end

#show_view?(view, args) ⇒ Boolean

Returns:

  • (Boolean)


231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/card/format.rb', line 231

def show_view? view, args
  default = args.delete(:default_visibility) || :show # FIXME: - ugly
  api_option = args["optional_#{view}".to_sym]
  case
  # permanent visibility specified in code
  when api_option == :always then true
  when api_option == :never  then false
  else
    # wagneer can override code settings
    contextual_setting = nest_arg_visibility(view, args) || api_option
    case contextual_setting
    when :show               then true
    when :hide               then false
    else default == :show
    end
  end
end

#showname(title = nil) ⇒ Object



148
149
150
151
152
153
154
# File 'lib/card/format.rb', line 148

def showname title=nil
  if title
    title.to_name.to_absolute_name(card.cardname).to_show *@context_names
  else
    @showname ||= card.cardname.to_show *@context_names
  end
end

#subformat(subcard) ⇒ Object

————- Sub Format and Inclusion Processing ————



317
318
319
320
321
322
323
324
325
326
# File 'lib/card/format.rb', line 317

def subformat subcard
  subcard = Card.fetch(subcard, new: {}) if subcard.is_a?(String)
  self.class.new subcard,
                 parent: self, depth: @depth + 1, root: @root,
                 # FIXME: - the following four should not be hard-coded
                 # here.  need a generalized mechanism
                 # for attribute inheritance
                 context_names: @context_names, mode: @mode,
                 mainline: @mainline, form: @form
end

#tagged(view, tag) ⇒ Object



378
379
380
# File 'lib/card/format.rb', line 378

def tagged view, tag
  self.class.tagged view, tag
end

#templateObject



176
177
178
179
180
181
182
183
# File 'lib/card/format.rb', line 176

def template
  @template ||= begin
    c = controller
    t = ActionView::Base.new c.class.view_paths, { _routes: c._routes }, c
    t.extend c.class._helpers
    t
  end
end

#unique_idObject



566
567
568
# File 'lib/card/format.rb', line 566

def unique_id
  "#{card.key}-#{Time.now.to_i}-#{rand(3)}"
end

#view_for_unknown(_view, _args) ⇒ Object



405
406
407
408
# File 'lib/card/format.rb', line 405

def view_for_unknown _view, _args
  # note: overridden in HTML
  focal? ? :not_found : :missing
end

#view_in_closed_mode(homeview, nested_card) ⇒ Object



516
517
518
519
520
521
522
523
524
525
# File 'lib/card/format.rb', line 516

def view_in_closed_mode homeview, nested_card
  approved_view = @@closed[homeview]
  case
  when approved_view == true  then homeview
  when @@error_code[homeview] then homeview
  when approved_view          then approved_view
  when !nested_card.known?    then :closed_missing
  else                             :closed_content
  end
end

#view_in_edit_mode(homeview, nested_card) ⇒ Object



507
508
509
510
511
512
513
514
# File 'lib/card/format.rb', line 507

def view_in_edit_mode homeview, nested_card
  not_in_form =
    @@perms[homeview] == :none || # view configured not to keep in form
    nested_card.structure || #      not yet nesting structures
    nested_card.key.blank? #        eg {{_self|type}} on new cards

  not_in_form ? :blank : :edit_in_form
end

#view_method(view, args) ⇒ Object



220
221
222
223
224
225
# File 'lib/card/format.rb', line 220

def view_method view, args
  method "_view_#{view}"
rescue
  args[:unsupported_view] = view
  method '_view_unsupported_view'
end

#with_inclusion_mode(mode) ⇒ Object



416
417
418
419
420
421
422
423
424
425
426
427
# File 'lib/card/format.rb', line 416

def with_inclusion_mode mode
  if (switch_mode = INCLUSION_MODES[mode]) && @mode != switch_mode
    old_mode, @mode = @mode, switch_mode
    @inclusion_defaults = nil
  end
  result = yield
  if old_mode
    @inclusion_defaults = nil
    @mode = old_mode
  end
  result
end

#with_name_context(name) ⇒ Object



156
157
158
159
160
161
162
# File 'lib/card/format.rb', line 156

def with_name_context name
  old_context = @context_names
  add_name_context name
  result = yield
  @context_names = old_context
  result
end

#wrap_main(content) ⇒ Object



473
474
475
# File 'lib/card/format.rb', line 473

def wrap_main content
  content  # no wrapping in base format
end