ddr-models 2.0.1 → 2.1.0.rc1

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +23 -17
  3. data/app/models/collection.rb +1 -35
  4. data/ddr-models.gemspec +2 -1
  5. data/lib/ddr/actions.rb +1 -0
  6. data/lib/ddr/actions/virus_check.rb +28 -0
  7. data/lib/ddr/auth.rb +4 -0
  8. data/lib/ddr/auth/ability_definitions/datastream_ability_definitions.rb +7 -5
  9. data/lib/ddr/auth/grouper_gateway.rb +9 -1
  10. data/lib/ddr/auth/permissions.rb +2 -1
  11. data/lib/ddr/auth/role_based_access_controls_enforcement.rb +5 -5
  12. data/lib/ddr/auth/roles/role_types.rb +2 -1
  13. data/lib/ddr/datastreams.rb +2 -2
  14. data/lib/ddr/datastreams/administrative_metadata_datastream.rb +27 -14
  15. data/lib/ddr/datastreams/datastream_behavior.rb +13 -0
  16. data/lib/ddr/datastreams/fits_datastream.rb +88 -0
  17. data/lib/ddr/derivatives/png_generator.rb +2 -0
  18. data/lib/ddr/derivatives/ptif_generator.rb +2 -0
  19. data/lib/ddr/events/fixity_check_event.rb +2 -2
  20. data/lib/ddr/events/virus_check_event.rb +2 -14
  21. data/lib/ddr/index.rb +29 -0
  22. data/lib/ddr/index/abstract_query_result.rb +23 -0
  23. data/lib/ddr/index/connection.rb +17 -0
  24. data/lib/ddr/index/csv_query_result.rb +61 -0
  25. data/lib/ddr/index/document_builder.rb +9 -0
  26. data/lib/ddr/index/field.rb +23 -0
  27. data/lib/ddr/index/fields.rb +83 -0
  28. data/lib/ddr/index/filter.rb +48 -0
  29. data/lib/ddr/index/filters.rb +19 -0
  30. data/lib/ddr/index/legacy_license_fields.rb +14 -0
  31. data/lib/ddr/index/query.rb +35 -0
  32. data/lib/ddr/index/query_builder.rb +74 -0
  33. data/lib/ddr/index/query_clause.rb +52 -0
  34. data/lib/ddr/index/query_result.rb +70 -0
  35. data/lib/ddr/index/query_value.rb +16 -0
  36. data/lib/ddr/index/response.rb +13 -0
  37. data/lib/ddr/index/unique_key_field.rb +12 -0
  38. data/lib/ddr/index_fields.rb +7 -53
  39. data/lib/ddr/jobs.rb +1 -1
  40. data/lib/ddr/jobs/fits_file_characterization.rb +51 -0
  41. data/lib/ddr/managers.rb +1 -0
  42. data/lib/ddr/managers/technical_metadata_manager.rb +104 -0
  43. data/lib/ddr/models.rb +39 -23
  44. data/lib/ddr/models/base.rb +0 -2
  45. data/lib/ddr/models/describable.rb +1 -1
  46. data/lib/ddr/models/effective_license.rb +9 -0
  47. data/lib/ddr/models/engine.rb +13 -0
  48. data/lib/ddr/models/file_management.rb +157 -160
  49. data/lib/ddr/models/governable.rb +0 -4
  50. data/lib/ddr/models/has_admin_metadata.rb +80 -72
  51. data/lib/ddr/models/has_children.rb +1 -1
  52. data/lib/ddr/models/has_content.rb +18 -0
  53. data/lib/ddr/models/has_struct_metadata.rb +5 -1
  54. data/lib/ddr/models/indexing.rb +32 -20
  55. data/lib/ddr/models/inherited_license.rb +13 -0
  56. data/lib/ddr/models/license.rb +38 -0
  57. data/lib/ddr/models/solr_document.rb +195 -211
  58. data/lib/ddr/models/version.rb +1 -1
  59. data/lib/ddr/models/year_facet.rb +154 -0
  60. data/lib/ddr/utils.rb +13 -1
  61. data/lib/ddr/vocab/roles.rb +0 -10
  62. data/spec/controllers/including_role_based_access_controls_enforcement_spec.rb +4 -4
  63. data/spec/datastreams/fits_datastream_spec.rb +84 -0
  64. data/spec/fixtures/fits/document.xml +65 -0
  65. data/spec/fixtures/fits/image.xml +59 -0
  66. data/spec/index/filter_spec.rb +47 -0
  67. data/spec/index/filters_spec.rb +17 -0
  68. data/spec/index/query_spec.rb +19 -0
  69. data/spec/jobs/fits_file_characterization_spec.rb +52 -0
  70. data/spec/managers/technical_metadata_manager_spec.rb +140 -0
  71. data/spec/models/active_fedora_datastream_spec.rb +44 -0
  72. data/spec/models/collection_spec.rb +7 -12
  73. data/spec/models/component_spec.rb +3 -6
  74. data/spec/models/effective_license_spec.rb +49 -0
  75. data/spec/models/has_admin_metadata_spec.rb +143 -194
  76. data/spec/models/has_struct_metadata_spec.rb +2 -2
  77. data/spec/models/indexing_spec.rb +40 -0
  78. data/spec/models/solr_document_spec.rb +96 -37
  79. data/spec/models/year_facet_spec.rb +65 -0
  80. data/spec/spec_helper.rb +1 -7
  81. data/spec/support/shared_examples_for_ddr_models.rb +0 -2
  82. data/spec/support/shared_examples_for_has_content.rb +37 -3
  83. metadata +79 -32
  84. data/lib/ddr/datastreams/properties_datastream.rb +0 -25
  85. data/lib/ddr/jobs/migrate_legacy_authorization.rb +0 -23
  86. data/lib/ddr/models/has_properties.rb +0 -15
  87. data/lib/ddr/models/licensable.rb +0 -28
  88. data/spec/auth/legacy_authorization_spec.rb +0 -94
  89. data/spec/auth/legacy_roles_spec.rb +0 -32
  90. data/spec/jobs/migrate_legacy_authorization_spec.rb +0 -43
  91. data/spec/support/shared_examples_for_has_properties.rb +0 -5
  92. data/spec/support/shared_examples_for_licensable.rb +0 -15
