sufia 7.0.0.beta1 → 7.0.0.beta2

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/sufia.js +3 -4
  3. data/app/assets/stylesheets/sufia/_styles.scss +2 -1
  4. data/app/controllers/concerns/sufia/transfers_controller_behavior.rb +1 -0
  5. data/app/controllers/errors_controller.rb +1 -1
  6. data/app/indexers/sufia/work_indexer.rb +1 -0
  7. data/app/presenters/sufia/admin_stats_presenter.rb +17 -11
  8. data/app/search_builders/sufia/catalog_search_builder.rb +29 -0
  9. data/app/search_builders/sufia/my_collections_search_builder.rb +7 -0
  10. data/app/search_builders/sufia/my_highlights_search_builder.rb +9 -3
  11. data/app/search_builders/sufia/my_shares_search_builder.rb +8 -1
  12. data/app/search_builders/sufia/search_builder.rb +0 -59
  13. data/app/services/sufia/analytics.rb +25 -8
  14. data/app/services/sufia/query_service.rb +1 -1
  15. data/app/services/sufia/statistics/collections/over_time.rb +13 -0
  16. data/app/services/sufia/statistics/depositors/summary.rb +54 -0
  17. data/app/services/sufia/statistics/file_sets/by_format.rb +14 -0
  18. data/app/services/sufia/statistics/over_time.rb +11 -3
  19. data/app/services/sufia/statistics/system_stats.rb +61 -0
  20. data/app/services/sufia/statistics/term_query.rb +65 -0
  21. data/app/services/sufia/statistics/works/by_depositor.rb +13 -0
  22. data/app/services/sufia/statistics/works/by_resource_type.rb +13 -0
  23. data/app/services/sufia/statistics/works/count.rb +49 -0
  24. data/app/services/sufia/statistics/works/over_time.rb +13 -0
  25. data/app/views/admin/stats/_stats_by_date.html.erb +1 -1
  26. data/app/views/admin/stats/_top_data.html.erb +4 -4
  27. data/app/views/admin/stats/_works.html.erb +8 -0
  28. data/app/views/collections/_form.html.erb +1 -1
  29. data/app/views/curation_concerns/base/_attribute_rows.html.erb +10 -10
  30. data/app/views/curation_concerns/base/_metadata.html.erb +1 -1
  31. data/app/views/curation_concerns/base/_relationships.html.erb +1 -1
  32. data/app/views/curation_concerns/file_sets/_descriptions.html.erb +1 -1
  33. data/app/views/error/404.html.erb +8 -19
  34. data/app/views/layouts/error.html.erb +3 -3
  35. data/app/views/layouts/homepage.html.erb +1 -1
  36. data/app/views/layouts/sufia-dashboard.html.erb +1 -1
  37. data/app/views/layouts/sufia-one-column.html.erb +1 -1
  38. data/app/views/{_footer.html.erb → shared/_footer.html.erb} +1 -1
  39. data/app/views/stats/file.html.erb +0 -1
  40. data/app/views/stats/work.html.erb +0 -2
  41. data/lib/generators/sufia/install_generator.rb +6 -0
  42. data/lib/sufia/engine.rb +1 -0
  43. data/lib/sufia/version.rb +1 -1
  44. data/spec/controllers/my/shares_controller_spec.rb +6 -7
  45. data/spec/controllers/transfers_controller_spec.rb +10 -0
  46. data/spec/features/batch_edit_spec.rb +1 -1
  47. data/spec/lib/sufia/analytics_spec.rb +18 -10
  48. data/spec/presenters/sufia/admin_stats_presenter_spec.rb +21 -14
  49. data/spec/search_builder/{sufia_search_builder_spec.rb → sufia/catalog_search_builder_spec.rb} +1 -1
  50. data/spec/search_builder/sufia/my_shares_search_builder_spec.rb +18 -0
  51. data/spec/services/statistics/{collections_spec.rb → collections/over_time_spec.rb} +1 -1
  52. data/spec/services/{sufia/admin/depositor_stats_spec.rb → statistics/depositors/summary_spec.rb} +5 -7
  53. data/spec/services/statistics/file_sets/by_format_spec.rb +30 -0
  54. data/spec/services/statistics/system_stats_spec.rb +54 -0
  55. data/spec/services/statistics/works/by_depositor_spec.rb +25 -0
  56. data/spec/services/statistics/works/by_resource_type_spec.rb +21 -0
  57. data/spec/services/statistics/works/count_spec.rb +42 -0
  58. data/spec/services/statistics/{works_spec.rb → works/over_time_spec.rb} +1 -1
  59. data/spec/views/admin/stats/index.html.erb_spec.rb +3 -6
  60. data/spec/views/curation_concerns/base/_relationships.html.erb_spec.rb +4 -1
  61. data/sufia.gemspec +2 -1
  62. metadata +49 -25
  63. data/app/services/sufia/admin/depositor_stats.rb +0 -48
  64. data/app/services/sufia/statistics/collections.rb +0 -12
  65. data/app/services/sufia/statistics/works.rb +0 -12
  66. data/app/services/sufia/system_stats.rb +0 -120
  67. data/app/views/admin/stats/_files.html.erb +0 -8
  68. data/lib/generators/sufia/fulltext_generator.rb +0 -26
  69. data/spec/services/sufia/system_stats_spec.rb +0 -224
  70. data/vendor/assets/javascripts/flot/excanvas.js +0 -1428
  71. data/vendor/assets/javascripts/flot/jquery.flot.js +0 -3168
  72. data/vendor/assets/javascripts/flot/jquery.flot.selection.js +0 -360
  73. data/vendor/assets/javascripts/flot/jquery.flot.time.js +0 -432
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 117c39bea3f165e71777729d3f4118473b8a876c
4
- data.tar.gz: 8b5a85e970c5ca8dd32733fbb8723548e80d8930
3
+ metadata.gz: 8878efdf23b664d1ccc522e05f997d099dcd43f9
4
+ data.tar.gz: 8062a829e1bc496ef751aca11363c10bc5fc143c
5
5
  SHA512:
