2
\$\begingroup\$

Currently I'm working on parsing data from a form service into a pdf form. I created 2 classes one inheriting from the other one. However, I see that the classes I created are growing in lines of code and I'm concern that I am concern that these classes become hard to maintain.

I would like to request feedback from the community regarding OOP principles, style violations and best practices. If there are also security concerns points those out. Here are the classes using inheritance.

require 'open-uri' class FillablePdfForm attr_writer :template_path attr_reader :attributes def initialize fill_out end def export(file_name, output_file_path: nil) output_file_path ||= "#{Rails.root}/tmp/pdfs/#{application_date}_#{which_application}_#{what_status}_#{file_name}.pdf" pdftk.fill_form template_path, output_file_path, attributes output_file_path end def form_fields pdftk.get_field_names template_path end def template_path pdf_form_application @template_path ||= "#{Rails.root}/public/pdfs/#{which_application.downcase}_#{what_status.downcase}.pdf" end protected def application_date Time.now.strftime('%Y-%m-%d') end def pdf_form_application File.open("#{Rails.root}/public/pdfs/#{which_application.downcase}_#{what_status.downcase}.pdf", "wb") do |file| file << URI.parse(url_of_pdf_form).read end end def url_of_pdf_form @form_fields.find do |field| field['label'] == "#{which_application}_#{what_status}_URL" end['default'] end def attributes @attributes ||= {} end def fill(key, value) attributes[key.to_s] = value end def pdftk @pdftk ||= PdfForms.new end def fill_out raise 'Must be overridden by child class' end end 

Also, I'm passing in the constructor FormStack::Form.new but I was wondering if I should pass it as an argument.

class PdfScrie < FillablePdfForm def initialize(user_submission_data, form_fields) @user_submission_data = user_submission_data @form_fields = form_fields @formstack = FormStack::Form.new super() end private # PDF Constants VALUE_CHECKBOX_ON = 'On'.freeze OPTION_SEP = ' | '.freeze LABEL_APPLICATION = 'APPLICATION'.freeze LABEL_STATUS = 'STATUS'.freeze def fill_out form_fields.each do |field| # PDF form fields unless dictionary[field] Rails.logger.warn "#{self.class.name}: Missing \"#{field}\" mapping." next end id = dictionary[field].split(OPTION_SEP)[0] @user_submission_data .select { |field_data| field_data[FormStack::Form::ATTR_FIELD_ID] == id } .each { |field_data| fill_form_with_data(field, field_data) } end end def fill_form_with_data(field, field_data) field_number = field_data[FormStack::Form::ATTR_FIELD_ID] value = field_data[FormStack::Form::ATTR_FIELD_VALUE] field_type = FormStack::Form::field_type(@form_fields, field_number) self_method = "fill_#{field_type}".to_sym if self.respond_to?(self_method, :include_private) send(self_method, field_number, field, value) else fill(field, value) end end # Field Type Methods def fill_address(field_number, field, value) address_by_section = FormStack::Form.parse_formstack_nested_attrs(value) address_by_section.each do |section, value| fill(field, value) if form_field_has_section?(field, section) || FormStack::Form::address_section_aparment?(field, section) end end def fill_phone(field_number, field, value) parse_phone_number(value) fill(field, @phone_number_sections.shift) end def fill_name(field_number, field, value) full_name = FormStack::Form::parse_name(value) fill(field, full_name) end def fill_checkbox(field_number, field, value) if FormStack::Form::field_is_grouped_checkbox(@form_fields, field_number) FormStack::Form::parse_checked_options(value).each do |option| fill(field, VALUE_CHECKBOX_ON) if checked_option_matches_value(field, option) end else fill(field, value) end end # END Field Type Methods # Helpers def checked_option_matches_value(field, option) dictionary[field].split(OPTION_SEP)[1].include?(option) end def parse_phone_number(phone_number) if phone_number_sections_empty? @phone_number_sections = FormStack::Form::parse_phone(phone_number) end end def phone_number_sections_empty? @phone_number_sections.nil? || @phone_number_sections.empty? end def form_field_has_section?(form_field_name, address_section) form_field_name.include? address_section.upcase end def dictionary @dictionary ||= JSON.parse(find_dictionary['section_text']) end def find_dictionary @formstack.find_field_by_label("#{which_application}_#{what_status}_DICTIONARY", @form_fields) end def which_application @formstack.find_value_by_label(LABEL_APPLICATION, @form_fields, @user_submission_data) end def what_status @formstack .find_value_by_label(LABEL_STATUS, @form_fields, @user_submission_data) end end 

Feel free to point out areas of improvement, feedback, best practices and resources.

\$\endgroup\$

    1 Answer 1

    3
    \$\begingroup\$

    An OOP suggestion by way of analogy:

    Consider a Document and a Cabinet. Should a Document know how to put a copy of itself in a Cabinet? Or should a Cabinet know how to copy a document into itself?

    In a small program, either is probably fine. However, it will become unmaintainable as more ways for them to interact are added as the system grows in complexity.

    When that happens, there should be an actor at a higher abstraction level, e.g. a "secretary" that makes a copy (perhaps by requesting it via Document#copy) and files the copy into the cabinet (perhaps by requesting it of the Cabinet#file). In their respective isolated context, they don't need to interact or know about each other, so their implementations would not contain references to each other.

    If there is only ever "one secretary", just leave the action at the top level abstraction -- the main program. As complexity grows, perhaps a Secretary class can be defined.

    However, remember that Secretary's actions are the higher abstraction and Document is a lower abstraction. The dependency directionality is important. A Document shouldn't be imposing a Secretary to act.

    Where this applies to your code:

    1. export
      • FillablePdfForm is the Document
      • PdfForms is the Cabinet
      • problem: FillablePdfForm#export is the Document putting itself in the Cabinet
    2. fill_form_with_data
      • field_data and FormStack::Form are the Documents
      • PdfScrie is the Cabinet
      • problem: PdfScrie#fill_form_with_data is the Cabinet putting the Document in itself

    By the way, this concept is the D in SOLID

    Another issue is where FillablePdfForm#template_path calls which_application, which is implemented in the subclass Scrie, which the L in SOLID talks about.

    The Wikipedia articles are a little thick to get through though, Google around for some alternative explanations of each of the SOLID principles.

    \$\endgroup\$

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.