Top Level Namespace
Defined Under Namespace
Modules: ActionModels, Api, BulletTrain, Scaffolding, SuperScaffoldBase, SuperScaffoldingHelper, TerminalCommands Classes: ConversationsGenerator, FieldGenerator, IncomingWebhooksGenerator, JoinModelGenerator, OauthProviderGenerator, SuperScaffoldGenerator
Constant Summary collapse
- FIELD_PARTIALS =
{ address_field: nil, boolean: "boolean", buttons: "string", cloudinary_image: "string", color_picker: "string", date_and_time_field: "datetime", date_field: "date", email_field: "string", emoji_field: "string", file_field: "attachment", image: "attachment", options: "string", password_field: "string", phone_field: "string", super_select: "string", text_area: "text", text_field: "string", number_field: "integer", trix_editor: "text" }
Instance Method Summary collapse
-
#check_class_name_for_namespace_conflict(class_name) ⇒ Object
class_name is a potentially namespaced class like “Tasks::Widget” or “Task::Widget”.
- #check_required_options_for_attributes(scaffolding_type, attributes, child, parent = nil) ⇒ Object
- #get_untracked_files ⇒ Object
- #oauth_scaffold_add_line_to_file(file, content, after, options, additional_options = {}) ⇒ Object
- #oauth_scaffold_directory(directory, options) ⇒ Object
-
#oauth_scaffold_file(file, options) ⇒ Object
there is a bunch of stuff duplicate here, but i’m OK with that for now.
- #oauth_transform_string(string, options) ⇒ Object
Instance Method Details
#check_class_name_for_namespace_conflict(class_name) ⇒ Object
class_name is a potentially namespaced class like “Tasks::Widget” or “Task::Widget”. Here we ensure that the namespace doesn’t clobber an existing model. If it does we suggest that the namespace could be pluralized.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/scaffolding/script.rb', line 56 def check_class_name_for_namespace_conflict(class_name) if class_name.include?("::") parts = class_name.split("::") # ["Task", "Widget"] # We drop the last segment because that's tne new model we're trying to create parts.pop # ["Task"] possible_conflicted_class_name = "" parts.each do |part| possible_conflicted_class_name += "::#{part}" begin klass = possible_conflicted_class_name.constantize is_active_record_class = klass&.ancestors&.include?(ActiveRecord::Base) is_aactive_hash_class = klass&.ancestors&.include?(ActiveHash::Base) if klass && (is_active_record_class || is_aactive_hash_class) problematic_namespace = possible_conflicted_class_name[2..] puts "It looks like the namespace you gave for this model conflicts with an existing class: #{klass.name}".red puts "You should use a namespace that doesn't clobber an existing class.".red puts "" puts "We reccomend using the pluralized version of the existing class.".red puts "" puts "For instance instead of #{problematic_namespace} use #{problematic_namespace.pluralize}".red exit end rescue NameError # this is good actually, it means we don't already have a class that will be clobbered end end end end |
#check_required_options_for_attributes(scaffolding_type, attributes, child, parent = nil) ⇒ Object
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/scaffolding/script.rb', line 85 def (scaffolding_type, attributes, child, parent = nil) tableized_parent = nil # Ensure the parent attribute name has the proper namespacing for adding as a foreign key. if parent.present? if child.include?("::") && parent.include?("::") child_parts = child.split("::") parent_parts = parent.split("::") child_parts_dup = child_parts.dup parent_parts_dup = parent_parts.dup # Pop off however many spaces match. child_parts_dup.each.with_index do |child_part, idx| if child_part == parent_parts_dup[idx] child_parts.shift parent_parts.shift else tableized_parent = parent_parts.map(&:downcase).join("_") break end end end # In case we're not working with namespaces, just tableize the parent as is. tableized_parent ||= parent.tableize.singularize.tr("/", "_") if parent.present? end generation_command = case scaffolding_type when "crud" "bin/rails generate model #{child} #{tableized_parent}:references" when "crud-field" "" # This is blank so we can create the proper migration name first after we get the attributes. end # Even if there are attributes passed to the scaffolder, # They may already exist in previous migrations, so we # only register ones that need to be generated. # i.e. - *_ids attributes in the join-model scaffolder. attributes_to_generate = [] attributes.each do |attribute| parts = attribute.split(":") name = parts.shift type = parts.join(":") type_without_option = type.gsub(/{.*}/, "") unless Scaffolding.valid_attribute_type?(type) raise "You have entered an invalid attribute type: #{type}. General data types are used when creating new models, but Bullet Train " \ "uses field partials when Super Scaffolding, i.e. - `name:text_field` as opposed to `name:string`. " \ "Please refer to the Field Partial documentation to view which attribute types are available." end # extract any options they passed in with the field. type, = type.scan(/^(.*){(.*)}/).first || type # create a hash of the options. = if .split(",").map do |s| option_name, option_value = s.split("=") [option_name.to_sym, option_value || true] end.to_h else {} end if type == "image" && cloudinary_enabled? && [:multiple] puts "You have Cloudinary enabled and tried to scaffold an image field with the `multiple` option. " \ "At this time we do not support multiple images in a single Cloudinary image attribute. " \ "We hope to add support for it in the future. " \ "For now you could use individual named image attributes, or you might try disabling Cloudinary and using ActiveStorage.".red exit end data_type = if type == "image" && cloudinary_enabled? "string" elsif [:multiple] case type when "file_field" "attachments" when "image" "attachments" else "jsonb" end else FIELD_PARTIALS[type_without_option.to_sym] end if name.match?(/_id$/) || name.match?(/_ids$/) ||= {} unless [:vanilla] name_without_id = if name.match?(/_id$/) name.delete_suffix("_id") elsif name.match?(/_ids$/) name.delete_suffix("_ids") end [:class_name] ||= name_without_id.classify file_name = Dir.glob("app/models/**/*.rb").find { |model| model.match?(/#{[:class_name].underscore}\.rb/) } || "" begin class_name_constant = [:class_name].constantize rescue NameError if [:class_name] == child puts "" puts "You appear to be tryingo scaffold a model that references itself. Unfotunately this needs to be a two-step process.".red puts "First you should generate the model without the reference, and then add the reference as a new field. For instance:".red puts "" puts " rails generate super_scaffold #{child}#{" " + parent if parent.present?}".red puts " rails generate super_scaffold:field #{child} #{name}:#{type}".red puts "" puts "If `#{name}` is just a regular field and isn't backed by an ActiveRecord association, you can skip all this with the `{vanilla}` option, e.g.:".red puts "" puts " rails generate super_scaffold #{child}#{" " + parent if parent.present?} #{name}:#{type}{vanilla}".red puts "" exit else # We don't do anything special here because we'll end up triggering the error message below. A self-referential model # is kind of a special case that's worth calling out specifically. If we just can't find the model the messaging below # should be sufficient to get folks on the right track. end end # If a model is namespaced, the parent's model file might exist under # `app/models/`, but sometimes these files are modules that resolve # table names by providing a prefix as opposed to an actual ApplicationRecord. # This check ensures that the _id attribute really is a model. is_active_record_class = class_name_constant&.ancestors&.include?(ActiveRecord::Base) unless File.exist?(file_name) && is_active_record_class puts "" puts "Attributes that end with `_id` or `_ids` trigger awesome, powerful magic in Super Scaffolding. However, because no `#{[:class_name]}` class was found defined in your app, you'll need to specify a `class_name` that exists to let us know what model class is on the other side of the association, like so:".red puts "" puts " bin/super-scaffold #{scaffolding_type} #{child}#{" " + parent if parent.present?} #{name}:#{type}{class_name=#{name.gsub(/_ids?$/, "").classify}}".red puts "" puts "If `#{name}` is just a regular field and isn't backed by an ActiveRecord association, you can skip all this with the `{vanilla}` option, e.g.:".red puts "" puts " bin/super-scaffold #{scaffolding_type} #{child}#{" " + parent if parent.present?} #{name}:#{type}{vanilla}".red puts "" exit end end end # TODO: Is there ever a case that we want this to be a string? data_type = "references" if name.match?(/_id$/) # For join models, we don't want to generate a migration when # running the crud-field scaffolder in the last step, so we skip *_ids. # Addresses belong_to :addressable, so they don't have to be represented in a migration. unless name.match?(/_ids$/) || data_type.nil? generation_command += " #{name_without_id || name}:#{data_type}" attributes_to_generate << name end end # Generate the models/migrations with the attributes passed. if attributes_to_generate.any? case scaffolding_type # "join-model" and "oauth-provider" are not here because the # `rails g` command is written inline in their own respective scaffolders. when "crud" puts "Generating #{child} model with '#{generation_command}'".green when "crud-field" generation_command = "bin/rails generate migration add_#{attributes_to_generate.join("_and_")}_to_#{child.tableize.tr("/", "_")}#{generation_command}" puts "Adding new fields to #{child} with '#{generation_command}'".green end puts "" unless @options["skip-migration-generation"] untracked_files = get_untracked_files generation_thread = Thread.new { `#{generation_command}` } generation_thread.join # Wait for the process to finish. newly_untracked_files = get_untracked_files if (newly_untracked_files - untracked_files).size.zero? = <<~MESSAGE Since you have already created the #{child} model, Super Scaffolding won't allow you to re-create it. You can either delete the model and try Super Scaffolding again, or add the `--skip-migration-generation` flag to Super Scaffold the classic Bullet Train way. MESSAGE puts "" puts .red exit 1 end end end end |
#get_untracked_files ⇒ Object
48 49 50 |
# File 'lib/scaffolding/script.rb', line 48 def get_untracked_files `git ls-files --other --exclude-standard`.split("\n") end |
#oauth_scaffold_add_line_to_file(file, content, after, options, additional_options = {}) ⇒ Object
101 102 103 104 105 106 107 |
# File 'lib/scaffolding/oauth_providers.rb', line 101 def oauth_scaffold_add_line_to_file(file, content, after, , = {}) empty_transformer = Scaffolding::Transformer.new("", "") file = oauth_transform_string(file, ) content = oauth_transform_string(content, ) after = oauth_transform_string(after, ) empty_transformer.add_line_to_file(file, content, after, ) end |
#oauth_scaffold_directory(directory, options) ⇒ Object
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# File 'lib/scaffolding/oauth_providers.rb', line 1 def oauth_scaffold_directory(directory, ) transformed_directory_name = oauth_transform_string(directory, ) empty_transformer = Scaffolding::Transformer.new("", "") begin Dir.mkdir(transformed_directory_name) rescue Errno::EEXIST => _ puts "The directory #{transformed_directory_name} already exists, skipping generation.".yellow rescue Errno::ENOENT => _ puts "Proceeding to generate '#{transformed_directory_name}'." end Dir.foreach(empty_transformer.resolve_template_path(directory)) do |file| file = "#{directory}/#{file}" unless File.directory?(empty_transformer.resolve_template_path(file)) oauth_scaffold_file(file, ) end end end |
#oauth_scaffold_file(file, options) ⇒ Object
there is a bunch of stuff duplicate here, but i’m OK with that for now.
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/scaffolding/oauth_providers.rb', line 21 def oauth_scaffold_file(file, ) transformed_file_name = oauth_transform_string(file, ) transformed_file_content = [] empty_transformer = Scaffolding::Transformer.new("", "") skipping = false File.open(empty_transformer.resolve_template_path(file)).each_line do |line| if line.include?("# 🚅 skip when scaffolding.") next end if line.include?("# 🚅 skip this section when scaffolding.") skipping = true next end if line.include?("# 🚅 stop any skipping we're doing now.") skipping = false next end if skipping next end # remove lines with 'remove in scaffolded files.' unless line.include?("remove in scaffolded files.") # only transform it if it doesn't have the lock emoji. if line.include?("🔒") # remove any comments that start with a lock. line.gsub!(/\s+?#\s+🔒.*/, "") else line = oauth_transform_string(line, ) end transformed_file_content << line end end transformed_file_content = transformed_file_content.join transformed_directory_name = File.dirname(transformed_file_name) unless File.directory?(transformed_directory_name) FileUtils.mkdir_p(transformed_directory_name) end puts "Writing '#{transformed_file_name}'." unless silence_logs? File.write(transformed_file_name, transformed_file_content) end |
#oauth_transform_string(string, options) ⇒ Object
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/scaffolding/oauth_providers.rb', line 73 def oauth_transform_string(string, ) name = [:our_provider_name] empty_transformer = Scaffolding::Transformer.new("", "") # get these out of the way first. string = string.gsub("stripe_connect: Stripe", empty_transformer.encode_double_replacement_fix([:gems_provider_name] + ": " + name.titleize)) string = string.gsub("ti-money", empty_transformer.encode_double_replacement_fix([:icon])) if [:icon] string = string.gsub("omniauth-stripe-connect", empty_transformer.encode_double_replacement_fix([:omniauth_gem])) string = string.gsub("stripe_connect", empty_transformer.encode_double_replacement_fix([:gems_provider_name])) string = string.gsub("STRIPE_CLIENT_ID", empty_transformer.encode_double_replacement_fix([:api_key])) string = string.gsub("STRIPE_SECRET_KEY", empty_transformer.encode_double_replacement_fix([:api_secret])) # then try for some matches that give us a little more context on what they're looking for. string = string.gsub("stripe-account", empty_transformer.encode_double_replacement_fix(name.underscore.dasherize + "_account")) string = string.gsub("stripe_account", empty_transformer.encode_double_replacement_fix(name.underscore + "_account")) string = string.gsub("StripeAccount", empty_transformer.encode_double_replacement_fix(name + "Account")) string = string.gsub("Stripe Account", empty_transformer.encode_double_replacement_fix(name.titleize + " Account")) string = string.gsub("Stripe account", empty_transformer.encode_double_replacement_fix(name.titleize + " account")) string = string.gsub("with Stripe", empty_transformer.encode_double_replacement_fix("with " + name.titleize)) # finally, just do the simplest string replace. it's possible this can produce weird results. # if they do, try adding more context aware replacements above, e.g. what i did with 'with'. string = string.gsub("stripe", empty_transformer.encode_double_replacement_fix(name.underscore)) string = string.gsub("Stripe", empty_transformer.encode_double_replacement_fix(name)) empty_transformer.decode_double_replacement_fix(string) end |