6
- metadata.gz: 8d03ab38475146b43bf223c5c9b6a9a9a20c00f82d49887acd922164d0423daee93d561101d116b36a8ffc328be5233c9bb7f84d4784a1a688df1c4d52c1c6c5
7
- data.tar.gz: e4b9d252e988795eb3888cdd4c01849f427857d2ac2db4210627b2745df4a9955528ca742492dc34009b2b60bc28f848a5b2537b9b24c1a3beb344a9bb9559ce
6
+ metadata.gz: e1457e174fcfdf5ceb54dc2c9090a37bb03af3c47882f04a046550f19b1687112cfd073ac9b75199ad4db8615ab13790b5ed47ee39772a582b3299dc3070fa82
7
+ data.tar.gz: afdcc985bc2f96201032af0c9ef78ceb2eb7387499ad0621c775fcfbd0d6a24e315b619fba59cfa2503432d626dcc209cffc754a6498f548091cac415338c9b1
@@ -21,10 +21,9 @@
21
21
  //= require select2
22
22
  //= require fixedsticky
23
23
 
24
- //= require flot/excanvas
25
- //= require flot/jquery.flot
26
- //= require flot/jquery.flot.time
27
- //= require flot/jquery.flot.selection
24
+ //= require jquery.flot
25
+ //= require jquery.flot.time
26
+ //= require jquery.flot.selection
28
27
 
29
28
  //= require batch_edit
30
29
  //
@@ -33,9 +33,10 @@
33
33
  padding-bottom: $padding-large-vertical;
34
34
  }
35
35
 
36
- #footer {
36
+ footer.navbar {
37
37
  padding-bottom: 0;
38
38
  margin-bottom: 0;
39
+ border-radius: 0;
39
40
  }
40
41
 
