yaml_exporter 0.1.0 → 0.2.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/gem-push.yml +20 -29
- data/.github/workflows/test.yml +27 -0
- data/AGENTS.md +34 -0
- data/CHANGELOG.md +56 -0
- data/Gemfile.lock +106 -0
- data/README.md +785 -37
- data/RELEASING.md +45 -0
- data/Rakefile +14 -0
- data/lib/yaml_exporter/builder.rb +103 -0
- data/lib/yaml_exporter/exporter.rb +139 -0
- data/lib/yaml_exporter/importer.rb +86 -0
- data/lib/yaml_exporter/nodes/attribute.rb +70 -0
- data/lib/yaml_exporter/nodes/many_base.rb +215 -0
- data/lib/yaml_exporter/nodes/many_find_by.rb +70 -0
- data/lib/yaml_exporter/nodes/many_positional.rb +14 -0
- data/lib/yaml_exporter/nodes/many_reference.rb +99 -0
- data/lib/yaml_exporter/nodes/many_through.rb +195 -0
- data/lib/yaml_exporter/nodes/of_resolution.rb +84 -0
- data/lib/yaml_exporter/nodes/one_owned.rb +96 -0
- data/lib/yaml_exporter/nodes/one_reference.rb +72 -0
- data/lib/yaml_exporter/nodes/one_reference_of.rb +85 -0
- data/lib/yaml_exporter/schema.rb +24 -0
- data/lib/yaml_exporter/structure.rb +25 -0
- data/lib/yaml_exporter/type_inference.rb +64 -0
- data/lib/yaml_exporter/version.rb +5 -0
- data/lib/yaml_exporter.rb +54 -8
- data/script/demo_quiz.rb +78 -0
- data/yaml_exporter.gemspec +8 -9
- metadata +59 -13
- data/lib/yaml_serializable/structure_builder.rb +0 -30
- data/lib/yaml_serializable/version.rb +0 -3
- data/lib/yaml_serializable/yaml_exporter.rb +0 -196
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YamlExporter
|
|
4
|
+
module Nodes
|
|
5
|
+
# `one :assoc, find_by: :col, of: :nested_assoc`
|
|
6
|
+
#
|
|
7
|
+
# Identifies the target record indirectly: the YAML value is looked up on
|
|
8
|
+
# a related model (`of:`) rather than on the target itself.
|
|
9
|
+
#
|
|
10
|
+
# Example: Book `belongs_to :responsible_editor` (CorporateUser), and
|
|
11
|
+
# CorporateUser `belongs_to :user` (User with a slug). Declaring
|
|
12
|
+
# `one :responsible_editor, find_by: :slug, of: :user`
|
|
13
|
+
# stores the User's slug in the YAML and resolves the CorporateUser on
|
|
14
|
+
# import by reversing the FK.
|
|
15
|
+
#
|
|
16
|
+
# Only 1:[0,1] `of:` associations are permitted (belongs_to or has_one).
|
|
17
|
+
# Phase: :pre_save (sets the FK before record.save!).
|
|
18
|
+
class OneReferenceOf
|
|
19
|
+
include OfResolution
|
|
20
|
+
|
|
21
|
+
attr_reader :name, :owner_class, :find_by, :of
|
|
22
|
+
|
|
23
|
+
def initialize(name:, owner_class:, find_by:, of:)
|
|
24
|
+
@name = name.to_sym
|
|
25
|
+
@owner_class = owner_class
|
|
26
|
+
@find_by = find_by.to_sym
|
|
27
|
+
@of = of.to_sym
|
|
28
|
+
|
|
29
|
+
reflection = owner_class.reflect_on_association(@name)
|
|
30
|
+
unless reflection
|
|
31
|
+
raise ArgumentError,
|
|
32
|
+
"`one #{name.inspect}`: #{owner_class} has no association `#{name}`"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
validate_of_reflection!("one #{name.inspect}")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def phase
|
|
39
|
+
:pre_save
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def yaml_keys
|
|
43
|
+
[@name.to_s]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def target_class
|
|
47
|
+
@target_class ||= @owner_class.reflect_on_association(@name).klass
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def import(record, data, path:, importer:)
|
|
51
|
+
value = data.key?(@name.to_s) ? data[@name.to_s] : nil
|
|
52
|
+
|
|
53
|
+
if value.nil?
|
|
54
|
+
record.public_send("#{@name}=", nil)
|
|
55
|
+
return
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
related = of_class.find_by(@find_by => value)
|
|
59
|
+
unless related
|
|
60
|
+
raise ActiveRecord::RecordNotFound,
|
|
61
|
+
"no #{of_class} with #{@find_by}=#{value.inspect}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
target = find_target_via(related)
|
|
65
|
+
unless target
|
|
66
|
+
raise ActiveRecord::RecordNotFound,
|
|
67
|
+
"no #{target_class} linked to #{of_class} #{@find_by}=#{value.inspect} via `#{@of}`"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
record.public_send("#{@name}=", target)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def export(record, exporter:)
|
|
74
|
+
target = record.public_send(@name)
|
|
75
|
+
return [@name.to_s, nil] if target.nil?
|
|
76
|
+
|
|
77
|
+
[@name.to_s, of_value_for(target)]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def schema_fragment
|
|
81
|
+
{ @name => { type: TypeInference.schema_type_for(of_class, @find_by) } }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YamlExporter
|
|
4
|
+
# Builds a JSON-schema-ish hash describing a structure. Keys are symbols
|
|
5
|
+
# so callers can use `schema[:properties][:title]` ergonomically.
|
|
6
|
+
class Schema
|
|
7
|
+
def self.generate(structure)
|
|
8
|
+
new(structure).generate
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(structure)
|
|
12
|
+
@structure = structure
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def generate
|
|
16
|
+
{
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: @structure.nodes.each_with_object({}) do |node, acc|
|
|
19
|
+
acc.merge!(node.schema_fragment)
|
|
20
|
+
end
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YamlExporter
|
|
4
|
+
# Immutable representation of a `yaml_structure do ... end` block.
|
|
5
|
+
#
|
|
6
|
+
# * `klass` — the ActiveRecord class this structure is attached to.
|
|
7
|
+
# Resolved lazily: the constructor accepts either a class or a callable,
|
|
8
|
+
# and defers to first access. This matters for inner structures whose
|
|
9
|
+
# class might be resolved via a reflection that isn't safe to compute
|
|
10
|
+
# at declaration time (anonymous parent classes, etc.).
|
|
11
|
+
# * `nodes` — ordered list of node objects. Order matches declaration and
|
|
12
|
+
# drives export key order.
|
|
13
|
+
class Structure
|
|
14
|
+
attr_reader :nodes
|
|
15
|
+
|
|
16
|
+
def initialize(klass:, nodes:)
|
|
17
|
+
@klass_resolver = klass.respond_to?(:call) ? klass : -> { klass }
|
|
18
|
+
@nodes = nodes
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def klass
|
|
22
|
+
@klass ||= @klass_resolver.call
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YamlExporter
|
|
4
|
+
# Maps an ActiveRecord column type to a JSON-schema primitive.
|
|
5
|
+
#
|
|
6
|
+
# Single source of truth used by every node that emits a `type:` entry
|
|
7
|
+
# in the generated schema (Nodes::Attribute, Nodes::OneReference,
|
|
8
|
+
# Nodes::ManyReference, and the extra_entry_keys path in Nodes::ManyBase).
|
|
9
|
+
#
|
|
10
|
+
# Unknown or un-resolvable columns fall back to 'string' so the schema
|
|
11
|
+
# always validates as JSON-schema-ish.
|
|
12
|
+
module TypeInference
|
|
13
|
+
# AR column type -> JSON-schema primitive. Date/time stay 'string' on
|
|
14
|
+
# purpose: YAML round-trips them as ISO-8601 strings by default, and we
|
|
15
|
+
# don't (yet) emit a `format:` facet.
|
|
16
|
+
COLUMN_TYPE_TO_JSON_SCHEMA = {
|
|
17
|
+
string: 'string',
|
|
18
|
+
text: 'string',
|
|
19
|
+
uuid: 'string',
|
|
20
|
+
binary: 'string',
|
|
21
|
+
integer: 'integer',
|
|
22
|
+
bigint: 'integer',
|
|
23
|
+
float: 'number',
|
|
24
|
+
decimal: 'number',
|
|
25
|
+
boolean: 'boolean',
|
|
26
|
+
date: 'string',
|
|
27
|
+
datetime: 'string',
|
|
28
|
+
time: 'string',
|
|
29
|
+
timestamp: 'string',
|
|
30
|
+
json: 'object',
|
|
31
|
+
jsonb: 'object'
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
FALLBACK = 'string'
|
|
35
|
+
|
|
36
|
+
# Returns the JSON-schema type string for `column_name` on `klass`.
|
|
37
|
+
# `klass` may be nil (e.g. declaration-time lookup on an anonymous
|
|
38
|
+
# class whose table doesn't exist yet) — we quietly fall back.
|
|
39
|
+
def self.schema_type_for(klass, column_name)
|
|
40
|
+
return FALLBACK unless klass.respond_to?(:columns_hash)
|
|
41
|
+
|
|
42
|
+
column = klass.columns_hash[column_name.to_s]
|
|
43
|
+
return FALLBACK unless column
|
|
44
|
+
|
|
45
|
+
COLUMN_TYPE_TO_JSON_SCHEMA[column.type] || FALLBACK
|
|
46
|
+
rescue ActiveRecord::ActiveRecordError
|
|
47
|
+
FALLBACK
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# True when `column_name` on `klass` is a `text` column (as opposed to a
|
|
51
|
+
# `string`/varchar). Drives whether string values export as YAML literal
|
|
52
|
+
# block scalars. Unknown/unresolvable columns are treated as non-text.
|
|
53
|
+
def self.text_column?(klass, column_name)
|
|
54
|
+
return false unless klass.respond_to?(:columns_hash)
|
|
55
|
+
|
|
56
|
+
column = klass.columns_hash[column_name.to_s]
|
|
57
|
+
return false unless column
|
|
58
|
+
|
|
59
|
+
column.type == :text
|
|
60
|
+
rescue ActiveRecord::ActiveRecordError
|
|
61
|
+
false
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
data/lib/yaml_exporter.rb
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
require 'active_record'
|
|
4
|
+
require 'active_support/core_ext/class/attribute'
|
|
5
5
|
|
|
6
|
+
require_relative 'yaml_exporter/version'
|
|
7
|
+
|
|
8
|
+
# Top-level entrypoint: defines the `include YamlExporter` hook and the
|
|
9
|
+
# public API methods (`yaml_structure`, `yaml_schema`, `yaml_import`,
|
|
10
|
+
# `yaml_export`). Parsing, import, export and schema generation live in
|
|
11
|
+
# dedicated classes under `lib/yaml_exporter/`.
|
|
6
12
|
module YamlExporter
|
|
13
|
+
# Base class for every library-specific error. Inherits from
|
|
14
|
+
# ActiveRecord::ActiveRecordError so existing `rescue ActiveRecord::ActiveRecordError`
|
|
15
|
+
# blocks (and the import transaction wrapper) catch it naturally.
|
|
16
|
+
class Error < ActiveRecord::ActiveRecordError; end
|
|
17
|
+
|
|
18
|
+
# Raised when a YAML document carries a key (top-level or inside an entry)
|
|
19
|
+
# that the corresponding `yaml_structure` scope does not declare.
|
|
20
|
+
class UnknownAttributeError < Error; end
|
|
21
|
+
|
|
22
|
+
# Raised when a `find_by`-identified list contains two entries with the same
|
|
23
|
+
# key value — e.g. two `slug: chapter-1` entries under one `book_parts:`.
|
|
24
|
+
class DuplicateKeyError < Error; end
|
|
25
|
+
|
|
7
26
|
def self.included(base)
|
|
8
27
|
base.extend(ClassMethods)
|
|
9
28
|
end
|
|
@@ -11,22 +30,49 @@ module YamlExporter
|
|
|
11
30
|
module ClassMethods
|
|
12
31
|
def yaml_structure(&block)
|
|
13
32
|
class_attribute :yaml_structure_definition, instance_writer: false
|
|
14
|
-
self.yaml_structure_definition =
|
|
33
|
+
self.yaml_structure_definition = YamlExporter::Builder.new(self, &block).build
|
|
15
34
|
include InstanceMethods
|
|
16
35
|
end
|
|
17
36
|
|
|
18
37
|
def yaml_schema
|
|
19
|
-
|
|
38
|
+
YamlExporter::Schema.generate(yaml_structure_definition)
|
|
20
39
|
end
|
|
21
40
|
end
|
|
22
41
|
|
|
23
42
|
module InstanceMethods
|
|
24
|
-
def
|
|
25
|
-
|
|
43
|
+
def yaml_import(yaml_string)
|
|
44
|
+
YamlExporter::Importer.new(self, self.class.yaml_structure_definition).import(yaml_string)
|
|
26
45
|
end
|
|
27
46
|
|
|
28
|
-
|
|
29
|
-
|
|
47
|
+
# `omit_nil:` (default true) drops keys whose value is empty — a nil
|
|
48
|
+
# attribute, a missing `one` reference, an absent owned `one`, or an empty
|
|
49
|
+
# `many` list. Round-trip safe: import treats a missing key, an explicit
|
|
50
|
+
# `null`, and an empty list identically. Pass `omit_nil: false` to keep
|
|
51
|
+
# explicit `null`s in the file (e.g. so optional fields stay discoverable).
|
|
52
|
+
def yaml_export(omit_nil: true)
|
|
53
|
+
YamlExporter::Exporter.new(
|
|
54
|
+
self, self.class.yaml_structure_definition, omit_nil: omit_nil
|
|
55
|
+
).to_yaml
|
|
30
56
|
end
|
|
31
57
|
end
|
|
32
58
|
end
|
|
59
|
+
|
|
60
|
+
require 'set'
|
|
61
|
+
require 'yaml'
|
|
62
|
+
|
|
63
|
+
require_relative 'yaml_exporter/structure'
|
|
64
|
+
require_relative 'yaml_exporter/type_inference'
|
|
65
|
+
require_relative 'yaml_exporter/nodes/attribute'
|
|
66
|
+
require_relative 'yaml_exporter/nodes/of_resolution'
|
|
67
|
+
require_relative 'yaml_exporter/nodes/one_owned'
|
|
68
|
+
require_relative 'yaml_exporter/nodes/one_reference'
|
|
69
|
+
require_relative 'yaml_exporter/nodes/one_reference_of'
|
|
70
|
+
require_relative 'yaml_exporter/nodes/many_base'
|
|
71
|
+
require_relative 'yaml_exporter/nodes/many_positional'
|
|
72
|
+
require_relative 'yaml_exporter/nodes/many_find_by'
|
|
73
|
+
require_relative 'yaml_exporter/nodes/many_reference'
|
|
74
|
+
require_relative 'yaml_exporter/nodes/many_through'
|
|
75
|
+
require_relative 'yaml_exporter/builder'
|
|
76
|
+
require_relative 'yaml_exporter/importer'
|
|
77
|
+
require_relative 'yaml_exporter/exporter'
|
|
78
|
+
require_relative 'yaml_exporter/schema'
|
data/script/demo_quiz.rb
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Runnable demo sharing the same schema and models as the test suite.
|
|
5
|
+
# From the repository root:
|
|
6
|
+
#
|
|
7
|
+
# ruby script/demo_quiz.rb
|
|
8
|
+
#
|
|
9
|
+
# One-time: gem install sqlite3 && bundle install
|
|
10
|
+
|
|
11
|
+
require 'bundler/setup'
|
|
12
|
+
require 'active_record'
|
|
13
|
+
require 'json'
|
|
14
|
+
require 'pathname'
|
|
15
|
+
|
|
16
|
+
root = Pathname.new(__dir__).join('..').expand_path
|
|
17
|
+
$LOAD_PATH.unshift root.join('lib').to_s
|
|
18
|
+
require 'yaml_exporter'
|
|
19
|
+
|
|
20
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
|
21
|
+
ActiveRecord::Base.logger = nil
|
|
22
|
+
|
|
23
|
+
load root.join('test/support/schema.rb')
|
|
24
|
+
load root.join('test/support/models.rb')
|
|
25
|
+
|
|
26
|
+
quiz = Quiz.create!(title: 'Ruby Basics', quiz_type: 'multiple_choice')
|
|
27
|
+
|
|
28
|
+
q1 = quiz.questions.create!(
|
|
29
|
+
text: 'What is Ruby?',
|
|
30
|
+
question_type: 'single_choice',
|
|
31
|
+
feedback: 'Ruby is a dynamic, open-source programming language.'
|
|
32
|
+
)
|
|
33
|
+
q1.answers.create!(text: 'A programming language', is_correct: true, impact: 10)
|
|
34
|
+
q1.answers.create!(text: 'A gemstone', is_correct: false, impact: 0)
|
|
35
|
+
q1.answers.create!(text: 'A color', is_correct: false, impact: 0)
|
|
36
|
+
|
|
37
|
+
q2 = quiz.questions.create!(
|
|
38
|
+
text: 'Which keyword defines a method in Ruby?',
|
|
39
|
+
question_type: 'single_choice',
|
|
40
|
+
feedback: "Use 'def' to define methods."
|
|
41
|
+
)
|
|
42
|
+
q2.answers.create!(text: 'def', is_correct: true, impact: 10)
|
|
43
|
+
q2.answers.create!(text: 'func', is_correct: false, impact: 0)
|
|
44
|
+
q2.answers.create!(text: 'fn', is_correct: false, impact: 0)
|
|
45
|
+
|
|
46
|
+
puts '=' * 60
|
|
47
|
+
puts '1) YAML Export'
|
|
48
|
+
puts '=' * 60
|
|
49
|
+
yaml_output = quiz.yaml_export
|
|
50
|
+
puts yaml_output
|
|
51
|
+
|
|
52
|
+
puts '=' * 60
|
|
53
|
+
puts '2) JSON Schema'
|
|
54
|
+
puts '=' * 60
|
|
55
|
+
schema = Quiz.yaml_schema
|
|
56
|
+
puts JSON.pretty_generate(schema)
|
|
57
|
+
|
|
58
|
+
puts "\n" + '=' * 60
|
|
59
|
+
puts '3) YAML Import (round-trip into a new Quiz)'
|
|
60
|
+
puts '=' * 60
|
|
61
|
+
|
|
62
|
+
imported_quiz = Quiz.create!(title: 'placeholder', quiz_type: 'placeholder')
|
|
63
|
+
imported_quiz.yaml_import(yaml_output)
|
|
64
|
+
|
|
65
|
+
puts "Title: #{imported_quiz.title}"
|
|
66
|
+
puts "Type: #{imported_quiz.quiz_type}"
|
|
67
|
+
puts "Questions: #{imported_quiz.questions.count}"
|
|
68
|
+
imported_quiz.questions.each do |q|
|
|
69
|
+
puts " Q: #{q.text}"
|
|
70
|
+
q.answers.each do |a|
|
|
71
|
+
marker = a.is_correct ? '✓' : '✗'
|
|
72
|
+
puts " #{marker} #{a.text} (impact: #{a.impact || 'n/a'})"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
puts "\n" + '=' * 60
|
|
77
|
+
puts 'Done — all features working!'
|
|
78
|
+
puts '=' * 60
|
data/yaml_exporter.gemspec
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
|
-
require_relative "lib/
|
|
1
|
+
require_relative "lib/yaml_exporter/version"
|
|
2
2
|
|
|
3
3
|
Gem::Specification.new do |spec|
|
|
4
4
|
spec.name = "yaml_exporter"
|
|
5
|
-
spec.version =
|
|
5
|
+
spec.version = YamlExporter::VERSION
|
|
6
6
|
spec.authors = ["Anatoly Zelenin"]
|
|
7
7
|
spec.email = ["anatoly@zelenin.de"]
|
|
8
8
|
|
|
9
|
-
spec.summary = "YAML
|
|
10
|
-
spec.description = "A Ruby gem
|
|
9
|
+
spec.summary = "YAML import/export for ActiveRecord models with a small declarative DSL"
|
|
10
|
+
spec.description = "A Ruby gem that declaratively maps ActiveRecord models to YAML documents (and back) via a compact `attributes` / `one` / `many` DSL."
|
|
11
11
|
spec.homepage = "https://github.com/itadventurer/yaml_exporter"
|
|
12
12
|
spec.license = "MIT"
|
|
13
13
|
|
|
14
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 2
|
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.2")
|
|
15
15
|
|
|
16
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
17
17
|
spec.metadata["source_code_uri"] = "https://github.com/itadventurer/yaml_exporter"
|
|
18
|
-
#spec.metadata["changelog_uri"] = "https://github.com/itadventurer/yaml_exporter/blob/master/CHANGELOG.md"
|
|
19
18
|
|
|
20
|
-
# Specify which files should be added to the gem when it is released.
|
|
21
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
22
19
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
23
20
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
|
24
21
|
end
|
|
@@ -27,5 +24,7 @@ Gem::Specification.new do |spec|
|
|
|
27
24
|
spec.add_dependency "activerecord", ">= 5.2"
|
|
28
25
|
spec.add_dependency "activesupport", ">= 5.2"
|
|
29
26
|
|
|
27
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
|
30
28
|
spec.add_development_dependency "rake", "~> 13.0"
|
|
31
|
-
|
|
29
|
+
spec.add_development_dependency "sqlite3", ">= 1.4"
|
|
30
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yaml_exporter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anatoly Zelenin
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activerecord
|
|
@@ -38,6 +37,20 @@ dependencies:
|
|
|
38
37
|
- - ">="
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
39
|
version: '5.2'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: minitest
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '5.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '5.0'
|
|
41
54
|
- !ruby/object:Gem::Dependency
|
|
42
55
|
name: rake
|
|
43
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -52,8 +65,22 @@ dependencies:
|
|
|
52
65
|
- - "~>"
|
|
53
66
|
- !ruby/object:Gem::Version
|
|
54
67
|
version: '13.0'
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: sqlite3
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '1.4'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '1.4'
|
|
82
|
+
description: A Ruby gem that declaratively maps ActiveRecord models to YAML documents
|
|
83
|
+
(and back) via a compact `attributes` / `one` / `many` DSL.
|
|
57
84
|
email:
|
|
58
85
|
- anatoly@zelenin.de
|
|
59
86
|
executables: []
|
|
@@ -61,14 +88,35 @@ extensions: []
|
|
|
61
88
|
extra_rdoc_files: []
|
|
62
89
|
files:
|
|
63
90
|
- ".github/workflows/gem-push.yml"
|
|
91
|
+
- ".github/workflows/test.yml"
|
|
64
92
|
- ".gitignore"
|
|
93
|
+
- AGENTS.md
|
|
94
|
+
- CHANGELOG.md
|
|
65
95
|
- Gemfile
|
|
96
|
+
- Gemfile.lock
|
|
66
97
|
- LICENSE
|
|
67
98
|
- README.md
|
|
99
|
+
- RELEASING.md
|
|
100
|
+
- Rakefile
|
|
68
101
|
- lib/yaml_exporter.rb
|
|
69
|
-
- lib/
|
|
70
|
-
- lib/
|
|
71
|
-
- lib/
|
|
102
|
+
- lib/yaml_exporter/builder.rb
|
|
103
|
+
- lib/yaml_exporter/exporter.rb
|
|
104
|
+
- lib/yaml_exporter/importer.rb
|
|
105
|
+
- lib/yaml_exporter/nodes/attribute.rb
|
|
106
|
+
- lib/yaml_exporter/nodes/many_base.rb
|
|
107
|
+
- lib/yaml_exporter/nodes/many_find_by.rb
|
|
108
|
+
- lib/yaml_exporter/nodes/many_positional.rb
|
|
109
|
+
- lib/yaml_exporter/nodes/many_reference.rb
|
|
110
|
+
- lib/yaml_exporter/nodes/many_through.rb
|
|
111
|
+
- lib/yaml_exporter/nodes/of_resolution.rb
|
|
112
|
+
- lib/yaml_exporter/nodes/one_owned.rb
|
|
113
|
+
- lib/yaml_exporter/nodes/one_reference.rb
|
|
114
|
+
- lib/yaml_exporter/nodes/one_reference_of.rb
|
|
115
|
+
- lib/yaml_exporter/schema.rb
|
|
116
|
+
- lib/yaml_exporter/structure.rb
|
|
117
|
+
- lib/yaml_exporter/type_inference.rb
|
|
118
|
+
- lib/yaml_exporter/version.rb
|
|
119
|
+
- script/demo_quiz.rb
|
|
72
120
|
- yaml_exporter.gemspec
|
|
73
121
|
homepage: https://github.com/itadventurer/yaml_exporter
|
|
74
122
|
licenses:
|
|
@@ -76,7 +124,6 @@ licenses:
|
|
|
76
124
|
metadata:
|
|
77
125
|
homepage_uri: https://github.com/itadventurer/yaml_exporter
|
|
78
126
|
source_code_uri: https://github.com/itadventurer/yaml_exporter
|
|
79
|
-
post_install_message:
|
|
80
127
|
rdoc_options: []
|
|
81
128
|
require_paths:
|
|
82
129
|
- lib
|
|
@@ -84,15 +131,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
84
131
|
requirements:
|
|
85
132
|
- - ">="
|
|
86
133
|
- !ruby/object:Gem::Version
|
|
87
|
-
version: 2
|
|
134
|
+
version: '3.2'
|
|
88
135
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
136
|
requirements:
|
|
90
137
|
- - ">="
|
|
91
138
|
- !ruby/object:Gem::Version
|
|
92
139
|
version: '0'
|
|
93
140
|
requirements: []
|
|
94
|
-
rubygems_version: 3.
|
|
95
|
-
signing_key:
|
|
141
|
+
rubygems_version: 3.6.9
|
|
96
142
|
specification_version: 4
|
|
97
|
-
summary: YAML
|
|
143
|
+
summary: YAML import/export for ActiveRecord models with a small declarative DSL
|
|
98
144
|
test_files: []
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module YamlSerializable
|
|
4
|
-
class StructureBuilder
|
|
5
|
-
def initialize(&block)
|
|
6
|
-
@structure = { attributes: [], associations: {} }
|
|
7
|
-
instance_eval(&block)
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def yaml_attribute(*attrs)
|
|
11
|
-
@structure[:attributes].concat(attrs)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def yaml_has_one(name, &block)
|
|
15
|
-
@structure[:associations][name] = { type: :has_one, structure: self.class.new(&block).build }
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def yaml_has_many(name, &block)
|
|
19
|
-
@structure[:associations][name] = { type: :has_many, structure: self.class.new(&block).build }
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def yaml_condition(&block)
|
|
23
|
-
@structure[:condition] = block
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def build
|
|
27
|
-
@structure
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|