sumaki 0.2.0 → 0.3.0
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 +4 -4
- data/.rubocop.yml +2 -2
- data/Gemfile.lock +14 -12
- data/README.md +45 -6
- data/lib/sumaki/adapter/hash.rb +16 -0
- data/lib/sumaki/model/associations/association.rb +53 -0
- data/lib/sumaki/model/associations/collection.rb +53 -0
- data/lib/sumaki/model/associations/reflection.rb +64 -0
- data/lib/sumaki/model/associations.rb +159 -0
- data/lib/sumaki/model/attribute.rb +43 -5
- data/lib/sumaki/model/enum.rb +11 -6
- data/lib/sumaki/model.rb +66 -2
- data/lib/sumaki/version.rb +1 -1
- metadata +6 -3
- data/lib/sumaki/model/association.rb +0 -116
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 4cc5d96c3a979b46ace35c6bcae70136875b415af2dbe971f45731ff69aa61ff
         | 
| 4 | 
            +
              data.tar.gz: dbae61d4d18b0c6e3034620ea3dbe77467b4133da5df1eb45b9d89c927d98504
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 9a7ecf9d761bf8b6f8ff2b5236090f83105795dcb78e45d27c109a7377ead5ed323a38e51a6b3be5996bedd95917e7676e13894dd16517bac180de6ea589be6f
         | 
| 7 | 
            +
              data.tar.gz: 1c3624fdf113f3d260e58e00f68c96f51abcdac1fe1f55e7f30ec93a3b62159b3a051618a3bfe4b9c791f66499d6160429083c73eb90d2661d97e027c74c0f16
         | 
    
        data/.rubocop.yml
    CHANGED
    
    | @@ -5,12 +5,12 @@ AllCops: | |
| 5 5 |  | 
| 6 6 | 
             
            Lint/ConstantDefinitionInBlock:
         | 
| 7 7 | 
             
              Exclude:
         | 
| 8 | 
            -
                - 'spec/sumaki/model/ | 
| 8 | 
            +
                - 'spec/sumaki/model/associations_spec.rb'
         | 
| 9 9 | 
             
                - 'spec/sumaki/model/attribute_spec.rb'
         | 
| 10 10 | 
             
                - 'spec/sumaki/model/enum_spec.rb'
         | 
| 11 11 |  | 
| 12 12 | 
             
            RSpec/LeakyConstantDeclaration:
         | 
| 13 13 | 
             
              Exclude:
         | 
| 14 | 
            -
                - 'spec/sumaki/model/ | 
| 14 | 
            +
                - 'spec/sumaki/model/associations_spec.rb'
         | 
| 15 15 | 
             
                - 'spec/sumaki/model/attribute_spec.rb'
         | 
| 16 16 | 
             
                - 'spec/sumaki/model/enum_spec.rb'
         | 
    
        data/Gemfile.lock
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            PATH
         | 
| 2 2 | 
             
              remote: .
         | 
| 3 3 | 
             
              specs:
         | 