41
42
  label.required, span.required {
@@ -3,6 +3,7 @@ module Sufia
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
+ before_action :authenticate_user!
6
7
  before_action :load_proxy_deposit_request, only: :create
7
8
  load_and_authorize_resource :proxy_deposit_request, parent: false, except: :index
8
9
  before_action :authorize_depositor_by_id, only: [:new, :create]
@@ -7,6 +7,6 @@ class ErrorsController < ApplicationController
7
7
 
8
8
  def render_routing_error(exception)
9
9
  logger.error("Rendering 404 page due to exception: #{exception.inspect} - #{exception.backtrace if exception.respond_to? :backtrace}")
10
- render template: '/error/404', layout: "error", formats: [:html], status: 404
10
+ render '404', layout: "error", status: 404
11
11
  end
12
12
  end
@@ -7,6 +7,7 @@ module Sufia
7
7
  # the search query. While at the same time allowing us not to return Collections
8
8
  # when a work in the collection matches the query.
9
9
  solr_doc[Solrizer.solr_name('file_set_ids', :symbol)] = solr_doc[Solrizer.solr_name('member_ids', :symbol)]
10
+ solr_doc[Solrizer.solr_name('resource_type', :facetable)] = object.resource_type
10
11
  end
11
12
  end
12
13
  end
@@ -1,16 +1,22 @@
1
1
  module Sufia
2
2
  class AdminStatsPresenter
3
- attr_reader :limit, :start_date, :end_date, :stats_filters
3
+ attr_reader :limit, :stats_filters
4
4
 
5
5
  def initialize(stats_filters, limit)
6
6
  @stats_filters = stats_filters
7
- @start_date = stats_filters[:start_date]
8
- @end_date = stats_filters[:end_date]
9
7
  @limit = limit
10
8
  end
11
9
 
10
+ def start_date
11
+ @start_date ||= Time.zone.parse(stats_filters[:start_date]).beginning_of_day if stats_filters[:start_date].present?
12
+ end
13
+
14
+ def end_date
15
+ @end_date ||= Time.zone.parse(stats_filters[:end_date]).end_of_day if stats_filters[:end_date].present?
16
+ end
17
+
12
18
  def depositors
13
- @depositors ||= Sufia::Admin::DepositorStats.new(start_date, end_date).depositors
19
+ @depositors ||= Sufia::Statistics::Depositors::Summary.new(start_date, end_date).depositors
14
20
  end
15
21
 
16
22
  def recent_users
@@ -18,15 +24,15 @@ module Sufia
18
24
  end
19
25
 
20
26
  def active_users
21
- @active_users ||= stats.top_depositors
27
+ @active_users ||= Sufia::Statistics::Works::ByDepositor.new(limit).query
22
28
  end
23
29
 
24
30
  def top_formats
25
- @top_formats ||= stats.top_formats
31
+ @top_formats ||= Sufia::Statistics::FileSets::ByFormat.new(limit).query
26
32
  end
27
33
 
28
- def files_count
29
- @files_count ||= stats.document_by_permission
34
+ def works_count
35
+ @works_count ||= Sufia::Statistics::Works::Count.new(start_date, end_date).by_permission
30
36
  end
31
37
 
32
38
  def users_count
@@ -37,16 +43,16 @@ module Sufia
37
43
  if start_date.blank?
38
44
  "unfiltered"
39
45
  elsif end_date.blank?
40
- "#{start_date} to #{Date.current}"
46
+ "#{start_date.to_date.to_formatted_s(:standard)} to #{Date.current.to_formatted_s(:standard)}"
41
47
  else
42
- "#{start_date} to #{end_date}"
48
+ "#{start_date.to_date.to_formatted_s(:standard)} to #{end_date.to_date.to_formatted_s(:standard)}"
43
49
  end
44
50
  end
45
51
 
46
52
  private
47
53
 
48
54
  def stats
49
- @stats ||= SystemStats.new(limit, start_date, end_date)
55
+ @stats ||= Sufia::Statistics::SystemStats.new(limit, start_date, end_date)
50
56
  end
51
57
  end
52
58
  end
@@ -4,4 +4,33 @@ class Sufia::CatalogSearchBuilder < Sufia::SearchBuilder
4
4
  :add_advanced_parse_q_to_solr,
5
5
  :show_works_or_works_that_contain_files
6
6
  ]