@@ -1,3 +1,5 @@
1
+ require 'open3'
2
+
1
3
  module Ddr
2
4
  module Derivatives
3
5
  class PngGenerator < Generator
@@ -1,3 +1,5 @@
1
+ require 'open3'
2
+
1
3
  module Ddr
2
4
  module Derivatives
3
5
  class PtifGenerator < Generator
@@ -28,8 +28,8 @@ module Ddr
28
28
  end
29
29
 
30
30
  def to_solr
31
- { Ddr::IndexFields::LAST_FIXITY_CHECK_ON => event_date_time_s,
32
- Ddr::IndexFields::LAST_FIXITY_CHECK_OUTCOME => outcome }
31
+ { Ddr::Index::Fields::LAST_FIXITY_CHECK_ON => event_date_time_s,
32
+ Ddr::Index::Fields::LAST_FIXITY_CHECK_OUTCOME => outcome }
33
33
  end
34
34
 
35
35
  protected
@@ -9,20 +9,8 @@ module Ddr
9
9
  self.description = "Content file scanned for viruses"
10
10
 
11
11
  def to_solr
12
- { Ddr::IndexFields::LAST_VIRUS_CHECK_ON => event_date_time_s,
13
- Ddr::IndexFields::LAST_VIRUS_CHECK_OUTCOME => outcome }
14
- end
15
-
16
- # Message sent by ActiveSupport::Notifications
17
- def self.call(*args)
18
- notification = ActiveSupport::Notifications::Event.new(*args)
19
- result = notification.payload[:result] # Ddr::Antivirus::ScanResult instance
20
- create(pid: notification.payload[:pid],
21
- event_date_time: result.scanned_at,
22
- outcome: result.ok? ? SUCCESS : FAILURE,
23
- software: result.version,
24
- detail: result.to_s
25
- )
12
+ { Ddr::Index::Fields::LAST_VIRUS_CHECK_ON => event_date_time_s,
13
+ Ddr::Index::Fields::LAST_VIRUS_CHECK_OUTCOME => outcome }
26
14
  end
