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.
@@ -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
@@ -0,0 +1,3 @@
1
+ module PropelFacets
2
+ VERSION = "0.1.1"
3
+ 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: []