7
+
8
+ # show both works that match the query and works that contain files that match the query
9
+ def show_works_or_works_that_contain_files(solr_parameters)
10
+ return if solr_parameters[:q].blank?
11
+ solr_parameters[:user_query] = solr_parameters[:q]
12
+ solr_parameters[:q] = new_query
13
+ end
14
+
15
+ private
16
+
17
+ # the {!lucene} gives us the OR syntax
18
+ def new_query
19
+ "{!lucene}#{interal_query(dismax_query)} #{interal_query(join_for_works_from_files)}"
20
+ end
21
+
22
+ # the _query_ allows for another parser (aka dismax)
23
+ def interal_query(query_value)
24
+ "_query_:\"#{query_value}\""
25
+ end
26
+
27
+ # the {!dismax} causes the query to go against the query fields
28
+ def dismax_query
29
+ "{!dismax v=$user_query}"
30
+ end
31
+
32
+ # join from file id to work relationship solrized file_set_ids_ssim
33
+ def join_for_works_from_files
34
+ "{!join from=#{ActiveFedora.id_field} to=file_set_ids_ssim}#{dismax_query}"
35
+ end
7
36
  end
@@ -6,4 +6,11 @@ class Sufia::MyCollectionsSearchBuilder < Sufia::SearchBuilder
6
6
  :show_only_resources_deposited_by_current_user,
7
7
  :show_only_collections
8
8
  ]
9
+
10
+ def show_only_collections(solr_parameters)
11
+ solr_parameters[:fq] ||= []
12
+ solr_parameters[:fq] += [
13
+ ActiveFedora::SolrQueryBuilder.construct_query_for_rel(has_model: Collection.to_class_uri)
14
+ ]
15
+ end
9
16
  end
@@ -2,7 +2,13 @@
2
2
  class Sufia::MyHighlightsSearchBuilder < Sufia::SearchBuilder
3
3
  include Sufia::MySearchBuilderBehavior
4
4
 
5
- self.default_processor_chain += [
6
- :show_only_highlighted_works
7
- ]
5
+ self.default_processor_chain += [:show_only_highlighted_works]
6
+
7
+ def show_only_highlighted_works(solr_parameters)
8
+ ids = scope.current_user.trophies.pluck(:work_id)
9
+ solr_parameters[:fq] ||= []
10
+ solr_parameters[:fq] += [
11
+ ActiveFedora::SolrQueryBuilder.construct_query_for_ids(ids)
12
+ ]
13
+ end
8
14
  end
@@ -2,5 +2,12 @@
2
2
  class Sufia::MySharesSearchBuilder < Sufia::SearchBuilder
3
3
  include Sufia::MySearchBuilderBehavior
4
4
 
5
- self.default_processor_chain = default_processor_chain - [:filter_models] + [:show_only_shared_files]
5
+ self.default_processor_chain += [:show_only_shared_files]
6
+
7
+ def show_only_shared_files(solr_parameters)
8
+ solr_parameters[:fq] ||= []
9
+ solr_parameters[:fq] += [
10
+ "-" + ActiveFedora::SolrQueryBuilder.construct_query_for_rel(depositor: scope.current_user.user_key)
11
+ ]
12
+ end
6
13
  end
@@ -4,13 +4,6 @@ class Sufia::SearchBuilder < Blacklight::SearchBuilder
4
4
  include Hydra::AccessControlsEnforcement
5
5
  include CurationConcerns::SearchFilters
6
6
 
7
- def show_only_collections(solr_parameters)
8
- solr_parameters[:fq] ||= []
9
- solr_parameters[:fq] += [
10
- ActiveFedora::SolrQueryBuilder.construct_query_for_rel(has_model: Collection.to_class_uri)
11
- ]
12
- end
13
-
14
7
  def show_only_resources_deposited_by_current_user(solr_parameters)
15
8
  solr_parameters[:fq] ||= []
16
9
  solr_parameters[:fq] += [
@@ -24,56 +17,4 @@ class Sufia::SearchBuilder < Blacklight::SearchBuilder
24
17
  ActiveFedora::SolrQueryBuilder.construct_query_for_rel(has_model: ::GenericWork.to_class_uri)
25
18
  ]
26
19
  end
