analytics_plane 1.0.2

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 (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +8 -0
  5. data/CHANGELOG.md +5 -0
  6. data/CODE_OF_CONDUCT.md +132 -0
  7. data/Gemfile +13 -0
  8. data/Gemfile.lock +271 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +468 -0
  11. data/Rakefile +12 -0
  12. data/analytics_plane.gemspec +31 -0
  13. data/bin/console +11 -0
  14. data/bin/setup +8 -0
  15. data/lib/analytics_plane/adapters/active_record/chart_query.rb +106 -0
  16. data/lib/analytics_plane/adapters/active_record/report_query.rb +88 -0
  17. data/lib/analytics_plane/adapters/active_record_adapter.rb +39 -0
  18. data/lib/analytics_plane/adapters/base.rb +21 -0
  19. data/lib/analytics_plane/adapters/registry.rb +23 -0
  20. data/lib/analytics_plane/builders/chart_builder.rb +65 -0
  21. data/lib/analytics_plane/builders/report_builder.rb +50 -0
  22. data/lib/analytics_plane/data_sources/base.rb +13 -0
  23. data/lib/analytics_plane/data_sources/registry.rb +24 -0
  24. data/lib/analytics_plane/railtie.rb +10 -0
  25. data/lib/analytics_plane/registrar.rb +15 -0
  26. data/lib/analytics_plane/services/dataset_fetcher.rb +17 -0
  27. data/lib/analytics_plane/version.rb +5 -0
  28. data/lib/analytics_plane.rb +23 -0
  29. data/lib/generators/fios/chart/chart_generator.rb +34 -0
  30. data/lib/generators/fios/dashboard/dashboard_generator.rb +41 -0
  31. data/lib/generators/fios/data_source/data_source_generator.rb +16 -0
  32. data/lib/generators/fios/dataset/dataset_generator.rb +34 -0
  33. data/lib/generators/fios/install/install_generator.rb +15 -0
  34. data/lib/generators/fios/report/report_generator.rb +34 -0
  35. data/lib/generators/fios/templates/analytics_plane_initializer.rb +6 -0
  36. data/lib/generators/fios/templates/chart_model.rb +5 -0
  37. data/lib/generators/fios/templates/create_charts.rb +9 -0
  38. data/lib/generators/fios/templates/create_dashboard_widgets.rb +9 -0
  39. data/lib/generators/fios/templates/create_dashboards.rb +8 -0
  40. data/lib/generators/fios/templates/create_datasets.rb +11 -0
  41. data/lib/generators/fios/templates/create_reports.rb +9 -0
  42. data/lib/generators/fios/templates/dashboard_model.rb +7 -0
  43. data/lib/generators/fios/templates/dashboard_widget_model.rb +5 -0
  44. data/lib/generators/fios/templates/data_source.rb +7 -0
  45. data/lib/generators/fios/templates/dataset_model.rb +5 -0
  46. data/lib/generators/fios/templates/report_model.rb +5 -0
  47. data/sig/analytics_plane.rbs +4 -0
  48. data/spec/analytics_plane_spec.rb +7 -0
  49. data/spec/spec_helper.rb +15 -0
  50. metadata +112 -0
@@ -0,0 +1,88 @@
1
+ module AnalyticsPlane
2
+ module Adapters
3
+ module ActiveRecord
4
+ class ReportQuery
5
+ def self.add_select_clause(query, data_source, report_config)
6
+ fields = []
7
+ report_config['columns'].each do |column|
8
+ next unless column['selected']
9
+
10
+ if report_config['aggregated']
11
+ fields << column['name'] if column['group_by']
12
+ fields << "COUNT(#{column['name']}) AS 'num_#{column['name']}'" if column['count']
13
+ if column['count_distinct']
14
+ fields << "COUNT(DISTINCT #{column['name']}) AS 'num_uniq_#{column['name']}'"
15
+ end
16
+ if column['average']
17
+ if column['type'].in?(%w[date datetime])
18
+ fields << "FROM_UNIXTIME(AVG(UNIX_TIMESTAMP(#{column['name']}))) AS 'avg_#{column['name']}'"
19
+ else
20
+ fields << "AVG(#{column['name']}) AS 'avg_#{column['name']}'"
21
+ end
22
+ end
23
+ fields << "MIN(#{column['name']}) AS 'min_#{column['name']}'" if column['min']
24
+ fields << "MAX(#{column['name']}) AS 'max_#{column['name']}'" if column['max']
25
+ fields << "SUM(#{column['name']}) AS 'sum_#{column['name']}'" if column['sum']
26
+ else
27
+ fields << column['name']
28
+ end
29
+ end
30
+
31
+ fields = data_source.column_names if fields.empty?
32
+
33
+ query.select(fields)
34
+ end
35
+
36
+ def self.add_where_clause(query, report_config)
37
+ return query if report_config['filters'].blank?
38
+
39
+ report_config['filters'].each do |filter|
40
+ field = filter['name']
41
+ operator = filter['operator']
42
+ value = filter['value']
43
+
44
+ case operator
45
+ when '='
46
+ query = query.where(field => value)
47
+ when '!='
48
+ query = query.where.not(field => value)
49
+ when '>'
50
+ query = query.where("#{field} > ?", value)
51
+ when '>='
52
+ query = query.where("#{field} >= ?", value)
53
+ when '<'
54
+ query = query.where("#{field} < ?", value)
55
+ when '<='
56
+ query = query.where("#{field} <= ?", value)
57
+ when 'contains'
58
+ query = query.where("#{field} LIKE ?", "%#{value}%")
59
+ when 'starts_with'
60
+ query = query.where("#{field} LIKE ?", "#{value}%")
61
+ when 'ends_with'
62
+ query = query.where("#{field} LIKE ?", "%#{value}")
63
+ when 'one_of'
64
+ query = query.where("#{field} IN (?)", value)
65
+ when 'not_one_of'
66
+ query = query.where.not("#{field} IN (?)", value)
67
+ end
68
+ end
69
+
70
+ query
71
+ end
72
+
73
+ def self.add_group_clause(query, report_config)
74
+ return query unless report_config['aggregated']
75
+
76
+ group_fields = []
77
+ report_config['columns'].each do |column|
78
+ next unless column['selected'] && column['group_by']
79
+
80
+ group_fields << column['name']
81
+ end
82
+
83
+ query.group(group_fields)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,39 @@
1
+ module AnalyticsPlane
2
+ module Adapters
3
+ class ActiveRecordAdapter
4
+ include AnalyticsPlane::Adapters::Base
5
+
6
+ def self.adapter_key
7
+ :active_record
8
+ end
9
+
10
+ def self.fetch_chart_data(data_source, chart)
11
+ chart_config = chart.configuration
12
+
13
+ query = data_source.all
14
+ query = AnalyticsPlane::Adapters::ActiveRecord::ChartQuery.add_select_clause(query, chart_config)
15
+ query = AnalyticsPlane::Adapters::ActiveRecord::ChartQuery.add_where_clause(query, chart_config)
16
+ query = AnalyticsPlane::Adapters::ActiveRecord::ChartQuery.add_group_clause(query, chart_config)
17
+ data = query.to_a
18
+
19
+ {
20
+ series: AnalyticsPlane::Adapters::ActiveRecord::ChartQuery.parse_series_data(data, chart_config),
21
+ categories: AnalyticsPlane::Adapters::ActiveRecord::ChartQuery.parse_category_data(data, chart_config, data_source),
22
+ meta: {
23
+ title: chart.name,
24
+ chart_type: chart_config['chart_type']
25
+ }
26
+ }
27
+ end
28
+
29
+ def self.fetch_report_data(data_source, report)
30
+ report_config = report.configuration
31
+
32
+ query = data_source.all
33
+ query = AnalyticsPlane::Adapters::ActiveRecord::ReportQuery.add_select_clause(query, data_source, report_config)
34
+ query = AnalyticsPlane::Adapters::ActiveRecord::ReportQuery.add_where_clause(query, report_config)
35
+ AnalyticsPlane::Adapters::ActiveRecord::ReportQuery.add_group_clause(query, report_config)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,21 @@
1
+ module AnalyticsPlane
2
+ module Adapters
3
+ module Base
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def adapter_key
8
+ raise NotImplementedError
9
+ end
10
+
11
+ def fetch_chart_data(data_source, chart)
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def fetch_report_data(data_source, report)
16
+ raise NotImplementedError
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ module AnalyticsPlane
2
+ module Adapters
3
+ class Registry
4
+ mattr_accessor :adapters, default: {}
5
+
6
+ def self.register(klass)
7
+ key = klass.adapter_key.to_sym
8
+ raise "Duplicate dataset adapter key: #{key}" if @adapters.key?(key)
9
+ @adapters[key] = klass
10
+ end
11
+
12
+ def self.fetch(key)
13
+ @adapters.fetch(key.to_sym) do
14
+ raise KeyError, "Unknown dataset adapter: #{key}"
15
+ end
16
+ end
17
+
18
+ def self.clear!
19
+ @adapters = {}
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,65 @@
1
+ module AnalyticsPlane
2
+ module Builders
3
+ class ChartBuilder
4
+ def self.build(chart)
5
+ data = fetch_data(chart)
6
+ parse_query_results(data)
7
+ end
8
+
9
+ def self.fetch_data(chart)
10
+ chart_config = chart.configuration || {}
11
+ dataset = Dataset.find(chart_config['dataset_id'])
12
+ AnalyticsPlane::Services::DatasetFetcher.fetch_chart_data(dataset, chart)
13
+ end
14
+
15
+ def self.parse_query_results(data)
16
+ {
17
+ 'chart': {
18
+ 'type': data[:meta][:chart_type]
19
+ },
20
+
21
+ 'title': {
22
+ 'text': data[:meta][:title]
23
+ },
24
+
25
+ 'xAxis': {
26
+ 'categories': data[:categories]
27
+ },
28
+
29
+ 'yAxis': {
30
+ 'title': {
31
+ 'text': nil
32
+ }
33
+ },
34
+
35
+ 'series': data[:series]
36
+ }
37
+ end
38
+
39
+ def self.build_csv(chart)
40
+ {
41
+ headers: csv_headers(chart),
42
+ rows: fetch_data(chart)
43
+ }
44
+ end
45
+
46
+ def self.csv_headers(chart)
47
+ chart_config = chart.configuration || {}
48
+
49
+ fields = []
50
+ fields << chart_config['x_axis']['attr']
51
+
52
+ chart_config['y_axes'].each do |column|
53
+ fields << "num_#{column['attr']}" if column['count']
54
+ fields << "num_uniq_#{column['attr']}" if column['count_distinct']
55
+ fields << "avg_#{column['attr']}" if column['average']
56
+ fields << "min_#{column['attr']}" if column['min']
57
+ fields << "max_#{column['attr']}" if column['max']
58
+ fields << "sum_#{column['attr']}" if column['sum']
59
+ end
60
+
61
+ fields
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,50 @@
1
+ module AnalyticsPlane
2
+ module Builders
3
+ class ReportBuilder
4
+ def self.build(report)
5
+ fetch_data(report)
6
+ end
7
+
8
+ def self.fetch_data(report)
9
+ report_config = report.configuration || {}
10
+ dataset = Dataset.find(report_config['dataset_id'])
11
+ AnalyticsPlane::Services::DatasetFetcher.fetch_report_data(dataset, report)
12
+ end
13
+
14
+ def self.build_csv(report)
15
+ {
16
+ headers: csv_headers(report),
17
+ rows: fetch_data(report)
18
+ }
19
+ end
20
+
21
+ def self.csv_headers(report)
22
+ report_config = report.configuration || {}
23
+
24
+ dataset = Dataset.find(report_config['dataset_id'])
25
+ dataset_class = AnalyticsPlane::Services::DatasetFetcher.fetch_report_data(dataset, report)
26
+
27
+ return dataset_class.column_names if report_config['columns'].blank?
28
+
29
+ fields = []
30
+ report_config['columns'].each do |column|
31
+ next unless column['selected']
32
+
33
+ if report_config['aggregated']
34
+ fields << column['name'] if column['group_by']
35
+ fields << "num_#{column['name']}" if column['count']
36
+ fields << "num_uniq_#{column['name']}" if column['count_distinct']
37
+ fields << "avg_#{column['name']}" if column['average']
38
+ fields << "min_#{column['name']}" if column['min']
39
+ fields << "max_#{column['name']}" if column['max']
40
+ fields << "sum_#{column['name']}" if column['sum']
41
+ else
42
+ fields << column['name']
43
+ end
44
+ end
45
+
46
+ fields
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,13 @@
1
+ module AnalyticsPlane
2
+ module DataSources
3
+ module Base
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def dataset_key
8
+ raise NotImplementedError
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ module AnalyticsPlane
2
+ module DataSources
3
+ class Registry
4
+ mattr_accessor :data_sources, default: {}
5
+
6
+ def self.register(klass)
7
+ key = klass.dataset_key.to_sym
8
+ raise "Duplicate dataset key: #{key}" if data_sources.key?(key)
9
+
10
+ @data_sources[key] = klass
11
+ end
12
+
13
+ def self.fetch(key)
14
+ @data_sources.fetch(key.to_sym) do
15
+ raise KeyError, "Unknown dataset: #{key}"
16
+ end
17
+ end
18
+
19
+ def self.clear!
20
+ @data_sources = {}
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ module AnalyticsPlane
2
+ class Railtie < Rails::Railtie
3
+ initializer "analytics_plane.prepare" do
4
+ Rails.application.config.to_prepare do
5
+ AnalyticsPlane::DataSources::Registry.clear!
6
+ AnalyticsPlane::Adapters::Registry.clear!
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ module AnalyticsPlane
2
+ module Registrar
3
+ def self.register(&block)
4
+ instance_eval(&block)
5
+ end
6
+
7
+ def self.data_source(klass)
8
+ AnalyticsPlane::DataSources::Registry.register(klass)
9
+ end
10
+
11
+ def self.adapter(klass)
12
+ AnalyticsPlane::Adapters::Registry.register(klass)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module AnalyticsPlane
2
+ module Services
3
+ class DatasetFetcher
4
+ def self.fetch_chart_data(dataset, chart)
5
+ adapter = AnalyticsPlane::Adapters::Registry.fetch(dataset.adapter)
6
+ data_source = AnalyticsPlane::DataSources::Registry.fetch(dataset.slug)
7
+ data = adapter.fetch_chart_data(data_source, chart)
8
+ end
9
+
10
+ def self.fetch_report_data(dataset, report)
11
+ adapter = AnalyticsPlane::Adapters::Registry.fetch(dataset.adapter)
12
+ data_source = AnalyticsPlane::DataSources::Registry.fetch(dataset.slug)
13
+ data = adapter.fetch_report_data(data_source, report)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnalyticsPlane
4
+ VERSION = "1.0.2"
5
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "active_support/concern"
5
+ require "rails"
6
+
7
+ require_relative "analytics_plane/adapters/base"
8
+ require_relative "analytics_plane/adapters/active_record/chart_query"
9
+ require_relative "analytics_plane/adapters/active_record/report_query"
10
+ require_relative "analytics_plane/adapters/active_record_adapter"
11
+ require_relative "analytics_plane/adapters/registry"
12
+ require_relative "analytics_plane/builders/chart_builder"
13
+ require_relative "analytics_plane/builders/report_builder"
14
+ require_relative "analytics_plane/data_sources/base"
15
+ require_relative "analytics_plane/data_sources/registry"
16
+ require_relative "analytics_plane/services/dataset_fetcher"
17
+ require_relative "analytics_plane/registrar"
18
+ require_relative "analytics_plane/version"
19
+ require_relative "analytics_plane/railtie"
20
+
21
+ module AnalyticsPlane
22
+ class Error < StandardError; end
23
+ end
@@ -0,0 +1,34 @@
1
+ require "rails/generators"
2
+ require "rails/generators/named_base"
3
+ require "rails/generators/migration"
4
+
5
+ module AnalyticsPlane
6
+ module Generators
7
+ class ChartGenerator < Rails::Generators::NamedBase
8
+ include Rails::Generators::Migration
9
+
10
+ source_root File.expand_path("../templates", __dir__)
11
+
12
+ desc "Creates a Chart model and migration for AnalyticsPlane"
13
+
14
+ def create_model
15
+ template "chart_model.rb", "app/models/#{file_name}.rb"
16
+ end
17
+
18
+ def create_chart_migration
19
+ migration_template(
20
+ "create_charts.rb",
21
+ "db/migrate/create_#{file_name.pluralize}.rb"
22
+ )
23
+ end
24
+
25
+ def self.next_migration_number(dirname)
26
+ if @next_migration_number
27
+ @next_migration_number += 1
28
+ else
29
+ @next_migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S")
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,41 @@
1
+ require "rails/generators"
2
+ require "rails/generators/named_base"
3
+ require "rails/generators/migration"
4
+
5
+ module AnalyticsPlane
6
+ module Generators
7
+ class DashboardGenerator < Rails::Generators::NamedBase
8
+ include Rails::Generators::Migration
9
+
10
+ source_root File.expand_path("../templates", __dir__)
11
+
12
+ desc "Creates a Dashboard model, Dashboard Widget model, and migrations for AnalyticsPlane"
13
+
14
+ def create_dashboard_model
15
+ template "dashboard_model.rb", "app/models/#{file_name}.rb"
16
+ end
17
+
18
+ def create_widget_model
19
+ template "dashboard_widget_model.rb", "app/models/#{file_name}_widget.rb"
20
+ end
21
+
22
+ def create_dashboard_migration
23
+ migration_template(
24
+ "create_dashboards.rb",
25
+ "db/migrate/create_#{file_name.pluralize}.rb"
26
+ )
27
+ end
28
+
29
+ def create_widget_migration
30
+ migration_template(
31
+ "create_dashboard_widgets.rb",
32
+ "db/migrate/create_#{file_name.pluralize}_widget.rb"
33
+ )
34
+ end
35
+
36
+ def self.next_migration_number(dirname)
37
+ @next_migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S")
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,16 @@
1
+ require "rails/generators"
2
+ require "rails/generators/named_base"
3
+
4
+ module AnalyticsPlane
5
+ module Generators
6
+ class DataSourceGenerator < Rails::Generators::NamedBase
7
+ source_root File.expand_path("../templates", __dir__)
8
+
9
+ desc "Creates a AnalyticsPlane data source"
10
+
11
+ def create_data_source
12
+ template "data_source.rb", "app/datasets/#{file_name}.rb"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ require "rails/generators"
2
+ require "rails/generators/named_base"
3
+ require "rails/generators/migration"
4
+
5
+ module AnalyticsPlane
6
+ module Generators
7
+ class DatasetGenerator < Rails::Generators::NamedBase
8
+ include Rails::Generators::Migration
9
+
10
+ source_root File.expand_path("../templates", __dir__)
11
+
12
+ desc "Creates a Dataset model and migration for AnalyticsPlane"
13
+
14
+ def create_dataset
15
+ template "dataset_model.rb", "app/models/#{file_name}.rb"
16
+ end
17
+
18
+ def create_dataset_migration
19
+ migration_template(
20
+ "create_datasets.rb",
21
+ "db/migrate/create_#{file_name.pluralize}.rb"
22
+ )
23
+ end
24
+
25
+ def self.next_migration_number(dirname)
26
+ if @next_migration_number
27
+ @next_migration_number += 1
28
+ else
29
+ @next_migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S")
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,15 @@
1
+ require "rails/generators"
2
+
3
+ module AnalyticsPlane
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("../templates", __dir__)
7
+
8
+ desc "Installs AnalyticsPlane and creates the initializer"
9
+
10
+ def create_initializer
11
+ template "analytics_plane_initializer.rb", "config/initializers/analytics_plane.rb"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ require "rails/generators"
2
+ require "rails/generators/named_base"
3
+ require "rails/generators/migration"
4
+
5
+ module AnalyticsPlane
6
+ module Generators
7
+ class ReportGenerator < Rails::Generators::NamedBase
8
+ include Rails::Generators::Migration
9
+
10
+ source_root File.expand_path("../templates", __dir__)
11
+
12
+ desc "Creates a Report model and migration for AnalyticsPlane"
13
+
14
+ def create_model
15
+ template "report_model.rb", "app/models/#{file_name}.rb"
16
+ end
17
+
18
+ def create_report_migration
19
+ migration_template(
20
+ "create_reports.rb",
21
+ "db/migrate/create_#{file_name.pluralize}.rb"
22
+ )
23
+ end
24
+
25
+ def self.next_migration_number(dirname)
26
+ if @next_migration_number
27
+ @next_migration_number += 1
28
+ else
29
+ @next_migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S")
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,6 @@
1
+ Rails.application.config.to_prepare do
2
+ AnalyticsPlane::Registrar.register do
3
+ # adapter AnalyticsPlane::Adapters::ActiveRecordAdapter
4
+ # data_source EmployeesDataSource
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class <%= class_name %> < ApplicationRecord
2
+ store_accessor :configuration, :chart_type, :dataset_id, :x_axis, :y_axes, :filters
3
+
4
+ validates :name, presence: true, uniqueness: true
5
+ end
@@ -0,0 +1,9 @@
1
+ class Create<%= class_name.pluralize %> < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :<%= file_name.pluralize %> do |t|
4
+ t.string :name, null: false
5
+ t.json :configuration, null: false, default: {}
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class Create<%= class_name %>Widgets < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :<%= file_name %>_widgets do |t|
4
+ t.references :dashboard, foreign_key: true
5
+ t.json :layout, null: false, default: {}
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ class Create<%= class_name.pluralize %> < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :<%= file_name.pluralize %> do |t|
4
+ t.string :name
5
+ t.timestamps
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ class Create<%= class_name.pluralize %> < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :<%= file_name.pluralize %> do |t|
4
+ t.string :slug, null: false
5
+ t.string :name, null: false
6
+ t.text :description
7
+ t.string :adapter, null: false
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ class Create<%= class_name.pluralize %> < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :<%= file_name.pluralize %> do |t|
4
+ t.string :name, null: false
5
+ t.json :configuration, null: false, default: {}
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ class <%= class_name %> < ApplicationRecord
2
+ has_many :dashboard_widgets, dependent: :destroy
3
+
4
+ accepts_nested_attributes_for :dashboard_widgets, allow_destroy: true
5
+
6
+ validates :name, presence: true, uniqueness: true
7
+ end
@@ -0,0 +1,5 @@
1
+ class <%= class_name %>Widget < ApplicationRecord
2
+ store_accessor :layout, :pos_x, :pos_y, :width, :height
3
+
4
+ belongs_to :dashboard
5
+ end