| 4 | 
            -
                sumaki (0. | 
| 4 | 
            +
                sumaki (0.3.0)
         | 
| 5 5 | 
             
                  minenum
         | 
| 6 6 |  | 
| 7 7 | 
             
            GEM
         | 
| @@ -14,14 +14,14 @@ GEM | |
| 14 14 | 
             
                diff-lcs (1.5.1)
         | 
| 15 15 | 
             
                docile (1.4.0)
         | 
| 16 16 | 
             
                io-console (0.7.2)
         | 
| 17 | 
            -
                irb (1. | 
| 18 | 
            -
                  rdoc
         | 
| 17 | 
            +
                irb (1.13.1)
         | 
| 18 | 
            +
                  rdoc (>= 4.0.0)
         | 
| 19 19 | 
             
                  reline (>= 0.4.2)
         | 
| 20 20 | 
             
                json (2.7.2)
         | 
| 21 21 | 
             
                language_server-protocol (3.17.0.3)
         | 
| 22 22 | 
             
                minenum (0.1.0)
         | 
| 23 23 | 
             
                parallel (1.24.0)
         | 
| 24 | 
            -
                parser (3.3.0 | 
| 24 | 
            +
                parser (3.3.1.0)
         | 
| 25 25 | 
             
                  ast (~> 2.4.1)
         | 
| 26 26 | 
             
                  racc
         | 
| 27 27 | 
             
                psych (5.1.2)
         | 
| @@ -31,10 +31,11 @@ GEM | |
| 31 31 | 
             
                rake (13.2.1)
         | 
| 32 32 | 
             
                rdoc (6.6.3.1)
         | 
| 33 33 | 
             
                  psych (>= 4.0.0)
         | 
| 34 | 
            -
                regexp_parser (2.9. | 
| 35 | 
            -
                reline (0.5. | 
| 34 | 
            +
                regexp_parser (2.9.2)
         | 
| 35 | 
            +
                reline (0.5.7)
         | 
| 36 36 | 
             
                  io-console (~> 0.5)
         | 
| 37 | 
            -
                rexml (3.2. | 
| 37 | 
            +
                rexml (3.2.8)
         | 
| 38 | 
            +
                  strscan (>= 3.0.9)
         | 
| 38 39 | 
             
                rspec (3.13.0)
         | 
| 39 40 | 
             
                  rspec-core (~> 3.13.0)
         | 
| 40 41 | 
             
                  rspec-expectations (~> 3.13.0)
         | 
| @@ -44,11 +45,11 @@ GEM | |
| 44 45 | 
             
                rspec-expectations (3.13.0)
         | 
| 45 46 | 
             
                  diff-lcs (>= 1.2.0, < 2.0)
         | 
| 46 47 | 
             
                  rspec-support (~> 3.13.0)
         | 
| 47 | 
            -
                rspec-mocks (3.13. | 
| 48 | 
            +
                rspec-mocks (3.13.1)
         | 
| 48 49 | 
             
                  diff-lcs (>= 1.2.0, < 2.0)
         | 
| 49 50 | 
             
                  rspec-support (~> 3.13.0)
         | 
| 50 51 | 
             
                rspec-support (3.13.1)
         | 
| 51 | 
            -
                rubocop (1.63. | 
| 52 | 
            +
                rubocop (1.63.5)
         | 
| 52 53 | 
             
                  json (~> 2.3)
         | 
| 53 54 | 
             
                  language_server-protocol (>= 3.17.0)
         | 
| 54 55 | 
             
                  parallel (~> 1.10)
         | 
| @@ -59,13 +60,13 @@ GEM | |
| 59 60 | 
             
                  rubocop-ast (>= 1.31.1, < 2.0)
         | 
| 60 61 | 
             
                  ruby-progressbar (~> 1.7)
         | 
| 61 62 | 
             
                  unicode-display_width (>= 2.4.0, < 3.0)
         | 
| 62 | 
            -
                rubocop-ast (1.31. | 
| 63 | 
            -
                  parser (>= 3.3.0 | 
| 63 | 
            +
                rubocop-ast (1.31.3)
         | 
| 64 | 
            +
                  parser (>= 3.3.1.0)
         | 
| 64 65 | 
             
                rubocop-capybara (2.20.0)
         | 
| 65 66 | 
             
                  rubocop (~> 1.41)
         | 
| 66 67 | 
             
                rubocop-factory_bot (2.25.1)
         | 
| 67 68 | 
             
                  rubocop (~> 1.41)
         | 
| 68 | 
            -
                rubocop-rspec (2.29. | 
| 69 | 
            +
                rubocop-rspec (2.29.2)
         | 
| 69 70 | 
             
                  rubocop (~> 1.40)
         | 
| 70 71 | 
             
                  rubocop-capybara (~> 2.17)
         | 
| 71 72 | 
             
                  rubocop-factory_bot (~> 2.22)
         | 
| @@ -80,6 +81,7 @@ GEM | |
| 80 81 | 
             
                simplecov-html (0.12.3)
         | 
| 81 82 | 
             
                simplecov_json_formatter (0.1.4)
         | 
| 82 83 | 
             
                stringio (3.1.0)
         | 
| 84 | 
            +
                strscan (3.1.0)
         | 
| 83 85 | 
             
                unicode-display_width (2.5.0)
         | 
| 84 86 |  | 
| 85 87 | 
             
            PLATFORMS
         | 
    
        data/README.md
    CHANGED
    
    | @@ -93,12 +93,22 @@ class Anime | |
| 93 93 | 
             
              field :title
         | 
| 94 94 | 
             
              field :url
         | 
| 95 95 | 
             
            end
         | 
| 96 | 
            +
            ```
         | 
| 96 97 |  | 
| 98 | 
            +
            ```ruby
         | 
| 99 | 
            +
            # Read the field values
         | 
| 97 100 | 
             
            anime = Anime.new({ title: 'The Vampire Dies in No Time', url: 'https://sugushinu-anime.jp/' })
         | 
| 98 101 | 
             
            anime.title #=> 'The Vampire Dies in No Time'
         | 
| 99 102 | 
             
            anime.url #=> 'https://sugushinu-anime.jp/'
         | 
| 100 103 | 
             
            ```
         | 
| 101 104 |  | 
| 105 | 
            +
            ```ruby
         | 
| 106 | 
            +
            # Write the field value
         | 
| 107 | 
            +
            anime = Anime.new({})
         | 
| 108 | 
            +
            anime.title = 'The Vampire Dies in No Time'
         | 
| 109 | 
            +
            anime.title #=> 'The Vampire Dies in No Time'
         | 
| 110 | 
            +
            ```
         | 
| 111 | 
            +
             | 
| 102 112 | 
             
            If the data contains attributes not declared in the field, it raises no error and is simply ignored.
         | 
| 103 113 |  | 
| 104 114 | 
             
            ### Access to the sub object
         | 
| @@ -114,23 +124,31 @@ class Book | |
| 114 124 | 
             
              class Company
         | 
| 115 125 | 
             
                include Sumaki::Model
         | 
| 116 126 | 
             
                field :name
         | 
| 117 | 
            -
                field :prefecture
         | 
| 118 127 | 
             
              end
         | 
| 119 128 | 
             
            end
         | 
| 129 | 
            +
            ```
         | 
| 120 130 |  | 
| 131 | 
            +
            ```ruby
         | 
| 121 132 | 
             
            data = {
         | 
| 122 133 | 
             
              title: 'The Ronaldo Chronicles',
         | 
| 123 134 | 
             
              company: {
         | 
| 124 135 | 
             
                name: 'Autumn Books',
         | 
| 125 | 
            -
                prefecture: 'Tokyo'
         | 
| 126 136 | 
             
              }
         | 
| 127 137 | 
             
            }
         | 
| 128 138 |  | 
| 129 | 
            -
             | 
| 130 | 
            -
             | 
| 139 | 
            +
            # Read from the sub object
         | 
| 140 | 
            +
            book = Book.new(data)
         | 
| 141 | 
            +
            book.company.name #=> 'Autumn Books'
         | 
| 142 | 
            +
            ```
         | 
| 143 | 
            +
             | 
| 144 | 
            +
            ```ruby
         | 
| 145 | 
            +
            # Build a sub object
         | 
| 146 | 
            +
            book = Book.new({})
         | 
| 147 | 
            +
            book.build_company(name: 'Autumn Books')
         | 
| 148 | 
            +
            book.company #=> #<Book::Company:0x000073a618e31e80 name: "Autumn Books">
         | 
| 131 149 | 
             
            ```
         | 
| 132 150 |  | 
| 133 | 
            -
             | 
| 151 | 
            +
            Sub object is wrapped with the class inferred from the field name under the original class.
         | 
| 134 152 |  | 
| 135 153 | 
             
            This can be changed by specifying the class to wrap.
         | 
| 136 154 |  | 
| @@ -172,7 +190,9 @@ class Company | |
| 172 190 | 
             
                field :name
         | 
| 173 191 | 
             
              end
         | 
| 174 192 | 
             
            end
         | 
| 193 | 
            +
            ```
         | 
| 175 194 |  | 
| 195 | 
            +
            ```ruby
         | 
| 176 196 | 
             
            data = {
         | 
| 177 197 | 
             
              name: 'The Ronaldo Vampire Hunter Agency',
         | 
| 178 198 | 
             
              member: [
         | 
| @@ -182,11 +202,19 @@ data = { | |
| 182 202 | 
             
              ]
         | 
| 183 203 | 
             
            }
         | 
| 184 204 |  | 
| 205 | 
            +
            # Read from the sub object
         | 
| 185 206 | 
             
            company = Company.new(data)
         | 
| 186 207 | 
             
            company.member.size #=> 3
         | 
| 187 208 | 
             
            company.member[2].name #=> 'John'
         | 
| 188 209 | 
             
            ```
         | 
| 189 210 |  | 
| 211 | 
            +
            ```ruby
         | 
| 212 | 
            +
            # Build a sub object
         | 
| 213 | 
            +
            company = Company.new({})
         | 
| 214 | 
            +
            company.member.build(name: 'John')
         | 
| 215 | 
            +
            company.member[0].name #=> 'John'
         | 
| 216 | 
            +
            ```
         | 
| 217 | 
            +
             | 
| 190 218 | 
             
            The `class_name` option can also be used to specify the class to wrap.
         | 
| 191 219 |  | 
| 192 220 | 
             
            ### Access to the parent object
         | 
| @@ -227,14 +255,25 @@ class Character | |
| 227 255 | 
             
              field :name
         | 
| 228 256 | 
             
              enum :type, { vampire: 1, vampire_hunter: 2, familier: 3, editor: 4 }
         | 
| 229 257 | 
             
            end
         | 
| 258 | 
            +
            ```
         | 
| 230 259 |  | 
| 260 | 
            +
            ```ruby
         | 
| 231 261 | 
             
            data = {
         | 
| 232 262 | 
             
              name: 'John',
         | 
| 233 263 | 
             
              type: 3
         | 
| 234 264 | 
             
            }
         | 
| 235 265 |  | 
| 266 | 
            +
            # Read the enum
         | 
| 236 267 | 
             
            character = Character.new(data)
         | 
| 237 | 
            -
            character.type #=> :familier
         | 
| 268 | 
            +
            character.type.name #=> :familier
         | 
| 269 | 
            +
            character.type.familier? #=> true
         | 
| 270 | 
            +
            ```
         | 
| 271 | 
            +
             | 
| 272 | 
            +
            ```ruby
         | 
| 273 | 
            +
            # Write the enum value
         | 
| 274 | 
            +
            character = Character.new({})
         | 
| 275 | 
            +
            character.type = 1
         | 
| 276 | 
            +
            character.type.name #=> :vampire
         | 
| 238 277 | 
             
            ```
         | 
| 239 278 |  | 
| 240 279 |  | 
    
        data/lib/sumaki/adapter/hash.rb
    CHANGED
    
    | @@ -7,6 +7,22 @@ module Sumaki | |
| 7 7 | 
             
                  def get(data, key)
         | 
| 8 8 | 
             
                    data[key]
         | 
| 9 9 | 
             
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def set(data, key, value)
         | 
| 12 | 
            +
                    data[key] = value
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def build_singular(data, name)
         | 
| 16 | 
            +
                    data[name] = {}
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def build_repeated_element(_data, _name)
         | 
| 20 | 
            +
                    {}
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def apply_repeated(data, name, objects)
         | 
| 24 | 
            +
                    data[name] = objects
         | 
| 25 | 
            +
                  end
         | 
| 10 26 | 
             
                end
         | 
| 11 27 | 
             
              end
         | 
| 12 28 | 
             
            end
         | 
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Sumaki
         | 
| 4 | 
            +
              module Model
         | 
| 5 | 
            +
                module Associations
         | 
| 6 | 
            +
                  module Association
         | 
| 7 | 
            +
                    class Singular # :nodoc:
         | 
| 8 | 
            +
                      def initialize(owner, reflection)
         | 
| 9 | 
            +
                        @owner = owner
         | 
| 10 | 
            +
                        @reflection = reflection
         | 
| 11 | 
            +
                      end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                      def model
         | 
| 14 | 
            +
                        @model ||= begin
         | 
| 15 | 
            +
                          object = @owner.get(@reflection.name)
         | 
| 16 | 
            +
                          object.nil? ? nil : @reflection.model_class.new(object, parent: @owner)
         | 
| 17 | 
            +
                        end
         | 
| 18 | 
            +
                      end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                      def build_model(attrs = {})
         | 
| 21 | 
            +
                        assoc = @owner.object_accessor.build_singular(@reflection.name)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                        model = @reflection.model_class.new(assoc, parent: @owner)
         | 
| 24 | 
            +
                        model.assign(attrs)
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                        @model = model
         | 
| 27 | 
            +
                      end
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    class Repeated # :nodoc:
         | 
| 31 | 
            +
                      def initialize(owner, reflection)
         | 
| 32 | 
            +
                        @owner = owner
         | 
| 33 | 
            +
                        @reflection = reflection
         | 
| 34 | 
            +
                      end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                      def collection
         | 
| 37 | 
            +
                        @collection ||= begin
         | 
| 38 | 
            +
                          objects_or_value = @owner.get(@reflection.name)
         | 
| 39 | 
            +
                          models = if objects_or_value.is_a?(Array)
         | 
| 40 | 
            +
                                     model_class = @reflection.model_class
         | 
| 41 | 
            +
                                     objects_or_value.map { |object| model_class.new(object, parent: @owner) }
         | 
| 42 | 
            +
                                   else
         | 
| 43 | 
            +
                                     []
         | 
| 44 | 
            +
                                   end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                          @reflection.collection_class.new(models, owner: @owner)
         | 
| 47 | 
            +
                        end
         | 
| 48 | 
            +
                      end
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'forwardable'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Sumaki
         | 
| 6 | 
            +
              module Model
         | 
| 7 | 
            +
                module Associations
         | 
| 8 | 
            +
                  class Collection # :nodoc:
         | 
| 9 | 
            +
                    include Enumerable
         | 
| 10 | 
            +
                    extend Forwardable
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    singleton_class.attr_accessor :reflection
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    def_delegators :@models, :each, :[]
         | 
| 15 | 
            +
                    def_delegators :@models, :inspect, :pretty_print
         | 
| 16 | 
            +
                    def_delegators 'self.class', :reflection
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    def self.build_subclass(reflection)
         | 
| 19 | 
            +
                      subclass = Class.new(self)
         | 
| 20 | 
            +
                      subclass.reflection = reflection
         | 
| 21 | 
            +
                      subclass
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    def initialize(models = [], owner:)
         | 
| 25 | 
            +
                      @models = models
         | 
| 26 | 
            +
                      @owner = owner
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    def build(attrs = {})
         | 
| 30 | 
            +
                      object = @owner.object_accessor.build_repeated_element(reflection.name)
         | 
| 31 | 
            +
                      model = reflection.model_class.new(object, parent: @owner)
         | 
| 32 | 
            +
                      model.assign(attrs)
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      self << model
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                      model
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    def <<(...)
         | 
| 40 | 
            +
                      r = @models.<<(...)
         | 
| 41 | 
            +
                      apply
         | 
| 42 | 
            +
                      r
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    private
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    def apply
         | 
| 48 | 
            +
                      @owner.object_accessor.apply_repeated(reflection.name, @models)
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
| @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'collection'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Sumaki
         | 
| 6 | 
            +
              module Model
         | 
| 7 | 
            +
                module Associations
         | 
| 8 | 
            +
                  module Reflection
         | 
| 9 | 
            +
                    class Base # :nodoc:
         | 
| 10 | 
            +
                      def initialize(owner_class, name, class_name: nil)
         | 
| 11 | 
            +
                        @owner_class = owner_class
         | 
| 12 | 
            +
                        @name = name
         | 
| 13 | 
            +
                        @class_name = class_name
         | 
| 14 | 
            +
                      end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                      def name = @name.to_sym
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                      def model_class
         | 
| 19 | 
            +
                        @model_class ||= begin
         | 
| 20 | 
            +
                          basename = @class_name&.to_s || classify(@name.to_s)
         | 
| 21 | 
            +
                          klass = if @owner_class.const_defined?(basename)
         | 
| 22 | 
            +
                                    @owner_class.const_get(basename)
         | 
| 23 | 
            +
                                  else
         | 
| 24 | 
            +
                                    @owner_class.const_set(basename, Class.new { include Model })
         | 
| 25 | 
            +
                                  end
         | 
| 26 | 
            +
                          klass.parent = @owner_class
         | 
| 27 | 
            +
                          klass
         | 
| 28 | 
            +
                        end
         | 
| 29 | 
            +
                      end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      def association_for(model)
         | 
| 32 | 
            +
                        self.class.association_class.new(model, self)
         | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                      private
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                      def classify(str)
         | 
| 38 | 
            +
                        str.gsub(/([a-z\d]+)_?/) { |_| Regexp.last_match(1).capitalize }
         | 
| 39 | 
            +
                      end
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    class Singular < Base # :nodoc:
         | 
| 43 | 
            +
                      def self.association_class = Association::Singular
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    class Repeated < Base # :nodoc:
         | 
| 47 | 
            +
                      def self.association_class = Association::Repeated
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                      def collection_class
         | 
| 50 | 
            +
                        @collection_class ||= begin
         | 
| 51 | 
            +
                          class_name = "#{model_class.name[/(\w+)$/]}Collection"
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                          if @owner_class.const_defined?(class_name)
         | 
| 54 | 
            +
                            @owner_class.const_get(class_name)
         | 
| 55 | 
            +
                          else
         | 
| 56 | 
            +
                            @owner_class.const_set(class_name, Collection.build_subclass(self))
         | 
| 57 | 
            +
                          end
         | 
| 58 | 
            +
                        end
         | 
| 59 | 
            +
                      end
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
            end
         | 
| @@ -0,0 +1,159 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'associations/reflection'
         | 
| 4 | 
            +
            require_relative 'associations/association'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Sumaki
         | 
| 7 | 
            +
              module Model
         | 
| 8 | 
            +
                # = Sumaki::Model::Associations
         | 
| 9 | 
            +
                module Associations
         | 
| 10 | 
            +
                  def self.included(base)
         | 
| 11 | 
            +
                    base.extend ClassMethods
         | 
| 12 | 
            +
                    base.include InstanceMethods
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  module AccessorAdder
         | 
| 16 | 
            +
                    module Singular # :nodoc:
         | 
| 17 | 
            +
                      def add(model_class, methods_module, reflection)
         | 
| 18 | 
            +
                        add_getter(methods_module, reflection.name)
         | 
| 19 | 
            +
                        add_builder(methods_module, reflection.name)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                        model_class.reflections[reflection.name] = reflection
         | 
| 22 | 
            +
                      end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                      private
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                      def add_getter(methods_module, name)
         | 
| 27 | 
            +
                        methods_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
         | 
| 28 | 
            +
                          def #{name}                   # def author
         | 
| 29 | 
            +
                            association(:#{name}).model #   association(:author).model
         | 
| 30 | 
            +
                          end                           # end
         | 
| 31 | 
            +
                        RUBY
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      def add_builder(methods_module, name)
         | 
| 35 | 
            +
                        methods_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
         | 
| 36 | 
            +
                          def build_#{name}(attrs = {})              # def build_author(attrs = {})
         | 
| 37 | 
            +
                            association(:#{name}).build_model(attrs) #   association(:author).build_model(attrs)
         | 
| 38 | 
            +
                          end                                        # end
         | 
| 39 | 
            +
                        RUBY
         | 
| 40 | 
            +
                      end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                      module_function :add, :add_getter, :add_builder
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    module Repeated # :nodoc:
         | 
| 46 | 
            +
                      def add(model_class, methods_module, reflection)
         | 
| 47 | 
            +
                        methods_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
         | 
| 48 | 
            +
                          def #{reflection.name}                        # def book
         | 
| 49 | 
            +
                            association(:#{reflection.name}).collection #   association(:book).collection
         | 
| 50 | 
            +
                          end                                           # end
         | 
| 51 | 
            +
                        RUBY
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                        model_class.reflections[reflection.name] = reflection
         | 
| 54 | 
            +
                      end
         | 
| 55 | 
            +
                      module_function :add
         | 
| 56 | 
            +
                    end
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  module ClassMethods # :nodoc:
         | 
| 60 | 
            +
                    # Access to the sub object.
         | 
| 61 | 
            +
                    #
         | 
| 62 | 
            +
                    #   class Book
         | 
| 63 | 
            +
                    #     include Sumaki::Model
         | 
| 64 | 
            +
                    #     singular :company
         | 
| 65 | 
            +
                    #
         | 
| 66 | 
            +
                    #     class Company
         | 
| 67 | 
            +
                    #       include Sumaki::Model
         | 
| 68 | 
            +
                    #     end
         | 
| 69 | 
            +
                    #   end
         | 
| 70 | 
            +
                    #
         | 
| 71 | 
            +
                    #   data = {
         | 
| 72 | 
            +
                    #     title: 'The Ronaldo Chronicles',
         | 
| 73 | 
            +
                    #     company: {
         | 
| 74 | 
            +
                    #       name: 'Autumn Books',
         | 
| 75 | 
            +
                    #     }
         | 
| 76 | 
            +
                    #   }
         | 
| 77 | 
            +
                    #   book = Book.new(data)
         | 
| 78 | 
            +
                    #   book.company.class #=> Book::Company
         | 
| 79 | 
            +
                    #
         | 
| 80 | 
            +
                    # Sub object can also be created.
         | 
| 81 | 
            +
                    #
         | 
| 82 | 
            +
                    #   book = Book.new({})
         | 
| 83 | 
            +
                    #   book.build_company(name: 'Autumn Books')
         | 
| 84 | 
            +
                    #   book.company #=> #<Book::Company:0x000073a618e31e80 name: "Autumn Books">
         | 
| 85 | 
            +
                    #
         | 
| 86 | 
            +
                    # == Options
         | 
| 87 | 
            +
                    #
         | 
| 88 | 
            +
                    # [:class_name]
         | 
| 89 | 
            +
                    #   Specify the name of the class to wrap. Use this if the name of the class
         | 
| 90 | 
            +
                    #   to wrap is not inferred from the nested field names.
         | 
| 91 | 
            +
                    def singular(name, class_name: nil)
         | 
| 92 | 
            +
                      reflection = Reflection::Singular.new(self, name, class_name: class_name)
         | 
| 93 | 
            +
                      AccessorAdder::Singular.add(self, association_methods_module, reflection)
         | 
| 94 | 
            +
                    end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                    # Access to the repeated sub objects
         | 
| 97 | 
            +
                    #
         | 
| 98 | 
            +
                    #   class Company
         | 
| 99 | 
            +
                    #     include Sumaki::Model
         | 
| 100 | 
            +
                    #     repeated :member
         | 
| 101 | 
            +
                    #
         | 
| 102 | 
            +
                    #     class Member
         | 
| 103 | 
            +
                    #       include Sumaki::Model
         | 
| 104 | 
            +
                    #     end
         | 
| 105 | 
            +
                    #   end
         | 
| 106 | 
            +
                    #
         | 
| 107 | 
            +
                    #   data = {
         | 
| 108 | 
            +
                    #     name: 'The Ronaldo Vampire Hunter Agency',
         | 
| 109 | 
            +
                    #     member: [
         | 
| 110 | 
            +
                    #       { name: 'Ronaldo' },
         | 
| 111 | 
            +
                    #       { name: 'Draluc' },
         | 
| 112 | 
            +
                    #       { name: 'John' }
         | 
| 113 | 
            +
                    #     ]
         | 
| 114 | 
            +
                    #   }
         | 
| 115 | 
            +
                    #   company = Company.new(data)
         | 
| 116 | 
            +
                    #   company.member[2].class #=> Company::Member
         | 
| 117 | 
            +
                    #
         | 
| 118 | 
            +
                    # Sub object can also be created.
         | 
| 119 | 
            +
                    #
         | 
| 120 | 
            +
                    #   company = Company.new({})
         | 
| 121 | 
            +
                    #   company.member.build(name: 'John')
         | 
| 122 | 
            +
                    #   company.member[0].name #=> 'John'
         | 
| 123 | 
            +
                    #
         | 
| 124 | 
            +
                    # == Options
         | 
| 125 | 
            +
                    #
         | 
| 126 | 
            +
                    # [:class_name]
         | 
| 127 | 
            +
                    #   Specify the name of the class to wrap. Use this if the name of the class
         | 
| 128 | 
            +
                    #   to wrap is not inferred from the nested field names.
         | 
| 129 | 
            +
                    def repeated(name, class_name: nil)
         | 
| 130 | 
            +
                      reflection = Reflection::Repeated.new(self, name, class_name: class_name)
         | 
| 131 | 
            +
                      AccessorAdder::Repeated.add(self, association_methods_module, reflection)
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                    def reflections
         | 
| 135 | 
            +
                      @reflections ||= {}
         | 
| 136 | 
            +
                    end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                    private
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                    def association_methods_module
         | 
| 141 | 
            +
                      @association_methods_module ||= begin
         | 
| 142 | 
            +
                        mod = Module.new
         | 
| 143 | 
            +
                        include mod
         | 
| 144 | 
            +
                        mod
         | 
| 145 | 
            +
                      end
         | 
| 146 | 
            +
                    end
         | 
| 147 | 
            +
                  end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                  module InstanceMethods # :nodoc:
         | 
| 150 | 
            +
                    private
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                    def association(name)
         | 
| 153 | 
            +
                      @associations ||= {}
         | 
| 154 | 
            +
                      @associations[name.to_sym] ||= self.class.reflections[name.to_sym].association_for(self)
         | 
| 155 | 
            +
                    end
         | 
| 156 | 
            +
                  end
         | 
| 157 | 
            +
                end
         | 
| 158 | 
            +
              end
         | 
| 159 | 
            +
            end
         | 
| @@ -6,6 +6,31 @@ module Sumaki | |
| 6 6 | 
             
                module Attribute
         | 
| 7 7 | 
             
                  def self.included(base)
         | 
| 8 8 | 
             
                    base.extend ClassMethods
         | 
| 9 | 
            +
                    base.include InstanceMethods
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  module AccessorAdder # :nodoc:
         | 
| 13 | 
            +
                    def add(methods_module, field_name)
         | 
| 14 | 
            +
                      add_getter(methods_module, field_name)
         | 
| 15 | 
            +
                      add_setter(methods_module, field_name)
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    def add_getter(methods_module, field_name)
         | 
| 19 | 
            +
                      methods_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
         | 
| 20 | 
            +
                        def #{field_name}       # def title
         | 
| 21 | 
            +
                          get(:'#{field_name}') #   get(:'title')
         | 
| 22 | 
            +
                        end                     # end
         | 
| 23 | 
            +
                      RUBY
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    def add_setter(methods_module, field_name)
         | 
| 27 | 
            +
                      methods_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
         | 
| 28 | 
            +
                        def #{field_name}=(value)      # def title=(value)
         | 
| 29 | 
            +
                          set(:'#{field_name}', value) #   set(:'title', value)
         | 
| 30 | 
            +
                        end                            # end
         | 
| 31 | 
            +
                      RUBY
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
                    module_function :add, :add_getter, :add_setter
         | 
| 9 34 | 
             
                  end
         | 
| 10 35 |  | 
| 11 36 | 
             
                  module ClassMethods # :nodoc:
         | 
| @@ -20,12 +45,19 @@ module Sumaki | |
| 20 45 | 
             
                    #   anime = Anime.new({ title: 'The Vampire Dies in No Time', url: 'https://sugushinu-anime.jp/' })
         | 
| 21 46 | 
             
                    #   anime.title #=> 'The Vampire Dies in No Time'
         | 
| 22 47 | 
             
                    #   anime.url #=> 'https://sugushinu-anime.jp/'
         | 
| 48 | 
            +
                    #
         | 
| 49 | 
            +
                    # The Field value cam be set.
         | 
| 50 | 
            +
                    #
         | 
| 51 | 
            +
                    #   anime = Anime.new({})
         | 
| 52 | 
            +
                    #   anime.title = 'The Vampire Dies in No Time'
         | 
| 53 | 
            +
                    #   anime.title #=> 'The Vampire Dies in No Time'
         | 
| 23 54 | 
             
                    def field(name)
         | 
| 24 | 
            -
                       | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 55 | 
            +
                      field_names << name.to_sym
         | 
| 56 | 
            +
                      AccessorAdder.add(attribute_methods_module, name)
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    def field_names
         | 
| 60 | 
            +
                      @field_names ||= []
         | 
| 29 61 | 
             
                    end
         | 
| 30 62 |  | 
| 31 63 | 
             
                    private
         | 
| @@ -38,6 +70,12 @@ module Sumaki | |
| 38 70 | 
             
                      end
         | 
| 39 71 | 
             
                    end
         | 
| 40 72 | 
             
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  module InstanceMethods # :nodoc:
         | 
| 75 | 
            +
                    def fields
         | 
| 76 | 
            +
                      self.class.field_names.map.with_object({}) { |e, r| r[e] = public_send(e) }
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
                  end
         | 
| 41 79 | 
             
                end
         | 
| 42 80 | 
             
              end
         | 
| 43 81 | 
             
            end
         | 
    
        data/lib/sumaki/model/enum.rb
    CHANGED
    
    | @@ -15,7 +15,11 @@ module Sumaki | |
| 15 15 | 
             
                    def get(model, name)
         | 
| 16 16 | 
             
                      model.get(name)
         | 
| 17 17 | 
             
                    end
         | 
| 18 | 
            -
             | 
| 18 | 
            +
             | 
| 19 | 
            +
                    def set(model, name, value)
         | 
| 20 | 
            +
                      model.set(name, value)
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
                    module_function :get, :set
         | 
| 19 23 | 
             
                  end
         | 
| 20 24 |  | 
| 21 25 | 
             
                  module ClassMethods # :nodoc:
         | 
| @@ -36,6 +40,12 @@ module Sumaki | |
| 36 40 | 
             
                    #   character.type.name #=> :familier
         | 
| 37 41 | 
             
                    #   character.type.familier? #=> true
         | 
| 38 42 | 
             
                    #   character.type.vampire? #=> false
         | 
| 43 | 
            +
                    #
         | 
| 44 | 
            +
                    # Enum can also be set.
         | 
| 45 | 
            +
                    #
         | 
| 46 | 
            +
                    #   character = Character.new({})
         | 
| 47 | 
            +
                    #   character.type = 1
         | 
| 48 | 
            +
                    #   character.type.name #=> :vampire
         | 
| 39 49 | 
             
                    def enum(name, values)
         | 
| 40 50 | 
             
                      super(name, values, adapter: EnumAttrAccessor)
         | 
| 41 51 | 
             
                    end
         | 
| @@ -49,11 +59,6 @@ module Sumaki | |
| 49 59 | 
             
                        mod
         | 
| 50 60 | 
             
                      end
         | 
| 51 61 | 
             
                    end
         | 
| 52 | 
            -
             | 
| 53 | 
            -
                    # TODO: remove this
         | 
| 54 | 
            -
                    def classify(key)
         | 
| 55 | 
            -
                      key.gsub(/([a-z\d]+)_?/) { |_| Regexp.last_match(1).capitalize }
         | 
| 56 | 
            -
                    end
         | 
| 57 62 | 
             
                  end
         | 
| 58 63 | 
             
                end
         | 
| 59 64 | 
             
              end
         | 
    
        data/lib/sumaki/model.rb
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require_relative 'model/attribute'
         | 
| 4 | 
            -
            require_relative 'model/ | 
| 4 | 
            +
            require_relative 'model/associations'
         | 
| 5 5 | 
             
            require_relative 'model/enum'
         | 
| 6 6 |  | 
| 7 7 | 
             
            module Sumaki
         | 
| @@ -164,7 +164,7 @@ module Sumaki | |
| 164 164 | 
             
                  base.include InstanceMethods
         | 
| 165 165 |  | 
| 166 166 | 
             
                  base.include Attribute
         | 
| 167 | 
            -
                  base.include  | 
| 167 | 
            +
                  base.include Associations
         | 
| 168 168 | 
             
                  base.include Enum
         | 
| 169 169 | 
             
                end
         | 
| 170 170 |  | 
| @@ -177,6 +177,33 @@ module Sumaki | |
| 177 177 | 
             
                  end
         | 
| 178 178 | 
             
                end
         | 
| 179 179 |  | 
| 180 | 
            +
                class ObjectAccessor # :nodoc:
         | 
| 181 | 
            +
                  def initialize(object, adapter)
         | 
| 182 | 
            +
                    @object = object
         | 
| 183 | 
            +
                    @adapter = adapter
         | 
| 184 | 
            +
                  end
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                  def get(name)
         | 
| 187 | 
            +
                    @adapter.get(@object, name)
         | 
| 188 | 
            +
                  end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                  def set(name, value)
         | 
| 191 | 
            +
                    @adapter.set(@object, name, value)
         | 
| 192 | 
            +
                  end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                  def build_singular(name)
         | 
| 195 | 
            +
                    @adapter.build_singular(@object, name)
         | 
| 196 | 
            +
                  end
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                  def build_repeated_element(name)
         | 
| 199 | 
            +
                    @adapter.build_repeated_element(@object, name)
         | 
| 200 | 
            +
                  end
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                  def apply_repeated(name, models)
         | 
| 203 | 
            +
                    @adapter.apply_repeated(@object, name, models.map(&:object))
         | 
| 204 | 
            +
                  end
         | 
| 205 | 
            +
                end
         | 
| 206 | 
            +
             | 
| 180 207 | 
             
                module InstanceMethods # :nodoc:
         | 
| 181 208 | 
             
                  attr_reader :object, :parent
         | 
| 182 209 |  | 
| @@ -184,6 +211,43 @@ module Sumaki | |
| 184 211 | 
             
                    @object = object
         | 
| 185 212 | 
             
                    @parent = parent
         | 
| 186 213 | 
             
                  end
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                  def object_accessor
         | 
| 216 | 
            +
                    @object_accessor ||= ObjectAccessor.new(object, self.class.adapter)
         | 
| 217 | 
            +
                  end
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                  def get(name)
         | 
| 220 | 
            +
                    object_accessor.get(name)
         | 
| 221 | 
            +
                  end
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                  def set(name, value)
         | 
| 224 | 
            +
                    object_accessor.set(name, value)
         | 
| 225 | 
            +
                  end
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                  def assign(attrs)
         | 
| 228 | 
            +
                    attrs.each do |attr, value|
         | 
| 229 | 
            +
                      public_send(:"#{attr}=", value)
         | 
| 230 | 
            +
                    end
         | 
| 231 | 
            +
                  end
         | 
| 232 | 
            +
             | 
| 233 | 
            +
                  def inspect
         | 
| 234 | 
            +
                    inspection = fields
         | 
| 235 | 
            +
                                 .map { |name, value| "#{name}: #{value.inspect}" }
         | 
| 236 | 
            +
                                 .join(', ')
         | 
| 237 | 
            +
                    "#<#{self.class.name} #{inspection}>"
         | 
| 238 | 
            +
                  end
         | 
| 239 | 
            +
             | 
| 240 | 
            +
                  def pretty_print(pp)
         | 
| 241 | 
            +
                    pp.object_address_group(self) do
         | 
| 242 | 
            +
                      pp.seplist(fields) do |field, value|
         | 
| 243 | 
            +
                        pp.breakable
         | 
| 244 | 
            +
                        pp.group(1) do
         | 
| 245 | 
            +
                          pp.text "#{field}: "
         | 
| 246 | 
            +
                          pp.pp value
         | 
| 247 | 
            +
                        end
         | 
| 248 | 
            +
                      end
         | 
| 249 | 
            +
                    end
         | 
| 250 | 
            +
                  end
         | 
| 187 251 | 
             
                end
         | 
| 188 252 | 
             
              end
         | 
| 189 253 | 
             
            end
         | 
    
        data/lib/sumaki/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: sumaki
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.3.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Loose Coupling
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024- | 
| 11 | 
            +
            date: 2024-05-17 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: minenum
         | 
| @@ -48,7 +48,10 @@ files: | |
| 48 48 | 
             
            - lib/sumaki/adapter/hash.rb
         | 
| 49 49 | 
             
            - lib/sumaki/config.rb
         | 
| 50 50 | 
             
            - lib/sumaki/model.rb
         | 
| 51 | 
            -
            - lib/sumaki/model/ | 
| 51 | 
            +
            - lib/sumaki/model/associations.rb
         | 
| 52 | 
            +
            - lib/sumaki/model/associations/association.rb
         | 
| 53 | 
            +
            - lib/sumaki/model/associations/collection.rb
         | 
| 54 | 
            +
            - lib/sumaki/model/associations/reflection.rb
         | 
| 52 55 | 
             
            - lib/sumaki/model/attribute.rb
         | 
| 53 56 | 
             
            - lib/sumaki/model/enum.rb
         | 
| 54 57 | 
             
            - lib/sumaki/version.rb
         | 
| @@ -1,116 +0,0 @@ | |
| 1 | 
            -
            # frozen_string_literal: true
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            module Sumaki
         | 
| 4 | 
            -
              module Model
         | 
| 5 | 
            -
                # = Sumaki::Model::Association
         | 
| 6 | 
            -
                module Association
         | 
| 7 | 
            -
                  def self.included(base)
         | 
| 8 | 
            -
                    base.extend ClassMethods
         | 
| 9 | 
            -
                    base.include InstanceMethods
         | 
| 10 | 
            -
             | 
| 11 | 
            -
                    base.instance_variable_set(:@classes, {})
         | 
| 12 | 
            -
                  end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
                  module ClassMethods # :nodoc:
         | 
| 15 | 
            -
                    # Access to the sub object.
         | 
| 16 | 
            -
                    #
         | 
| 17 | 
            -
                    #   class Book
         | 
| 18 | 
            -
                    #     include Sumaki::Model
         | 
| 19 | 
            -
                    #     singular :company
         | 
| 20 | 
            -
                    #
         | 
| 21 | 
            -
                    #     class Company
         | 
| 22 | 
            -
                    #       include Sumaki::Model
         | 
| 23 | 
            -
                    #     end
         | 
| 24 | 
            -
                    #   end
         | 
| 25 | 
            -
                    #
         | 
| 26 | 
            -
                    #   data = {
         | 
| 27 | 
            -
                    #     title: 'The Ronaldo Chronicles',
         | 
| 28 | 
            -
                    #     company: {
         | 
| 29 | 
            -
                    #       name: 'Autumn Books',
         | 
| 30 | 
            -
                    #     }
         | 
| 31 | 
            -
                    #   }
         | 
| 32 | 
            -
                    #   book = Book.new(data)
         | 
| 33 | 
            -
                    #   book.company.class #=> Book::Company
         | 
| 34 | 
            -
                    #
         | 
| 35 | 
            -
                    # == Options
         | 
| 36 | 
            -
                    #
         | 
| 37 | 
            -
                    # [:class_name]
         | 
| 38 | 
            -
                    #   Specify the name of the class to wrap. Use this if the name of the class
         | 
| 39 | 
            -
                    #   to wrap is not inferred from the nested field names.
         | 
| 40 | 
            -
                    def singular(name, class_name: nil)
         | 
| 41 | 
            -
                      association_methods_module.define_method(name) do
         | 
| 42 | 
            -
                        klass = self.class.class_for(name, class_name)
         | 
| 43 | 
            -
                        klass.new(get(name), parent: self)
         | 
| 44 | 
            -
                      end
         | 
| 45 | 
            -
                    end
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                    # Access to the repeated sub objects
         | 
| 48 | 
            -
                    #
         | 
| 49 | 
            -
                    #   class Company
         | 
| 50 | 
            -
                    #     include Sumaki::Model
         | 
| 51 | 
            -
                    #     repeated :member
         | 
| 52 | 
            -
                    #
         | 
| 53 | 
            -
                    #     class Member
         | 
| 54 | 
            -
                    #       include Sumaki::Model
         | 
| 55 | 
            -
                    #     end
         | 
| 56 | 
            -
                    #   end
         | 
| 57 | 
            -
                    #
         | 
| 58 | 
            -
                    #   data = {
         | 
| 59 | 
            -
                    #     name: 'The Ronaldo Vampire Hunter Agency',
         | 
| 60 | 
            -
                    #     member: [
         | 
| 61 | 
            -
                    #       { name: 'Ronaldo' },
         | 
| 62 | 
            -
                    #       { name: 'Draluc' },
         | 
| 63 | 
            -
                    #       { name: 'John' }
         | 
| 64 | 
            -
                    #     ]
         | 
| 65 | 
            -
                    #   }
         | 
| 66 | 
            -
                    #   company = Company.new(data)
         | 
| 67 | 
            -
                    #   company.member[2].class #=> Company::Member
         | 
| 68 | 
            -
                    #
         | 
| 69 | 
            -
                    # == Options
         | 
| 70 | 
            -
                    #
         | 
| 71 | 
            -
                    # [:class_name]
         | 
| 72 | 
            -
                    #   Specify the name of the class to wrap. Use this if the name of the class
         | 
| 73 | 
            -
                    #   to wrap is not inferred from the nested field names.
         | 
| 74 | 
            -
                    def repeated(name, class_name: nil)
         | 
| 75 | 
            -
                      association_methods_module.define_method(name) do
         | 
| 76 | 
            -
                        klass = self.class.class_for(name, class_name)
         | 
| 77 | 
            -
                        get(name).map { |object| klass.new(object, parent: self) }
         | 
| 78 | 
            -
                      end
         | 
| 79 | 
            -
                    end
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                    def class_for(name, class_name = nil)
         | 
| 82 | 
            -
                      return @classes[name] if @classes.key?(name)
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                      basename = class_name || classify(name.to_s)
         | 
| 85 | 
            -
                      klass = if const_defined?(basename)
         | 
| 86 | 
            -
                                const_get(basename)
         | 
| 87 | 
            -
                              else
         | 
| 88 | 
            -
                                const_set(basename, Class.new { include Model })
         | 
| 89 | 
            -
                              end
         | 
| 90 | 
            -
                      klass.parent ||= self
         | 
| 91 | 
            -
                      @classes[name] = klass
         | 
| 92 | 
            -
                    end
         | 
| 93 | 
            -
             | 
| 94 | 
            -
                    private
         | 
| 95 | 
            -
             | 
| 96 | 
            -
                    def association_methods_module
         | 
| 97 | 
            -
                      @association_methods_module ||= begin
         | 
| 98 | 
            -
                        mod = Module.new
         | 
| 99 | 
            -
                        include mod
         | 
| 100 | 
            -
                        mod
         | 
| 101 | 
            -
                      end
         | 
| 102 | 
            -
                    end
         | 
| 103 | 
            -
             | 
| 104 | 
            -
                    def classify(key)
         | 
| 105 | 
            -
                      key.gsub(/([a-z\d]+)_?/) { |_| Regexp.last_match(1).capitalize }
         | 
| 106 | 
            -
                    end
         | 
| 107 | 
            -
                  end
         | 
| 108 | 
            -
             | 
| 109 | 
            -
                  module InstanceMethods # :nodoc:
         | 
| 110 | 
            -
                    def get(name)
         | 
| 111 | 
            -
                      self.class.adapter.get(object, name)
         | 
| 112 | 
            -
                    end
         | 
| 113 | 
            -
                  end
         | 
| 114 | 
            -
                end
         | 
| 115 | 
            -
              end
         | 
| 116 | 
            -
            end
         |