27
-
28
- def show_only_shared_files(solr_parameters)
29
- solr_parameters[:fq] ||= []
30
- solr_parameters[:fq] += [
31
- "-" + ActiveFedora::SolrQueryBuilder.construct_query_for_rel(depositor: scope.current_user.user_key)
32
- ]
33
- end
34
-
35
- def show_only_highlighted_works(solr_parameters)
36
- ids = scope.current_user.trophies.pluck(:work_id)
37
- solr_parameters[:fq] ||= []
38
- solr_parameters[:fq] += [
39
- ActiveFedora::SolrQueryBuilder.construct_query_for_ids(ids)
40
- ]
41
- end
42
-
43
- # Limits search results just to GenericWorks and collections
44
- # @param solr_parameters the current solr parameters
45
- # @param user_parameters the current user-submitted parameters
46
- def only_works_and_collections(solr_parameters)
47
- solr_parameters[:fq] ||= []
48
- solr_parameters[:fq] << "#{Solrizer.solr_name('has_model', :symbol)}:(\"GenericWork\" \"Collection\")"
49
- end
50
-
51
- # show both works that match the query and works that contain files that match the query
52
- def show_works_or_works_that_contain_files(solr_parameters)
53
- return if solr_parameters[:q].blank?
54
- solr_parameters[:user_query] = solr_parameters[:q]
55
- solr_parameters[:q] = new_query
56
- end
57
-
58
- protected
59
-
60
- # the {!lucene} gives us the OR syntax
61
- def new_query
62
- "{!lucene}#{interal_query(dismax_query)} #{interal_query(join_for_works_from_files)}"
63
- end
64
-
65
- # the _query_ allows for another parser (aka dismax)
66
- def interal_query(query_value)
67
- "_query_:\"#{query_value}\""
68
- end
69
-
70
- # the {!dismax} causes the query to go against the query fields
71
- def dismax_query
72
- "{!dismax v=$user_query}"
73
- end
74
-
75
- # join from file id to work relationship solrized file_set_ids_ssim
76
- def join_for_works_from_files
77
- "{!join from=#{ActiveFedora.id_field} to=file_set_ids_ssim}#{dismax_query}"
78
- end
79
20
  end
@@ -13,30 +13,47 @@ module Sufia
13
13
  # ` client_email: GOOGLE_OAUTH_CLIENT_EMAIL`
14
14
  # @return [Hash] A hash containing five keys: 'app_name', 'app_version', 'client_email', 'privkey_path', 'privkey_secret'
15
15
  def self.config
16
- @config ||= YAML.load(File.read(File.join(Rails.root, 'config', 'analytics.yml')))['analytics']
16
+ @config ||= load_config
17
17
  end
18
+ private_class_method :config
19
+
20
+ def self.load_config
21
+ filename = File.join(Rails.root, 'config', 'analytics.yml')
22
+ yaml = YAML.load(File.read(filename))
23
+ unless yaml
24
+ Rails.logger.error("Unable to fetch any keys from #{filename}.")
25
+ return
26
+ end
27
+ yaml.fetch('analytics')
28
+ end
29
+ private_class_method :load_config
18
30
 
19
31
  # Generate an OAuth2 token for Google Analytics
20
32
  # @return [OAuth2::AccessToken] An OAuth2 access token for GA
21
33
  def self.token
22
34
  scope = 'https://www.googleapis.com/auth/analytics.readonly'