27
15
 
28
16
  end
@@ -0,0 +1,29 @@
1
+ module Ddr
2
+ module Index
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :AbstractQueryResult
6
+ autoload :Connection
7
+ autoload :CSVQueryResult
8
+ autoload :DocumentBuilder
9
+ autoload :Field
10
+ autoload :Fields
11
+ autoload :Filter
12
+ autoload :Filters
13
+ autoload :LegacyLicenseFields
14
+ autoload :Query
15
+ autoload :QueryBuilder
16
+ autoload :QueryClause
17
+ autoload :QueryResult
18
+ autoload :QueryValue
19
+ autoload :Response
20
+ autoload :UniqueKeyField
21
+
22
+ def self.pids
23
+ builder = QueryBuilder.new
24
+ query = builder.query
25
+ query.pids
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ module Ddr::Index
2
+ class AbstractQueryResult
3
+ include Enumerable
4
+
5
+ attr_reader :query, :conn
6
+ delegate :params, to: :query
7
+
8
+ def initialize(query)
9
+ @query = query
10
+ @conn = Connection.new
11
+ end
12
+
13
+ def count
14
+ response = conn.select(params, rows: 0)
15
+ response.num_found
16
+ end
17
+
18
+ def each
19
+ raise NotImplementedError, "Subclasses must implement `#each`."
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module Ddr::Index
2
+ class Connection < SimpleDelegator
3
+
4
+ def initialize
5
+ super RSolr.connect(ActiveFedora.solr_config)
6
+ end
7
+
8
+ def select(params, extra={})
9
+ Response.new get("select", params: params.merge(extra))
10
+ end
11
+
12
+ def page(*args)
13
+ Response.new paginate(*args)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,61 @@
1
+ require "csv"
2
+
3
+ module Ddr::Index
4
+ class CSVQueryResult < AbstractQueryResult
5
+
6
+ MAX_ROWS = 10**8
7
+
8
+ COL_SEP = CSV::DEFAULT_OPTIONS[:col_sep].freeze
9
+ QUOTE_CHAR = CSV::DEFAULT_OPTIONS[:quote_char].freeze
10
+
11
+ SOLR_CSV_OPTS = {
12
+ "csv.header" => "false",
13
+ "csv.mv.separator" => "|",
14
+ "wt" => "csv",
15
+ }.freeze
16
+
17
+ CSV_OPTS = {
18
+ return_headers: true,
19
+ write_headers: true,
20
+ }.freeze
21
+
22
+ attr_reader :csv_opts, :solr_csv_opts
23
+
24
+ def initialize(query, **opts)
25
+ super(query)
26
+
27
+ @solr_csv_opts = SOLR_CSV_OPTS.dup
28
+ @solr_csv_opts[:rows] ||= MAX_ROWS
29
+
30
+ @csv_opts = CSV_OPTS.dup
31
+ @csv_opts[:headers] = query.fields.map(&:label)
32
+
33
+ # Set column separator and quote character consistently
34
+ @csv_opts[:col_sep] = @solr_csv_opts["csv.separator"] = opts.fetch(:col_sep, COL_SEP)
35
+ @csv_opts[:quote_char] = @solr_csv_opts["csv.encapsulator"] = opts.fetch(:quote_char, QUOTE_CHAR)
36
+ end
37
+
38
+ def csv
39
+ CSV.new(data, csv_opts)
40
+ end
41
+
42
+ def each
43
+ csv.each
44
+ end
45
+
46
+ def to_s
47
+ csv.string
48
+ end
49
+
50
+ private
51
+
52
+ def csv_params
53
+ params.merge(solr_csv_opts)
54
+ end
55
+
56
+ def data
57
+ conn.get "select", params: csv_params
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,9 @@
1
+ module Ddr::Index
2
+ class DocumentBuilder
3
+
4
+ def self.build(doc)
5
+ SolrDocument.new(doc)
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ module Ddr::Index
2
+ class Field < SimpleDelegator
3
+
4
+ attr_reader :base
5
+
6
+ def initialize(base, *args)
7
+ @base = base.to_s
8
+ name = if args.empty?
9
+ @base
10
+ elsif args.last.is_a?(Hash) && args.last[:solr_name]
11
+ args.last[:solr_name]
12
+ else
13
+ Solrizer.solr_name(base, *args)
14
+ end
15
+ super(name)
16
+ end
17
+
18
+ def label
19
+ I18n.t "ddr.index.fields.#{base}", default: base.titleize
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,83 @@
1
+ module Ddr::Index
2
+ module Fields
3
+
4
+ include LegacyLicenseFields
5
+
6
+ def self.get(name)
7
+ const_get(name.to_s.upcase, false)
8
+ end
9
+
10
+ ID = UniqueKeyField.instance
11
+ PID = UniqueKeyField.instance
12
+
13
+ ACCESS_ROLE = Field.new :access_role, :stored_sortable
14
+ ACTIVE_FEDORA_MODEL = Field.new :active_fedora_model, :stored_sortable
15
+ ADMIN_SET = Field.new :admin_set, :stored_sortable
16
+ ADMIN_SET_FACET = Field.new :admin_set_facet, :facetable
17
+ BOX_NUMBER_FACET = Field.new :box_number_facet, :facetable
18
+ COLLECTION_FACET = Field.new :collection_facet, :facetable
19
+ COLLECTION_URI = Field.new :collection_uri, :symbol
20
+ CONTENT_CONTROL_GROUP = Field.new :content_control_group, :searchable, type: :string
21
+ CONTENT_SIZE = Field.new :content_size, :stored_sortable, type: :integer
22
+ CONTENT_SIZE_HUMAN = Field.new :content_size_human, :symbol
23
+ CREATOR_FACET = Field.new :creator_facet, :facetable
24
+ DATE_FACET = Field.new :date_facet, :facetable
25
+ DATE_SORT = Field.new :date_sort, :sortable
26
+ DEPOSITOR = Field.new :depositor, :stored_sortable
27
+ DISPLAY_FORMAT = Field.new :display_format, :stored_sortable
28
+ DOI = Field.new :doi, :symbol
29
+ EXTRACTED_TEXT = Field.new :extracted_text, :searchable, type: :text
30
+ HAS_MODEL = Field.new :has_model, :symbol
31
+ IDENTIFIER_ALL = Field.new :identifier_all, :symbol
32
+ INTERNAL_URI = Field.new :internal_uri, :stored_sortable
33
+ IS_ATTACHED_TO = Field.new :is_attached_to, :symbol
34
+ IS_EXTERNAL_TARGET_FOR = Field.new :is_external_target_for, :symbol
35
+ IS_GOVERNED_BY = Field.new :is_governed_by, :symbol
36
+ IS_MEMBER_OF = Field.new :is_member_of, :symbol
37
+ IS_MEMBER_OF_COLLECTION = Field.new :is_member_of_collection, :symbol
38
+ IS_PART_OF = Field.new :is_part_of, :symbol
39
+ LAST_FIXITY_CHECK_ON = Field.new :last_fixity_check_on, :stored_sortable, type: :date
40
+ LAST_FIXITY_CHECK_OUTCOME = Field.new :last_fixity_check_outcome, :symbol
41
+ LAST_VIRUS_CHECK_ON = Field.new :last_virus_check_on, :stored_sortable, type: :date
42
+ LAST_VIRUS_CHECK_OUTCOME = Field.new :last_virus_check_outcome, :symbol
43
+ LICENSE = Field.new :license, :stored_sortable
44
+ LOCAL_ID = Field.new :local_id, :stored_sortable
45
+ MEDIA_SUB_TYPE = Field.new :content_media_sub_type, :facetable
46
+ MEDIA_MAJOR_TYPE = Field.new :content_media_major_type, :facetable
47
+ MEDIA_TYPE = Field.new :content_media_type, :symbol
48
+ MULTIRES_IMAGE_FILE_PATH = Field.new :multires_image_file_path, :stored_sortable
49
+ OBJECT_PROFILE = Field.new :object_profile, :displayable
50
+ OBJECT_STATE = Field.new :object_state, :stored_sortable
51
+ OBJECT_CREATE_DATE = Field.new :system_create, :stored_sortable, type: :date
52
+ OBJECT_MODIFIED_DATE = Field.new :system_modified, :stored_sortable, type: :date
53
+ PERMANENT_ID = Field.new :permanent_id, :stored_sortable, type: :string
54
+ PERMANENT_URL = Field.new :permanent_url, :stored_sortable, type: :string
55
+ POLICY_ROLE = Field.new :policy_role, :symbol
56
+ PUBLISHER_FACET = Field.new :publisher_facet, :facetable
57
+ RESEARCH_HELP_CONTACT = Field.new :research_help_contact, :stored_sortable
58
+ RESOURCE_ROLE = Field.new :resource_role, :symbol
59
+ SERIES_FACET = Field.new :series_facet, :facetable
60
+ SPATIAL_FACET = Field.new :spatial_facet, :facetable
61
+ STRUCT_MAPS = Field.new :struct_maps, :stored_sortable
62
+ TECHMD_COLOR_SPACE = Field.new :techmd_color_space, :symbol
63
+ TECHMD_CREATING_APPLICATION = Field.new :techmd_creating_application, :symbol
64
+ TECHMD_CREATION_TIME = Field.new :techmd_creation_time, :stored_searchable, type: :date
65
+ TECHMD_FILE_SIZE = Field.new :techmd_file_size, :stored_searchable, type: :integer
66
+ TECHMD_FITS_VERSION = Field.new :techmd_fits_version, :stored_sortable
67
+ TECHMD_FITS_DATETIME = Field.new :techmd_fits_datetime, :stored_sortable, type: :date
68
+ TECHMD_FORMAT_LABEL = Field.new :techmd_format_label, :symbol
69
+ TECHMD_FORMAT_VERSION = Field.new :techmd_format_version, :symbol
70
+ TECHMD_IMAGE_HEIGHT = Field.new :techmd_image_height, :stored_searchable, type: :integer
71
+ TECHMD_IMAGE_WIDTH = Field.new :techmd_image_width, :stored_searchable, type: :integer
72
+ TECHMD_MEDIA_TYPE = Field.new :techmd_media_type, :symbol
73
+ TECHMD_MODIFICATION_TIME = Field.new :techmd_modification_time, :stored_searchable, type: :date
74
+ TECHMD_PRONOM_IDENTIFIER = Field.new :techmd_pronom_identifier, :symbol
75
+ TECHMD_VALID = Field.new :techmd_valid, :symbol
76
+ TECHMD_WELL_FORMED = Field.new :techmd_well_formed, :symbol
77
+ TITLE = Field.new :title, :stored_sortable
78
+ TYPE_FACET = Field.new :type_facet, :facetable
79
+ WORKFLOW_STATE = Field.new :workflow_state, :stored_sortable
80
+ YEAR_FACET = Field.new :year_facet, solr_name: "year_facet_iim"
81
+
82
+ end
83
+ end
@@ -0,0 +1,48 @@
1
+ module Ddr::Index
2
+ class Filter
3
+
4
+ class << self
5
+ delegate :where, :raw, :before_days, :before, :present, :absent, to: :new
6
+ end
7
+
8
+ attr_accessor :clauses
9
+
10
+ def initialize
11
+ @clauses = [ ]
12
+ end
13
+
14
+ def where(conditions)
15
+ clauses = conditions.map do |field, value|
16
+ if value.respond_to?(:each)
17
+ QueryClause.or_values(field, *value)
18
+ else
19
+ QueryClause.term(field, value)
20
+ end
21
+ end
22
+ raw *clauses
23
+ end
24
+
25
+ # Adds clause (String) w/o escaping
26
+ def raw(*clauses)
27
+ self.clauses += clauses
28
+ self
29
+ end
30
+
31
+ def present(field)
32
+ raw QueryClause.present(field)
33
+ end
34
+
35
+ def absent(field)
36
+ raw QueryClause.absent(field)
37
+ end
38
+
39
+ def before(field, date_time)
40
+ raw QueryClause.before(field, date_time)
41
+ end
42
+
43
+ def before_days(field, days)
44
+ raw QueryClause.before_days(field, days)
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,19 @@
1
+ module Ddr::Index
2
+ module Filters
3
+
4
+ HAS_CONTENT = Filter.where(Fields::ACTIVE_FEDORA_MODEL => ["Component", "Attachment", "Target"])
5
+
6
+ class << self
7
+ def is_governed_by(pid)
8
+ Filter.where(Fields::IS_GOVERNED_BY => internal_uri(pid))
9
+ end
10
+
11
+ def internal_uri(pid)
12
+ ActiveFedora::Base.internal_uri(pid)
13
+ end
14
+ end
15
+
16
+ private_class_method :internal_uri
17
+
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ module Ddr::Index
2
+ module LegacyLicenseFields
3
+
4
+ warn "[DEPRECATION] `Ddr::Index::LegacyLicenseFields` is deprecated and will be removed from ddr-models 3.0."
5
+
6
+ DEFAULT_LICENSE_DESCRIPTION = Field.new :default_license_description, type: :string
7
+ DEFAULT_LICENSE_TITLE = Field.new :default_license_title, type: :string
8
+ DEFAULT_LICENSE_URL = Field.new :default_license_url, type: :string
9
+ LICENSE_DESCRIPTION = Field.new :license_description, type: :string
10
+ LICENSE_TITLE = Field.new :license_title, type: :string
11
+ LICENSE_URL = Field.new :license_url, type: :string
12
+
13
+ end
14
+ end
@@ -0,0 +1,35 @@
1
+ module Ddr::Index
2
+ class Query
3
+
4
+ attr_reader :q, :fields, :filters, :sort, :rows
5
+
6
+ delegate :count, :docs, :pids, :each_pid, :all, to: :result
7
+
8
+ def inspect
9
+ "#<#{self.class.name} q=#{q.inspect}, filters=#{filters.inspect}," \
10
+ " sort=#{sort.inspect}, rows=#{rows.inspect}, fields=#{fields.inspect}>"
11
+ end
12
+
13
+ def to_s
14
+ URI.encode_www_form(params)
15
+ end
16
+
17
+ def params
18
+ { q: q,
19
+ fq: filters.map(&:clauses).flatten,
20
+ fl: fields.join(","),
21
+ sort: sort.join(","),
22
+ rows: rows,
23
+ }.select { |k, v| v.present? }
24
+ end
25
+
26
+ def result
27
+ QueryResult.new(self)
28
+ end
29
+
30
+ def csv(**opts)
31
+ CSVQueryResult.new(self, **opts)
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,74 @@
1
+ module Ddr::Index
2
+ class QueryBuilder
3
+
4
+ def self.build
5
+ builder = new
6
+ yield builder
7
+ builder.query
8
+ end
9
+
10
+ def initialize
11
+ @q = nil
12
+ @fields = [ ]
13
+ @filters = [ ]
14
+ @sort = [ ]
15
+ @rows = nil
16
+ end
17
+
18
+ def query
19
+ Query.new.tap do |qry|
20
+ instance_variables.each do |var|
21
+ qry.instance_variable_set(var, instance_variable_get(var))
22
+ end
23
+ end
24
+ end
25
+
26
+ def id(pid)
27
+ q QueryClause.id(pid)
28
+ limit 1
29
+ end
30
+
31
+ def filter(*fltrs)
32
+ @filters.push *fltrs
33
+ self
34
+ end
35
+
36
+ def fields(*flds)
37
+ @fields.push *flds
38
+ self
39
+ end
40
+
41
+ def limit(num)
42
+ @rows = num
43
+ self
44
+ end
45
+
46
+ def order_by(field, order)
47
+ @sort << [field, order].join(" ")
48
+ self
49
+ end
50
+
51
+ def asc(field)
52
+ order_by field, "asc"
53
+ end
54
+
55
+ def desc(field)
56
+ order_by field, "desc"
57
+ end
58
+
59
+ def q(q)
60
+ @q = q
61
+ self
62
+ end
63
+
64
+ protected
65
+
66
+ def method_missing(name, *args, &block)
67
+ if Filter.respond_to? name
68
+ return filter Filter.send(name, *args, &block)
69
+ end
70
+ super
71
+ end
72
+
73
+ end
74
+ end