Rails Hash to Struct - 원숭이 패치

17983 단어 rubyrails
취미 개발자(나!)는 내 사이트 몇 개를 리팩토링하느라 바빴습니다. 모델에서 직렬화된 필드 사용에 대한 게시물에서 언급한 적이 있습니다. 대부분은 모델에 직렬화 속성을 추가하기만 했습니다serialize :settings, ActiveSupport::HashWithIndifferentAccess. Rails 7.0.4는 그것을 망쳤습니다. 나는 해시 대 '문자열'에서 기호를 사용하는 것을 좋아하기 때문에 HashWithIndifferentAccess를 사용하고 있었습니다. Rails는 YAML을 사용하여 해시를 직렬화합니다. YAML에는 보안 버그가 있었고 Rails는 직렬화할 항목을 명시적으로 정의하도록 요구하여 이를 수정했습니다. 문제를 해결하는 데 약간의 시간이 걸렸지만 버그에 대한 이야기에서 그들은 기본적으로 'JSON을 사용하지 않는 이유'라고 말했습니다.

제가 리팩토링한 내용 중 일부입니다. 나는 여전히 HashWithIndifferentAccess에서 JSON으로 몇 가지 속성을 변경하는 방법을 알아 내려고 노력하고 있습니다. 다음과 같을 것 같습니다.
  • 서버 다운
  • 제거 serialize :settings, ActiveSupport::HashWithIndifferentAccess
  • 배포 변환 버전이 YMAL을 구문 분석하고 JSON으로 저장합니다
  • serialize :settings, JSON를 추가하고 재배포합니다.

  • 아마 15분 정도 걸릴 것 같지만 조금 더 생각해 보고 싶습니다.

    내가 최근에 하고 있는 것은 이러한 설정/기본 설정 해시 중 일부를 Struct(원래 OpenStruct - 그러나 포기함)로 변환하는 것입니다. 다시 말하지만 이것은 개인적인 취향일 뿐입니다. 저는 settings.acct_placeholders보다 settings['acct_placeholders']를 선호합니다. 저는 원래 config/initializers에 있는 Monkey Patch를 사용하여 이 작업을 수행했습니다.

    Hash.class_eval do
      def to_struct
        Struct.new(*keys.map(&:to_sym)).new(*values)
      end
    end
    


    아마도 좋은 생각은 아니지만 단순 해시에는 작동했지만 중첩된 해시에는 작동하지 않았습니다. Monkey patching in Rails3 Ways to Monkey-Patch Without Making a Mess 에서 Monkey Patching에 대해 조금 더 읽으면서 모듈을 사용하여 Rails 방식으로 수행하기로 결정했습니다.

    /libcore_extensions에 폴더 하나와 두 개의 하위 폴더hasharray를 추가했습니다. 하위 폴더에서 내 원숭이 패치를 추가했습니다.
  • core_extensions
  • 해시
  • as_struct.rb
  • to_struct.rb
  • 어레이
  • test_array.rb


  • 방금 개념 증명으로 어레이를 추가했습니다.

    # just a proof of concept
    module CoreExtensions
      module Array
        def test_array
          puts "test_array"
          self
        end
      end
    end
    


    이러한 패치가 작동하려면 패치를 로드해야 하므로 config/initializers에서 추가했습니다monkey_patches.rb.

    # config/initializers/money_patches.rb
    # Require all Ruby files in the core_extensions directory by class
    Dir[Rails.root.join('lib', 'core_extensions/*', '*.rb')].each { |f| require f }
    
    # Apply the monkey patches
    Array.include CoreExtensions::Array
    Hash.include CoreExtensions::Hash
    

    hash.to_struct 패치의 경우 .to_struct 및 .as_struct라는 두 개의 패치로 끝났습니다. 이것은 스핀오프와 Rails .to_json 및 .as_json입니다. 하나(.as_json)는 해시를 삭제하고 다른 하나는 변환을 수행합니다.

    # /lib/core_extensins/as_struct.rb
    # convert Hash to Struct on a single level
    module CoreExtensions
      module Hash
        def as_struct
          Struct.new(*keys.map(&:to_sym)).new(*values)
        end
      end
    end
    



    # /lib/core_extensins/to_struct.rb
    # convert Hash to a nested Struct 
    module CoreExtensions
      module Hash
        def to_struct
          hash_to_struct(self)
        end
    
        private
    
        def hash_to_struct(ahash)
          struct = ahash.as_struct # convert to struct
          struct.members.each do |m|
            if struct[m].is_a? Hash
              struct[m] = hash_to_struct(struct[m]) # nested hash, recursive call
            elsif struct[m].is_a? Array 
              # look for hashes in an array and convert to struct
              struct[m].each_index do |i|
                # normal use, an array of hashes
                struct[m][i] = hash_to_struct(struct[m][i]) if struct[m][i].is_a? Hash
                # convoluded use, an array that may contain hash(es)
                struct[m][i] = hash_in_array(struct[m][i]) if struct[m][i].is_a? Array
              end
            end
          end
          struct 
        end
    
        def hash_in_array(arr)
          arr.each_index do |ii|
            arr[ii] = hash_to_struct(arr[ii]) if arr[ii].is_a? Hash 
          end
          arr
        end 
      end
    end
    


    따라서 복잡한 중첩 해시를 정의하면(이 작업을 수행하지 않을 것입니다... 다시 개념 증명)

    h = {
      game:{id:1,date:'2022-09-11',player:6},
      players:[{name:'Joe',quota:21},{name:'Harry',quota:26},{name:'Pete',quota:14},
        {name:'don',quota:21},{name:'sally',quota:26},{name:'red',quota:14}],
      teams:[['joe','don',team:{a:1,b:2,c:3}],['harry','sally',lost:{skins:2,par3:9}],['pete','red']]}
    

    s = h.to_struct를 호출하면 복잡한 Struct가 생성됩니다.

    <struct                                               
     game=<struct  id=1, date="2022-09-11", player=6>,    
     players=                                              
      [<struct  name="Joe", quota=21>,                    
       <struct  name="Harry", quota=26>,                  
       <struct  name="Pete", quota=14>,                   
       <struct  name="don", quota=21>,                    
       <struct  name="sally", quota=26>,                  
       <struct  name="red", quota=14>],                   
     teams=                                                
      [["joe", "don", <struct  team=<struct  a=1, b=2, c=3>>],
       ["harry", "sally", <struct  lost=<struct  skins=2, par3=9>>],
       ["pete", "red"]]>     
    


    그래서

      # s.game returns 
      <struct  id=1, date="2022-09-11", player=6>
      # s.game.date return
      "2022-09-11"
    


    그것은 그!

    다시 말하지만, 저는 그저 취미 생활을 하는 사람이고 제 Ruby 기술은 깊지는 않지만 12년 전에 알고 있던 것보다 훨씬 낫습니다.

    다른하실 말씀 있나요?

    좋은 웹페이지 즐겨찾기