forest_admin_datasource_active_record 1.0.0.pre.beta.21

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: aace6865ce55eb722690ad3a410c31144c81a2ac7aa3f9db0d5dcb7d706e4544
4
+ data.tar.gz: 1ced7531287ce44c3d834875980471ccfaab5a7f2ea401ab84405f9547da96c8
5
+ SHA512:
6
+ metadata.gz: 9c6f07ba223bcbe79dac1bd5ab787378e4b68e9e0bc75576e67cc91d7d5f7de6cac8b2d60c94d66716b7140a93c8d5b19206336e0fa6efae8d419b93a07f0565
7
+ data.tar.gz: fd9d4172f74352ff3226513a2bc7b59f04f26b10ba04948c6d5ee87f8cc73e9f14575fe657cb36ea1892a2d5239c893cb7e7d7fc86a5683b81c0edd959d691ca
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # ForestAdminDatasourceActiveRecord
2
+
3
+ TODO: Delete this and the text below, and describe your gem
4
+
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/forest_admin_datasource_active_record`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+
7
+ ## Installation
8
+
9
+ TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
+
11
+ Install the gem and add to the application's Gemfile by executing:
12
+
13
+ $ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
14
+
15
+ If bundler is not being used to manage dependencies, install the gem by executing:
16
+
17
+ $ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Development
24
+
25
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
26
+
27
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
28
+
29
+ ## Contributing
30
+
31
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/forest_admin_datasource_active_record.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,37 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
3
+
4
+ require_relative "lib/forest_admin_datasource_active_record/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "forest_admin_datasource_active_record"
8
+ spec.version = ForestAdminDatasourceActiveRecord::VERSION
9
+ spec.authors = ["Matthieu", "Nicolas"]
10
+ spec.email = ["matthv@gmail.com", "nicolasalexandre9@gmail.com"]
11
+ spec.homepage = "https://www.forestadmin.com"
12
+ spec.summary = "Ruby agent for Forest Admin."
13
+ spec.description = "Forest is a modern admin interface that works on all major web frameworks. This gem makes Forest
14
+ admin work on any Ruby application."
15
+ spec.license = "MIT"
16
+ spec.required_ruby_version = ">= 3.0.0"
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = "https://github.com/ForestAdmin/agent-ruby"
20
+ spec.metadata["changelog_uri"] = "https://github.com/ForestAdmin/agent-ruby/CHANGELOG.md"
21
+ spec.metadata["rubygems_mfa_required"] = "false"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(__dir__) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (File.expand_path(f) == __FILE__) ||
28
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
29
+ end
30
+ end
31
+ spec.bindir = "exe"
32
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ["lib"]
34
+
35
+ spec.add_dependency "activerecord", ">= 6.1"
36
+ spec.add_dependency "zeitwerk", "~> 2.3"
37
+ end
@@ -0,0 +1,117 @@
1
+ module ForestAdminDatasourceActiveRecord
2
+ class Collection < ForestAdminDatasourceToolkit::Collection
3
+ include Parser::Column
4
+ include Parser::Relation
5
+ include ForestAdminDatasourceToolkit::Components::Query
6
+
7
+ attr_reader :model
8
+
9
+ def initialize(datasource, model)
10
+ @model = model
11
+ name = model.name.demodulize.underscore
12
+ super(datasource, name)
13
+ fetch_fields
14
+ fetch_associations
15
+ end
16
+
17
+ def list(_caller, filter, projection)
18
+ query = Utils::Query.new(self, projection, filter).build
19
+
20
+ query.all
21
+ end
22
+
23
+ def aggregate(_caller, _filter, aggregation, _limit = nil)
24
+ field = aggregation.field || '*'
25
+
26
+ [
27
+ {
28
+ value: @model.send(aggregation.operation.downcase, field),
29
+ group: []
30
+ }
31
+ ]
32
+ end
33
+
34
+ def create(_caller, data)
35
+ @model.create(data)
36
+ end
37
+
38
+ def update(_caller, filter, data)
39
+ entity = Utils::Query.new(self, nil, filter).build.first
40
+ entity.update(data)
41
+ end
42
+
43
+ def delete(_caller, filter)
44
+ entities = Utils::Query.new(self, nil, filter).build
45
+ entities&.each(&:destroy)
46
+ end
47
+
48
+ private
49
+
50
+ def fetch_fields
51
+ @model.columns_hash.each do |column_name, column|
52
+ # TODO: check is not sti column
53
+ field = ForestAdminDatasourceToolkit::Schema::ColumnSchema.new(
54
+ column_type: get_column_type(@model, column),
55
+ filter_operators: operators_for_column_type(get_column_type(@model, column)),
56
+ is_primary_key: column_name == @model.primary_key,
57
+ is_read_only: false,
58
+ is_sortable: true,
59
+ default_value: column.default,
60
+ enum_values: get_enum_values(@model, column),
61
+ # validations: get_validations(column)
62
+ validations: []
63
+ )
64
+
65
+ add_field(column_name, field)
66
+ end
67
+ end
68
+
69
+ def fetch_associations
70
+ associations(@model).each do |association|
71
+ case association.macro
72
+ when :has_one
73
+ add_field(
74
+ association.name.to_s,
75
+ ForestAdminDatasourceToolkit::Schema::Relations::OneToOneSchema.new(
76
+ foreign_collection: association.class_name.demodulize.underscore,
77
+ origin_key: association.foreign_key,
78
+ origin_key_target: association.association_primary_key
79
+ )
80
+ )
81
+ when :belongs_to
82
+ add_field(
83
+ association.name.to_s,
84
+ ForestAdminDatasourceToolkit::Schema::Relations::ManyToOneSchema.new(
85
+ foreign_collection: association.class_name.demodulize.underscore,
86
+ foreign_key: association.foreign_key,
87
+ foreign_key_target: association.association_primary_key
88
+ )
89
+ )
90
+ when :has_many
91
+ if association.through_reflection?
92
+ add_field(
93
+ association.name.to_s,
94
+ ForestAdminDatasourceToolkit::Schema::Relations::ManyToManySchema.new(
95
+ foreign_collection: association.class_name.demodulize.underscore,
96
+ origin_key: association.through_reflection.foreign_key,
97
+ origin_key_target: association.through_reflection.join_foreign_key,
98
+ foreign_key: association.join_foreign_key,
99
+ foreign_key_target: association.association_primary_key,
100
+ through_collection: association.through_reflection.class_name.demodulize.underscore
101
+ )
102
+ )
103
+ else
104
+ add_field(
105
+ association.name.to_s,
106
+ ForestAdminDatasourceToolkit::Schema::Relations::OneToManySchema.new(
107
+ foreign_collection: association.class_name.demodulize.underscore,
108
+ origin_key: association.foreign_key,
109
+ origin_key_target: association.association_primary_key
110
+ )
111
+ )
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,29 @@
1
+ require 'active_record'
2
+
3
+ module ForestAdminDatasourceActiveRecord
4
+ class Datasource < ForestAdminDatasourceToolkit::Datasource
5
+ def initialize(db_config = {})
6
+ super()
7
+ @models = []
8
+ init_orm(db_config)
9
+ generate
10
+ end
11
+
12
+ private
13
+
14
+ def generate
15
+ ActiveRecord::Base.descendants.each { |model| fetch_model(model) }
16
+ @models.each do |model|
17
+ add_collection(Collection.new(self, model))
18
+ end
19
+ end
20
+
21
+ def fetch_model(model)
22
+ @models << model unless model.abstract_class? || @models.include?(model) || !model.table_exists?
23
+ end
24
+
25
+ def init_orm(db_config)
26
+ ActiveRecord::Base.establish_connection(db_config)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,71 @@
1
+ module ForestAdminDatasourceActiveRecord
2
+ module Parser
3
+ module Column
4
+ include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
5
+
6
+ TYPES = {
7
+ boolean: 'Boolean',
8
+ datetime: 'Date',
9
+ date: 'Dateonly',
10
+ integer: 'Number',
11
+ float: 'Number',
12
+ decimal: 'Number',
13
+ json: 'Json',
14
+ jsonb: 'Json',
15
+ hstore: 'Json',
16
+ string: 'String',
17
+ text: 'String',
18
+ citext: 'String',
19
+ time: 'Time',
20
+ uuid: 'Uuid',
21
+ binary: 'Binary'
22
+ }.freeze
23
+
24
+ def get_column_type(model, column)
25
+ if model.respond_to?(:defined_enums) &&
26
+ model.defined_enums.key?(column.name)
27
+ return 'Enum'
28
+ end
29
+
30
+ is_array = (column.respond_to?(:array) && column.array == true)
31
+ is_array ? "[#{TYPES[column.type]}]" : TYPES[column.type]
32
+ end
33
+
34
+ def get_enum_values(model, column)
35
+ enum_values = []
36
+ if get_column_type(model, column) == 'Enum'
37
+ if sti_column?(model, column)
38
+ model.descendants.each { |sti_model| enum_values << sti_model.name }
39
+ else
40
+ model.defined_enums[column.name].each { |name, _value| enum_values << name }
41
+ end
42
+ end
43
+ enum_values
44
+ end
45
+
46
+ def sti_column?(model, column)
47
+ model.inheritance_column && column.name == model.inheritance_column
48
+ end
49
+
50
+ def operators_for_column_type(type)
51
+ result = [Operators::PRESENT, Operators::MISSING]
52
+ equality = [Operators::EQUAL, Operators::NOT_EQUAL, Operators::IN, Operators::NOT_IN]
53
+
54
+ if type.is_a? String
55
+ orderables = [Operators::LESS_THAN, Operators::GREATER_THAN]
56
+ strings = [Operators::LIKE, Operators::I_LIKE, Operators::NOT_CONTAINS]
57
+
58
+ result += equality if %w[Boolean Binary Enum Uuid].include?(type)
59
+
60
+ result = result + equality + orderables if %w[Date Dateonly Number].include?(type)
61
+
62
+ result = result + equality + orderables + strings if %w[String].include?(type)
63
+ end
64
+
65
+ result = result + equality + ['Includes_All'] if type.is_a? Array
66
+
67
+ result
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,21 @@
1
+ module ForestAdminDatasourceActiveRecord
2
+ module Parser
3
+ module Relation
4
+ def associations(model)
5
+ model.reflect_on_all_associations.select do |association|
6
+ !polymorphic?(association) && !active_type?(association.klass)
7
+ end
8
+ end
9
+
10
+ def polymorphic?(association)
11
+ association.options[:polymorphic]
12
+ end
13
+
14
+ # NOTICE: Ignores ActiveType::Object association during introspection and interactions.
15
+ # See the gem documentation: https://github.com/makandra/active_type
16
+ def active_type?(model)
17
+ Object.const_defined?('ActiveType::Object') && model < ActiveType::Object
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,108 @@
1
+ module ForestAdminDatasourceActiveRecord
2
+ module Parser
3
+ module Validation
4
+ def get_validations(column)
5
+ validations = []
6
+ # NOTICE: Do not consider validations if a before_validation Active Records
7
+ # Callback is detected.
8
+ return validations if @model._validation_callbacks.map(&:kind).include? :before
9
+
10
+ if @model._validators? && @model._validators[column.name.to_sym].size.positive?
11
+ @model._validators[column.name.to_sym].each do |validator|
12
+ # NOTICE: Do not consider conditional validations
13
+ next if validator.options[:if] || validator.options[:unless] || validator.options[:on]
14
+
15
+ case validator
16
+ when ActiveRecord::Validations::PresenceValidator
17
+ validations << {
18
+ type: 'is present',
19
+ message: validator.options[:message]
20
+ }
21
+ when ActiveModel::Validations::NumericalityValidator
22
+ validations = parse_numericality_validator(validator, validations)
23
+ when ActiveModel::Validations::LengthValidator
24
+ validations = parse_length_validator(validator, validations)
25
+ when ActiveModel::Validations::FormatValidator
26
+ validations = parse_format_validator(validator, validations)
27
+ end
28
+ end
29
+ end
30
+
31
+ validations
32
+ end
33
+
34
+ def parse_numericality_validator(validator, parsed_validations)
35
+ validator.options.each do |option, value|
36
+ case option
37
+ when :greater_than, :greater_than_or_equal_to
38
+ parsed_validations << {
39
+ type: 'is greater than',
40
+ value: value,
41
+ message: validator.options[:message]
42
+ }
43
+ when :less_than, :less_than_or_equal_to
44
+ parsed_validations << {
45
+ type: 'is less than',
46
+ value: value,
47
+ message: validator.options[:message]
48
+ }
49
+ end
50
+ end
51
+ end
52
+
53
+ def parse_length_validator(validator, parsed_validations)
54
+ return unless get_column_type(column) == 'String'
55
+
56
+ validator.options.each do |option, value|
57
+ case option
58
+ when :minimum
59
+ parsed_validations << {
60
+ type: 'is longer than',
61
+ value: value,
62
+ message: validator.options[:message]
63
+ }
64
+ when :maximum
65
+ parsed_validations << {
66
+ type: 'is shorter than',
67
+ value: value,
68
+ message: validator.options[:message]
69
+ }
70
+ when :is
71
+ parsed_validations << {
72
+ type: 'is longer than',
73
+ value: value,
74
+ message: validator.options[:message]
75
+ }
76
+ parsed_validations << {
77
+ type: 'is shorter than',
78
+ value: value,
79
+ message: validator.options[:message]
80
+ }
81
+ end
82
+ end
83
+ end
84
+
85
+ def parse_format_validator(validator, parsed_validations)
86
+ validator.options.each do |option, value|
87
+ case option
88
+ when :with
89
+ options = /\?([imx]){0,3}/.match(validator.options[:with].to_s)
90
+ options = options && options[1] ? options[1] : ''
91
+ regex = value.source
92
+
93
+ # NOTICE: Transform a Ruby regex into a JS one
94
+ regex = regex.sub('\\A', '^').sub('\\Z', '$').sub('\\z', '$').gsub(/\n+|\s+/, '')
95
+
96
+ parsed_validations << {
97
+ type: 'is like',
98
+ value: "/#{regex}/#{options}",
99
+ message: validator.options[:message]
100
+ }
101
+ end
102
+ end
103
+
104
+ parsed_validations
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,118 @@
1
+ module ForestAdminDatasourceActiveRecord
2
+ module Utils
3
+ class Query
4
+ include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
5
+
6
+ def initialize(collection, projection, filter)
7
+ @collection = collection
8
+ @query = @collection.model
9
+ @projection = projection
10
+ @filter = filter
11
+ @arel_table = @collection.model.arel_table
12
+ end
13
+
14
+ def build
15
+ @query = select
16
+ @query = apply_filter
17
+
18
+ @query
19
+ end
20
+
21
+ def apply_filter
22
+ @query = apply_condition_tree(@filter.condition_tree) unless @filter.condition_tree.nil?
23
+ @query = apply_pagination(@filter.page) unless @filter.page.nil?
24
+
25
+ @query
26
+ end
27
+
28
+ def apply_pagination(page)
29
+ @query.offset(page.offset).limit(page.limit)
30
+
31
+ @query
32
+ end
33
+
34
+ def apply_condition_tree(condition_tree, aggregator = nil)
35
+ if condition_tree.is_a? Nodes::ConditionTreeBranch
36
+ aggregator = condition_tree.aggregator.downcase
37
+ condition_tree.conditions.each do |condition|
38
+ query = apply_condition_tree(condition, aggregator)
39
+ @query = @query.send(aggregator, query)
40
+ end
41
+
42
+ @query
43
+ else
44
+ compute_main_operator(condition_tree, aggregator || 'and')
45
+ end
46
+ end
47
+
48
+ def compute_main_operator(condition_tree, aggregator)
49
+ field = format_field(condition_tree.field)
50
+ value = condition_tree.value
51
+
52
+ case condition_tree.operator
53
+ when Operators::PRESENT
54
+ @query = @query.send(aggregator, @query.where.not({ field => nil }))
55
+ when Operators::EQUAL, Operators::IN
56
+ @query = @query.send(aggregator, @query.where({ field => value }))
57
+ when Operators::NOT_EQUAL, Operators::NOT_IN
58
+ @query = @query.send(aggregator, @query.where.not({ field => value }))
59
+ when Operators::GREATER_THAN
60
+ @query = @query.send(aggregator, @query.where(@arel_table[field.to_sym].gt(value)))
61
+ when Operators::LESS_THAN
62
+ @query = @query.send(aggregator, @query.where(@arel_table[field.to_sym].lt(value)))
63
+ when Operators::NOT_CONTAINS
64
+ @query = @query.send(aggregator, @query.where.not(@arel_table[field.to_sym].matches("%#{value}%")))
65
+ when Operators::CONTAINS
66
+ @query = @query.send(aggregator, @query.where(@arel_table[field.to_sym].matches("%#{value}%")))
67
+ when Operators::INCLUDES_ALL
68
+ # TODO: to implement
69
+ end
70
+
71
+ @query
72
+ end
73
+
74
+ def select
75
+ unless @projection.nil?
76
+ query_select = @projection.columns.map { |field| "#{@collection.model.table_name}.#{field}" }
77
+ @projection.relations.each do |relation, _fields|
78
+ relation_schema = @collection.fields[relation]
79
+ if relation_schema.type == 'OneToOne'
80
+ query_select.push("#{@collection.model.table_name}.#{relation_schema.origin_key_target}")
81
+ else
82
+ query_select.push("#{@collection.model.table_name}.#{relation_schema.foreign_key}")
83
+ end
84
+ end
85
+
86
+ @query = @query.select(query_select.join(', '))
87
+ @query = @query.eager_load(@projection.relations.keys.map(&:to_sym))
88
+ # TODO: replace eager_load by joins because eager_load select ALL columns of relation
89
+ # @query = @query.joins(@projection.relations.keys.map(&:to_sym))
90
+ end
91
+
92
+ @query
93
+ end
94
+
95
+ def add_join_relation(relation, relation_name)
96
+ if relation.type == 'ManyToMany'
97
+ # TODO: to implement
98
+ else
99
+ @query = @query.joins(relation_name.to_sym)
100
+ end
101
+
102
+ @query
103
+ end
104
+
105
+ def format_field(field)
106
+ if field.include?(':')
107
+ relation_name, field = field.split(':')
108
+ relation = @collection.fields[relation_name]
109
+ table_name = @collection.datasource.collection(relation.foreign_collection).model.table_name
110
+ add_join_relation(relation, relation_name)
111
+ return "#{table_name}.#{field}"
112
+ end
113
+
114
+ field
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,3 @@
1
+ module ForestAdminDatasourceActiveRecord
2
+ VERSION = "1.0.0-beta.21"
3
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'forest_admin_datasource_active_record/version'
2
+ # require 'forest_admin_datasource_toolkit'
3
+ require 'zeitwerk'
4
+
5
+ loader = Zeitwerk::Loader.for_gem
6
+ loader.setup
7
+
8
+ module ForestAdminDatasourceActiveRecord
9
+ class Error < StandardError; end
10
+ # Your code goes here...
11
+ end
@@ -0,0 +1,11 @@
1
+ module ForestAdminDatasourceActiveRecord
2
+ class Collection
3
+ @model: untyped
4
+
5
+ private
6
+
7
+ def fetch_fields: -> void
8
+
9
+ def get_enum_values: -> [String]
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ module ForestAdminDatasourceActiveRecord
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: forest_admin_datasource_active_record
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.pre.beta.21
5
+ platform: ruby
6
+ authors:
7
+ - Matthieu
8
+ - Nicolas
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2023-11-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '6.1'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '6.1'
28
+ - !ruby/object:Gem::Dependency
29
+ name: zeitwerk
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '2.3'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '2.3'
42
+ description: |-
43
+ Forest is a modern admin interface that works on all major web frameworks. This gem makes Forest
44
+ admin work on any Ruby application.
45
+ email:
46
+ - matthv@gmail.com
47
+ - nicolasalexandre9@gmail.com
48
+ executables: []
49
+ extensions: []
50
+ extra_rdoc_files: []
51
+ files:
52
+ - ".rspec"
53
+ - README.md
54
+ - Rakefile
55
+ - forest_admin_datasource_active_record.gemspec
56
+ - lib/forest_admin_datasource_active_record.rb
57
+ - lib/forest_admin_datasource_active_record/collection.rb
58
+ - lib/forest_admin_datasource_active_record/datasource.rb
59
+ - lib/forest_admin_datasource_active_record/parser/column.rb
60
+ - lib/forest_admin_datasource_active_record/parser/relation.rb
61
+ - lib/forest_admin_datasource_active_record/parser/validation.rb
62
+ - lib/forest_admin_datasource_active_record/utils/query.rb
63
+ - lib/forest_admin_datasource_active_record/version.rb
64
+ - sig/forest_admin_datasource_active_record.rbs
65
+ - sig/forest_admin_datasource_active_record/collection.rbs
66
+ homepage: https://www.forestadmin.com
67
+ licenses:
68
+ - MIT
69
+ metadata:
70
+ homepage_uri: https://www.forestadmin.com
71
+ source_code_uri: https://github.com/ForestAdmin/agent-ruby
72
+ changelog_uri: https://github.com/ForestAdmin/agent-ruby/CHANGELOG.md
73
+ rubygems_mfa_required: 'false'
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 3.0.0
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">"
86
+ - !ruby/object:Gem::Version
87
+ version: 1.3.1
88
+ requirements: []
89
+ rubygems_version: 3.3.5
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Ruby agent for Forest Admin.
93
+ test_files: []