propel_facets 0.1.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 +7 -0
- data/CHANGELOG.md +62 -0
- data/LICENSE +21 -0
- data/README.md +354 -0
- data/lib/generators/propel_facets/README.md +130 -0
- data/lib/generators/propel_facets/USAGE +68 -0
- data/lib/generators/propel_facets/install_generator.rb +152 -0
- data/lib/generators/propel_facets/templates/config/propel_facets.rb +188 -0
- data/lib/generators/propel_facets/templates/controllers/concerns/facet_renderer.rb +81 -0
- data/lib/generators/propel_facets/templates/controllers/concerns/strong_params_helper.rb +61 -0
- data/lib/generators/propel_facets/templates/doc/json_facet.md +78 -0
- data/lib/generators/propel_facets/templates/errors/application_error.rb +10 -0
- data/lib/generators/propel_facets/templates/errors/missing_facet.rb +13 -0
- data/lib/generators/propel_facets/templates/lib/api_params.rb +44 -0
- data/lib/generators/propel_facets/templates/models/application_record.rb +13 -0
- data/lib/generators/propel_facets/templates/models/concerns/model_facet.rb +189 -0
- data/lib/generators/propel_facets/test/propel_facets_generator_test.rb +75 -0
- data/lib/generators/propel_facets/test/test_helper.rb +20 -0
- data/lib/generators/propel_facets/unpack_generator.rb +307 -0
- data/lib/propel_facets.rb +3 -0
- metadata +86 -0
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Helper for defining json facets for an ActiveRecord model
|
4
|
+
#
|
5
|
+
# This should allow you to define json facets like seen in the following
|
6
|
+
# example. Facets are used by JSON renderers like our controllers to determine
|
7
|
+
# which format suits better for a given situation.
|
8
|
+
#
|
9
|
+
# ```ruby
|
10
|
+
# class MyModel < ApplicationRecord
|
11
|
+
# json_facet :short, :name, :logo
|
12
|
+
# json_facet :public, :email, extend: :short
|
13
|
+
# json_facet :agency, :address_1, :address_2, extend: :public
|
14
|
+
# end
|
15
|
+
# ```
|
16
|
+
#
|
17
|
+
# The names for the facets here are :short, :public and :agency, but you are
|
18
|
+
# free to use any name you wish. Those are names expected to be looked by
|
19
|
+
# our API controllers. If not facet satisfies the reader, it may choose to
|
20
|
+
# either throw an error or fall-back to some format of his own choice.
|
21
|
+
#
|
22
|
+
module ModelFacet
|
23
|
+
extend ActiveSupport::Concern
|
24
|
+
|
25
|
+
class_methods do
|
26
|
+
attr_accessor :all_facets
|
27
|
+
|
28
|
+
def inherited(subclass)
|
29
|
+
super(subclass)
|
30
|
+
subclass.all_facets = @all_facets.dup
|
31
|
+
end
|
32
|
+
|
33
|
+
# Directive to define a JSON facet
|
34
|
+
#
|
35
|
+
# Defines a named JSON facet specification that controls how a model is serialized to JSON.
|
36
|
+
# The facet can include fields, methods, and nested associations.
|
37
|
+
#
|
38
|
+
# Facets from base classes are merged on declaration of a facet.
|
39
|
+
# Base facets for a spec are merged upon usage.
|
40
|
+
#
|
41
|
+
# @param name [Symbol] The name of the facet
|
42
|
+
# @param *args [Array] List of fields/methods to include
|
43
|
+
# @param opts [Hash] Options hash that can include:
|
44
|
+
# fields: Additional fields to include
|
45
|
+
# methods: Method names to call and include
|
46
|
+
# include: Nested associations to include with their original names
|
47
|
+
# include_as: Nested associations with custom key names in the JSON output
|
48
|
+
# extend: Name of another facet to extend from
|
49
|
+
#
|
50
|
+
# The difference between include and include_as:
|
51
|
+
# - include: Includes associations using their model name as the JSON key
|
52
|
+
# Example: include: [:posts] => {"posts": [...]}
|
53
|
+
#
|
54
|
+
# - include_as: Includes associations with a custom key name in the JSON
|
55
|
+
# Example: include_as: [comments: :replies] => {"replies": [...]}
|
56
|
+
#
|
57
|
+
# @example Basic facet with fields
|
58
|
+
# json_facet :basic, :id, :name, :email
|
59
|
+
# # => {"id": 1, "name": "John", "email": "john@example.com"}
|
60
|
+
#
|
61
|
+
# @example Complex facet showing include vs include_as
|
62
|
+
# json_facet :detailed,
|
63
|
+
# :id, :name,
|
64
|
+
# methods: [:full_name],
|
65
|
+
# include: [:posts], # Will appear as "posts" in JSON
|
66
|
+
# include_as: [comments: :replies] # Will appear as "replies" in JSON
|
67
|
+
# # => {
|
68
|
+
# # "id": 1,
|
69
|
+
# # "name": "John",
|
70
|
+
# # "full_name": "John Smith",
|
71
|
+
# # "posts": [...], # Original association name
|
72
|
+
# # "replies": [...] # Custom key name
|
73
|
+
# # }
|
74
|
+
def json_facet(name, *args)
|
75
|
+
opts = args.extract_options!
|
76
|
+
@all_facets = {} if @all_facets.nil?
|
77
|
+
spec = opts.merge(
|
78
|
+
fields: [opts[:fields] || []].flatten,
|
79
|
+
include: [opts[:include] || []].flatten,
|
80
|
+
include_as: [opts[:include_as] || []].flatten,
|
81
|
+
methods: [opts[:methods] || []].flatten
|
82
|
+
)
|
83
|
+
existing = @all_facets.fetch(name, {})
|
84
|
+
@all_facets[name] = merge_facet existing, spec
|
85
|
+
end
|
86
|
+
|
87
|
+
# Gets base facet to be extended
|
88
|
+
def base_facet(name)
|
89
|
+
if name.nil?
|
90
|
+
{ fields: [], include: [], include_as: [], methods: [] }
|
91
|
+
elsif @all_facets.include? name.to_sym
|
92
|
+
@all_facets[name.to_sym]
|
93
|
+
else
|
94
|
+
warn "[FACET BASE] `#{name}` JSON facet not found to be a extended"
|
95
|
+
{ fields: [], include: [], include_as: [], methods: [] }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Merges a base facet with another facet definition
|
100
|
+
def merge_facet(*args)
|
101
|
+
a, b = args.shift(2)
|
102
|
+
extended_lists = {
|
103
|
+
fields: [b[:fields], a[:fields]].flatten.uniq.excluding(nil),
|
104
|
+
include: [b[:include], a[:include]].flatten.uniq.excluding(nil),
|
105
|
+
include_as: [b[:include_as], a[:include_as]].flatten.uniq.excluding(nil),
|
106
|
+
methods: [b[:methods], a[:methods]].flatten.uniq.excluding(nil)
|
107
|
+
}
|
108
|
+
extended = a.merge(b).merge(extended_lists)
|
109
|
+
return extended if args.empty?
|
110
|
+
|
111
|
+
merge_facet(extended, *args)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Helps getting a facet after a given value
|
115
|
+
#
|
116
|
+
# This value can point to either a symbol, an array or even be nil.
|
117
|
+
# A symbol and an array with one symbol will behave exactly the same.
|
118
|
+
# If nil or an empty array is given, then this will return nil.
|
119
|
+
# While processing an array of symbols this will return the first facet
|
120
|
+
# it finds registered for current model. If none is found, returns nil.
|
121
|
+
def find_facet(names, missing: :noaction)
|
122
|
+
@all_facets = {} if @all_facets.nil?
|
123
|
+
array = [names].flatten
|
124
|
+
facet = array.find { |name| @all_facets.include? name }
|
125
|
+
if facet.nil?
|
126
|
+
case missing
|
127
|
+
when :noaction
|
128
|
+
nil
|
129
|
+
when :error
|
130
|
+
raise MissingFacet
|
131
|
+
end
|
132
|
+
else
|
133
|
+
@all_facets[facet]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns spec with its base spec merged
|
138
|
+
#
|
139
|
+
# If base spec has any base chain, those will be merged recursively.
|
140
|
+
#
|
141
|
+
def extend_base(spec)
|
142
|
+
if spec.include? :base
|
143
|
+
base = base_facet(spec[:base])
|
144
|
+
nobase_spec = spec.dup
|
145
|
+
nobase_spec.delete :base
|
146
|
+
extended = merge_facet(base, nobase_spec)
|
147
|
+
extend_base extended
|
148
|
+
else
|
149
|
+
spec
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Builds a `serializable_hash` options object after a facet spec
|
154
|
+
def facet_serializable_options(name, spec)
|
155
|
+
actual = extend_base(spec)
|
156
|
+
include_as = (actual[:include_as] + [name]).flatten.uniq
|
157
|
+
include = actual[:include].reduce({}) do |memo, current|
|
158
|
+
memo.update(current => { facet: include_as })
|
159
|
+
end
|
160
|
+
{ only: actual[:fields], include: include, methods: actual[:methods] }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Public model method to allow getting JSON facet
|
165
|
+
def serializable_hash(options = nil)
|
166
|
+
injected = inject_facet(options)
|
167
|
+
hash = super(injected)
|
168
|
+
hash['id'] = id
|
169
|
+
hash['type'] = model_name.element.pluralize.underscore.dasherize
|
170
|
+
hash
|
171
|
+
end
|
172
|
+
|
173
|
+
# Inject our facet options to this model
|
174
|
+
def inject_facet(options = nil)
|
175
|
+
if options&.include? :facet
|
176
|
+
missing = options.fetch(:missing, :error)
|
177
|
+
name = options[:facet]
|
178
|
+
facet = self.class.find_facet(name, missing: missing)
|
179
|
+
if facet.nil?
|
180
|
+
options
|
181
|
+
else
|
182
|
+
extra = self.class.facet_serializable_options(name, facet)
|
183
|
+
options.merge(extra).except(:facet, :missing)
|
184
|
+
end
|
185
|
+
else
|
186
|
+
options
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require_relative "../propel_facets_generator"
|
3
|
+
|
4
|
+
class PropelFacetsGeneratorTest < Rails::Generators::TestCase
|
5
|
+
tests PropelFacetsGenerator
|
6
|
+
destination Rails.root.join("tmp/generators")
|
7
|
+
setup :prepare_destination
|
8
|
+
|
9
|
+
test "generator runs without errors" do
|
10
|
+
assert_nothing_raised do
|
11
|
+
run_generator
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
test "generates all required files" do
|
16
|
+
run_generator
|
17
|
+
|
18
|
+
# Model concerns
|
19
|
+
assert_file "app/models/concerns/model_facet.rb"
|
20
|
+
|
21
|
+
# Controller concerns
|
22
|
+
assert_file "app/controllers/concerns/facet_renderer.rb"
|
23
|
+
assert_file "app/controllers/concerns/strong_params_helper.rb"
|
24
|
+
|
25
|
+
# Utility classes
|
26
|
+
assert_file "lib/api_params.rb"
|
27
|
+
|
28
|
+
# Error classes
|
29
|
+
assert_file "app/errors/application_error.rb"
|
30
|
+
assert_file "app/errors/missing_facet.rb"
|
31
|
+
|
32
|
+
# Documentation
|
33
|
+
assert_file "doc/json_facet.md"
|
34
|
+
|
35
|
+
# ApplicationRecord
|
36
|
+
assert_file "app/models/application_record.rb"
|
37
|
+
end
|
38
|
+
|
39
|
+
test "generates files with correct content" do
|
40
|
+
run_generator
|
41
|
+
|
42
|
+
assert_file "app/models/concerns/model_facet.rb" do |content|
|
43
|
+
assert_match(/module ModelFacet/, content)
|
44
|
+
assert_match(/def json_facet/, content)
|
45
|
+
end
|
46
|
+
|
47
|
+
assert_file "app/controllers/concerns/facet_renderer.rb" do |content|
|
48
|
+
assert_match(/module FacetRenderer/, content)
|
49
|
+
assert_match(/def connect_facet/, content)
|
50
|
+
end
|
51
|
+
|
52
|
+
assert_file "lib/api_params.rb" do |content|
|
53
|
+
assert_match(/class ApiParams/, content)
|
54
|
+
assert_match(/def permit/, content)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
test "template structure is properly organized" do
|
59
|
+
# Verify the template files exist in organized structure
|
60
|
+
assert File.exist?(File.join(source_root, "models/concerns/model_facet.rb"))
|
61
|
+
assert File.exist?(File.join(source_root, "controllers/concerns/facet_renderer.rb"))
|
62
|
+
assert File.exist?(File.join(source_root, "controllers/concerns/strong_params_helper.rb"))
|
63
|
+
assert File.exist?(File.join(source_root, "lib/api_params.rb"))
|
64
|
+
assert File.exist?(File.join(source_root, "errors/application_error.rb"))
|
65
|
+
assert File.exist?(File.join(source_root, "errors/missing_facet.rb"))
|
66
|
+
assert File.exist?(File.join(source_root, "doc/json_facet.md"))
|
67
|
+
assert File.exist?(File.join(source_root, "models/application_record.rb"))
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def source_root
|
73
|
+
File.expand_path("../templates", __FILE__)
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/all'
|
4
|
+
require 'rails/generators'
|
5
|
+
require 'rails/generators/test_case'
|
6
|
+
|
7
|
+
# Configure Rails.application as this generator requires it
|
8
|
+
module Dummy
|
9
|
+
class Application < Rails::Application
|
10
|
+
config.root = __dir__
|
11
|
+
config.session_store :cookie_store, key: '_dummy_session'
|
12
|
+
config.secret_key_base = 'dummy_secret_key_base'
|
13
|
+
config.eager_load = false
|
14
|
+
config.active_support.deprecation = :log
|
15
|
+
config.active_support.test_order = :random
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Rails.application = Dummy::Application.new
|
20
|
+
Rails.application.initialize!
|
@@ -0,0 +1,307 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# PropelFacets unpacker that extracts the FACETS GENERATOR into the host application
|
5
|
+
#
|
6
|
+
# Usage:
|
7
|
+
# rails generate propel_facets:unpack # Unpack entire generator (code + templates)
|
8
|
+
# rails generate propel_facets:unpack --force # Overwrite existing generator files
|
9
|
+
# rails generate propel_facets:unpack --templates-only # Only copy templates, not generator logic
|
10
|
+
# rails generate propel_facets:unpack --models-only # Only copy model templates
|
11
|
+
# rails generate propel_facets:unpack --controllers-only # Only copy controller templates
|
12
|
+
# rails generate propel_facets:unpack --errors-only # Only copy error templates
|
13
|
+
# rails generate propel_facets:unpack --lib-only # Only copy lib templates
|
14
|
+
# rails generate propel_facets:unpack --docs-only # Only copy documentation templates
|
15
|
+
#
|
16
|
+
# This extracts the PropelFacets generator from the gem into lib/generators/propel_facets/
|
17
|
+
# so you can customize the generator logic and templates for your specific needs.
|
18
|
+
#
|
19
|
+
module PropelFacets
|
20
|
+
class UnpackGenerator < Rails::Generators::Base
|
21
|
+
source_root File.expand_path("templates", __dir__)
|
22
|
+
|
23
|
+
desc "Extract PropelFacets generator code from gem to lib/generators/propel_facets/ for full customization"
|
24
|
+
|
25
|
+
class_option :force,
|
26
|
+
type: :boolean,
|
27
|
+
default: false,
|
28
|
+
desc: "Overwrite existing generator files"
|
29
|
+
|
30
|
+
class_option :templates_only,
|
31
|
+
type: :boolean,
|
32
|
+
default: false,
|
33
|
+
desc: "Extract only generator templates, not generator logic"
|
34
|
+
|
35
|
+
class_option :models_only,
|
36
|
+
type: :boolean,
|
37
|
+
default: false,
|
38
|
+
desc: "Extract only model generator templates"
|
39
|
+
|
40
|
+
class_option :controllers_only,
|
41
|
+
type: :boolean,
|
42
|
+
default: false,
|
43
|
+
desc: "Extract only controller generator templates"
|
44
|
+
|
45
|
+
class_option :errors_only,
|
46
|
+
type: :boolean,
|
47
|
+
default: false,
|
48
|
+
desc: "Extract only error generator templates"
|
49
|
+
|
50
|
+
class_option :lib_only,
|
51
|
+
type: :boolean,
|
52
|
+
default: false,
|
53
|
+
desc: "Extract only lib generator templates"
|
54
|
+
|
55
|
+
class_option :docs_only,
|
56
|
+
type: :boolean,
|
57
|
+
default: false,
|
58
|
+
desc: "Extract only documentation generator templates"
|
59
|
+
|
60
|
+
def unpack_propel_facets_generator
|
61
|
+
show_welcome_message
|
62
|
+
|
63
|
+
components_to_unpack = determine_components
|
64
|
+
destination_path = "lib/generators/propel_facets"
|
65
|
+
|
66
|
+
if File.exist?(destination_path) && !options[:force]
|
67
|
+
say "โ ๏ธ #{destination_path} already exists (use --force to overwrite)", :yellow
|
68
|
+
return
|
69
|
+
end
|
70
|
+
|
71
|
+
say "๐ฆ Extracting PropelFacets generator components: #{components_to_unpack.join(', ')}", :green
|
72
|
+
say ""
|
73
|
+
|
74
|
+
unpack_components(components_to_unpack, destination_path)
|
75
|
+
show_completion_message(destination_path)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def show_welcome_message
|
81
|
+
say ""
|
82
|
+
say "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ", :cyan
|
83
|
+
say "โ PROPEL FACETS GENERATOR UNPACKER โ", :cyan
|
84
|
+
say "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ", :cyan
|
85
|
+
say ""
|
86
|
+
say "This extracts the PropelFacets GENERATOR from the gem into your application", :blue
|
87
|
+
say "at lib/generators/propel_facets/ so you can customize generator logic and templates.", :blue
|
88
|
+
say ""
|
89
|
+
say "๐ฆ FROM: Gem-based generator (black box)", :yellow
|
90
|
+
say "๐ TO: Local generator in lib/generators/propel_facets/ (fully customizable)", :green
|
91
|
+
say ""
|
92
|
+
end
|
93
|
+
|
94
|
+
def determine_components
|
95
|
+
if options[:models_only]
|
96
|
+
return %w[models]
|
97
|
+
elsif options[:controllers_only]
|
98
|
+
return %w[controllers]
|
99
|
+
elsif options[:errors_only]
|
100
|
+
return %w[errors]
|
101
|
+
elsif options[:lib_only]
|
102
|
+
return %w[lib]
|
103
|
+
elsif options[:docs_only]
|
104
|
+
return %w[docs]
|
105
|
+
elsif options[:templates_only]
|
106
|
+
return %w[models controllers errors lib docs]
|
107
|
+
else
|
108
|
+
# Default: everything including generator logic
|
109
|
+
return %w[models controllers errors lib docs generator_logic]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def unpack_components(components, destination_path)
|
114
|
+
FileUtils.mkdir_p(destination_path)
|
115
|
+
|
116
|
+
components.each do |component|
|
117
|
+
unpack_component(component, destination_path)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def unpack_component(component, destination_path)
|
122
|
+
case component
|
123
|
+
when 'models'
|
124
|
+
copy_model_templates(destination_path)
|
125
|
+
when 'controllers'
|
126
|
+
copy_controller_templates(destination_path)
|
127
|
+
when 'errors'
|
128
|
+
copy_error_templates(destination_path)
|
129
|
+
when 'lib'
|
130
|
+
copy_lib_templates(destination_path)
|
131
|
+
when 'docs'
|
132
|
+
copy_doc_templates(destination_path)
|
133
|
+
when 'generator_logic'
|
134
|
+
copy_generator_logic(destination_path)
|
135
|
+
else
|
136
|
+
say " โ ๏ธ Unknown component: #{component}", :red
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def copy_model_templates(destination_path)
|
141
|
+
say "๐ Extracting model generator templates...", :blue
|
142
|
+
|
143
|
+
copy_directory_templates("models", destination_path)
|
144
|
+
|
145
|
+
say " โ
Model generator templates extracted", :green
|
146
|
+
end
|
147
|
+
|
148
|
+
def copy_controller_templates(destination_path)
|
149
|
+
say "๐ Extracting controller generator templates...", :blue
|
150
|
+
|
151
|
+
copy_directory_templates("controllers", destination_path)
|
152
|
+
|
153
|
+
say " โ
Controller generator templates extracted", :green
|
154
|
+
end
|
155
|
+
|
156
|
+
def copy_error_templates(destination_path)
|
157
|
+
say "๐ Extracting error generator templates...", :blue
|
158
|
+
|
159
|
+
copy_directory_templates("errors", destination_path)
|
160
|
+
|
161
|
+
say " โ
Error generator templates extracted", :green
|
162
|
+
end
|
163
|
+
|
164
|
+
def copy_lib_templates(destination_path)
|
165
|
+
say "๐ Extracting lib generator templates...", :blue
|
166
|
+
|
167
|
+
copy_directory_templates("lib", destination_path)
|
168
|
+
|
169
|
+
say " โ
Lib generator templates extracted", :green
|
170
|
+
end
|
171
|
+
|
172
|
+
def copy_doc_templates(destination_path)
|
173
|
+
say "๐ Extracting documentation generator templates...", :blue
|
174
|
+
|
175
|
+
copy_directory_templates("doc", destination_path)
|
176
|
+
|
177
|
+
say " โ
Documentation generator templates extracted", :green
|
178
|
+
end
|
179
|
+
|
180
|
+
def copy_generator_logic(destination_path)
|
181
|
+
say "๐ Extracting generator logic code...", :blue
|
182
|
+
|
183
|
+
# Copy the main generator files with proper Rails registration
|
184
|
+
generator_files = {
|
185
|
+
'install_generator.rb' => 'install_generator.rb',
|
186
|
+
'propel_facets.rb' => 'propel_facets.rb'
|
187
|
+
}
|
188
|
+
|
189
|
+
copied_count = 0
|
190
|
+
generator_files.each do |source_name, dest_name|
|
191
|
+
source_file = File.join(File.dirname(__dir__), source_name)
|
192
|
+
dest_file = File.join(destination_path, dest_name)
|
193
|
+
|
194
|
+
if File.exist?(source_file)
|
195
|
+
if source_name == 'install_generator.rb'
|
196
|
+
# Copy install generator with proper Rails namespace for host app
|
197
|
+
source_content = File.read(source_file)
|
198
|
+
|
199
|
+
# Ensure proper module structure for Rails generator registration
|
200
|
+
modified_content = source_content.gsub(
|
201
|
+
/^class PropelFacets::InstallGenerator/,
|
202
|
+
"module PropelFacets\n class InstallGenerator"
|
203
|
+
)
|
204
|
+
|
205
|
+
# Add module end if we added module start
|
206
|
+
if modified_content != source_content
|
207
|
+
modified_content += "\nend" unless modified_content.end_with?("end\n") || modified_content.end_with?("end")
|
208
|
+
end
|
209
|
+
|
210
|
+
FileUtils.mkdir_p(File.dirname(dest_file))
|
211
|
+
File.write(dest_file, modified_content)
|
212
|
+
else
|
213
|
+
# Copy other files normally
|
214
|
+
FileUtils.cp(source_file, dest_file)
|
215
|
+
end
|
216
|
+
copied_count += 1
|
217
|
+
else
|
218
|
+
say " โ ๏ธ Generator file not found: #{source_name}", :yellow
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Also copy test directory if it exists
|
223
|
+
test_source_dir = File.join(File.dirname(__dir__), "test")
|
224
|
+
test_dest_dir = File.join(destination_path, "test")
|
225
|
+
|
226
|
+
if Dir.exist?(test_source_dir)
|
227
|
+
copy_directory_recursively(test_source_dir, test_dest_dir)
|
228
|
+
copied_count += 1
|
229
|
+
say " ๐ Test directory copied", :blue
|
230
|
+
end
|
231
|
+
|
232
|
+
if copied_count > 0
|
233
|
+
say " โ
Generator logic code extracted with proper Rails registration (#{copied_count} files/directories)", :green
|
234
|
+
else
|
235
|
+
say " โ ๏ธ No generator logic files found", :yellow
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def copy_directory_templates(directory, destination_path)
|
240
|
+
source_dir = File.join(self.class.source_root, directory)
|
241
|
+
dest_dir = File.join(destination_path, "templates", directory)
|
242
|
+
|
243
|
+
if Dir.exist?(source_dir)
|
244
|
+
FileUtils.mkdir_p(dest_dir)
|
245
|
+
copy_directory_recursively(source_dir, dest_dir)
|
246
|
+
else
|
247
|
+
say " โ ๏ธ Directory not found: #{directory}", :yellow
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def copy_directory_recursively(source, destination)
|
252
|
+
Dir.glob("#{source}/**/*", File::FNM_DOTMATCH).each do |source_file|
|
253
|
+
next if File.directory?(source_file)
|
254
|
+
next if File.basename(source_file).start_with?('.')
|
255
|
+
|
256
|
+
relative_path = source_file.sub("#{source}/", '')
|
257
|
+
dest_file = File.join(destination, relative_path)
|
258
|
+
|
259
|
+
FileUtils.mkdir_p(File.dirname(dest_file))
|
260
|
+
FileUtils.cp(source_file, dest_file)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def show_completion_message(destination_path)
|
265
|
+
say ""
|
266
|
+
say "๐ PropelFacets GENERATOR unpacked successfully!", :green
|
267
|
+
say ""
|
268
|
+
say "๐ Generator extracted to: #{destination_path}/", :bold
|
269
|
+
say "๐ Templates available at: #{destination_path}/templates/", :bold
|
270
|
+
say ""
|
271
|
+
|
272
|
+
say "๐ง GENERATOR customization guide:", :bold
|
273
|
+
say " โ๏ธ Main generator: #{destination_path}/propel_facets_generator.rb"
|
274
|
+
say " ๐ Model templates: #{destination_path}/templates/models/"
|
275
|
+
say " ๐ฎ Controller templates: #{destination_path}/templates/controllers/"
|
276
|
+
say " ๐จ Error templates: #{destination_path}/templates/errors/"
|
277
|
+
say " ๐ Lib templates: #{destination_path}/templates/lib/"
|
278
|
+
say " ๐ Documentation: #{destination_path}/templates/doc/"
|
279
|
+
say " ๐งช Tests: #{destination_path}/test/"
|
280
|
+
say ""
|
281
|
+
|
282
|
+
say "โ ๏ธ Important: You now have a LOCAL GENERATOR, not gem generator:", :yellow
|
283
|
+
say " โข Your local generator overrides the gem version completely"
|
284
|
+
say " โข Customize any part: logic, templates, options, behavior"
|
285
|
+
say " โข Commit generator code to version control"
|
286
|
+
say " โข Update manually when PropelFacets gem is updated"
|
287
|
+
say ""
|
288
|
+
|
289
|
+
say "๐ก Test your customized generator:", :blue
|
290
|
+
say " # Your local generator will be used instead of the gem:"
|
291
|
+
say " rails generate propel_facets"
|
292
|
+
say ""
|
293
|
+
|
294
|
+
say "๐ To use gem generator again:", :cyan
|
295
|
+
say " rm -rf #{destination_path} # Removes local generator, falls back to gem"
|
296
|
+
say ""
|
297
|
+
|
298
|
+
say "๐ Now you can:", :green
|
299
|
+
say " โข Modify facet templates for your organization's JSON standards"
|
300
|
+
say " โข Customize model concern behavior and default facets"
|
301
|
+
say " โข Add custom error handling for your API patterns"
|
302
|
+
say " โข Create company-specific facet naming conventions"
|
303
|
+
say " โข Extend the JSON serialization capabilities"
|
304
|
+
say " โข Add new utility classes for API parameter handling"
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: propel_facets
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Propel Team
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-07-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '9.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '7.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '9.0'
|
33
|
+
description: A Rails generator that provides a flexible system for defining different
|
34
|
+
JSON representations of ActiveRecord models and automatically connecting them to
|
35
|
+
controller actions.
|
36
|
+
email:
|
37
|
+
- admin@propel-hq.dev
|
38
|
+
executables: []
|
39
|
+
extensions: []
|
40
|
+
extra_rdoc_files: []
|
41
|
+
files:
|
42
|
+
- CHANGELOG.md
|
43
|
+
- LICENSE
|
44
|
+
- README.md
|
45
|
+
- lib/generators/propel_facets/README.md
|
46
|
+
- lib/generators/propel_facets/USAGE
|
47
|
+
- lib/generators/propel_facets/install_generator.rb
|
48
|
+
- lib/generators/propel_facets/templates/config/propel_facets.rb
|
49
|
+
- lib/generators/propel_facets/templates/controllers/concerns/facet_renderer.rb
|
50
|
+
- lib/generators/propel_facets/templates/controllers/concerns/strong_params_helper.rb
|
51
|
+
- lib/generators/propel_facets/templates/doc/json_facet.md
|
52
|
+
- lib/generators/propel_facets/templates/errors/application_error.rb
|
53
|
+
- lib/generators/propel_facets/templates/errors/missing_facet.rb
|
54
|
+
- lib/generators/propel_facets/templates/lib/api_params.rb
|
55
|
+
- lib/generators/propel_facets/templates/models/application_record.rb
|
56
|
+
- lib/generators/propel_facets/templates/models/concerns/model_facet.rb
|
57
|
+
- lib/generators/propel_facets/test/propel_facets_generator_test.rb
|
58
|
+
- lib/generators/propel_facets/test/test_helper.rb
|
59
|
+
- lib/generators/propel_facets/unpack_generator.rb
|
60
|
+
- lib/propel_facets.rb
|
61
|
+
homepage: https://github.com/propel-hq/propel_rails.git
|
62
|
+
licenses:
|
63
|
+
- MIT
|
64
|
+
metadata:
|
65
|
+
homepage_uri: https://github.com/propel-hq/propel_rails.git
|
66
|
+
source_code_uri: https://github.com/propel-hq/propel_rails.git
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.2.0
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubygems_version: 3.4.19
|
83
|
+
signing_key:
|
84
|
+
specification_version: 4
|
85
|
+
summary: Rails generator for flexible JSON representations
|
86
|
+
test_files: []
|