23
- client = Google::APIClient.new(application_name: config['app_name'],
24
- application_version: config['app_version'])
25
- key = Google::APIClient::PKCS12.load_key(config['privkey_path'],
26
- config['privkey_secret'])
27
- service_account = Google::APIClient::JWTAsserter.new(config['client_email'], scope,
35
+ client = Google::APIClient.new(application_name: config.fetch('app_name'),
36
+ application_version: config.fetch('app_version'))
37
+ key = Google::APIClient::PKCS12.load_key(config.fetch('privkey_path'),
38
+ config.fetch('privkey_secret'))
39
+ service_account = Google::APIClient::JWTAsserter.new(config.fetch('client_email'),
40
+ scope,
28
41
  key)
29
42
  client.authorization = service_account.authorize
30
- oauth_client = OAuth2::Client.new('', '', authorize_url: 'https://accounts.google.com/o/oauth2/auth',
31
- token_url: 'https://accounts.google.com/o/oauth2/token')
43
+ oauth_client = OAuth2::Client.new('',
44
+ '',
45
+ authorize_url: 'https://accounts.google.com/o/oauth2/auth',
46
+ token_url: 'https://accounts.google.com/o/oauth2/token')
32
47
  OAuth2::AccessToken.new(oauth_client, client.authorization.access_token)
33
48
  end
49
+ private_class_method :token
34
50
 
35
51
  # Return a user object linked to a Google Analytics account
36
52
  # @return [Legato::User] A user account wit GA access
37
53
  def self.user
38
54
  Legato::User.new(token)
39
55
  end
56
+ private_class_method :user
40
57
 
41
58
  # Return a Google Analytics profile matching specified ID
42
59
  # @ return [Legato::Management::Profile] A user profile associated with GA
@@ -1,6 +1,6 @@
1
1
  module Sufia
2
2
  class QueryService
3
- # query to find generic files created during the time range
3
+ # query to find works created during the time range
4
4
  # @param [DateTime] start_datetime starting date time for range query
5
5
  # @param [DateTime] end_datetime ending date time for range query
6
6
  def find_by_date_created(start_datetime, end_datetime = nil)
@@ -0,0 +1,13 @@
1
+ module Sufia
2
+ module Statistics
3
+ module Collections
4
+ class OverTime < Statistics::OverTime
5
+ private
6
+
7
+ def relation
8
+ Collection
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,54 @@
1
+ # Gather information about the depositors who have contributed to the repository
2
+ module Sufia
3
+ module Statistics
4
+ module Depositors
5
+ class Summary
6
+ include Blacklight::SearchHelper
7
+
8
+ # @param [Time] start_date optionally specify the start date to gather the stats from
9
+ # @param [Time] end_date optionally specify the end date to gather the stats from
10
+ def initialize(start_date, end_date)
11
+ @start_dt = start_date
12
+ @end_dt = end_date
13
+ end
14
+
15
+ attr_accessor :start_dt, :end_dt
16
+
17
+ def depositors
18
+ # step through the array by twos to get each pair
19
+ results.map do |key, deposits|
20
+ user = ::User.find_by_user_key(key)
21
+ { key: key, deposits: deposits, user: user }
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ delegate :blacklight_config, to: CatalogController
28
+ delegate :depositor_field, to: DepositSearchBuilder
29
+
30
+ # results come from Solr in an array where the first item is the user and
31
+ # the second item is the count
32
+ # [ abc123, 55, ccczzz, 205 ]
33
+ # @return [#each] an enumerable object of tuples (user and count)
34
+ def results
35
+ facet_results = repository.search(query)
36
+ facet_results.facet_fields[depositor_field].each_slice(2)
37
+ end
38
+
39
+ def search_builder
40
+ DepositSearchBuilder.new([:include_depositor_facet], self)
41
+ end
42
+
43
+ # TODO: This can probably be pushed into the DepositSearchBuilder
44
+ def query
45
+ search_builder.merge(q: date_query).query
46
+ end
47
+
48
+ def date_query
49
+ Sufia::QueryService.new.build_date_query(start_dt, end_dt) unless start_dt.blank?
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,14 @@
1
+ module Sufia
2
+ module Statistics
3
+ module FileSets
4
+ class ByFormat < Statistics::TermQuery
5
+ private
6
+
7
+ # Returns 'file_format_sim'
8
+ def index_key
9
+ Solrizer.solr_name('file_format', :facetable)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,7 +1,7 @@
1
1
  module Sufia
2
2
  module Statistics
3
3
  # An abstract class for generating cumulative graphs
4
- # you must provide a `point` method in the concrete class
4
+ # you must provide a `relation` method in the concrete class
5
5
  class OverTime
6
6
  # @param [Fixnum] delta_x change in x (in days)
7
7
  # @param [Time] x_min minimum date
@@ -24,12 +24,20 @@ module Sufia
24
24
 
25
25
  private
26
26
 
27
+ def point(min, max)
28
+ relation.where(query(min, max)).count
29
+ end
30
+
31
+ def query(min, max)
32
+ QueryService.new.build_date_query(min, max)
33
+ end
34
+
27
35
  def size
28
36
  ((@x_max - @x_min) / @delta_x.days.to_i).ceil
29
37
  end
30
38
 
31
- def point
32
- raise NotImplementedError, "Implement the point method in a base class"
39
+ def relation
40
+ raise NotImplementedError, "Implement the relation method in a concrete class"
33
41
  end
34
42
  end
35
43
  end