dinamo 0.1.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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +52 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/dinamo.gemspec +38 -0
- data/lib/dinamo/adapter.rb +89 -0
- data/lib/dinamo/exceptions.rb +10 -0
- data/lib/dinamo/model/attributes.rb +166 -0
- data/lib/dinamo/model/callback.rb +39 -0
- data/lib/dinamo/model/caster.rb +75 -0
- data/lib/dinamo/model/dirty.rb +64 -0
- data/lib/dinamo/model/errors.rb +17 -0
- data/lib/dinamo/model/persistence.rb +144 -0
- data/lib/dinamo/model/validation.rb +51 -0
- data/lib/dinamo/model/validations/presence.rb +18 -0
- data/lib/dinamo/model/validator.rb +78 -0
- data/lib/dinamo/model.rb +57 -0
- data/lib/dinamo/version.rb +3 -0
- data/lib/dinamo.rb +5 -0
- metadata +168 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: f5fe3967b08bb83ba8b65bb065bf886b133a559b
         | 
| 4 | 
            +
              data.tar.gz: ff65fae8abc087bf0b622562468292ebff19d444
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: 71c0a99f50c16c791eed652d77ff743d22cd8c37bf6bc5f3df17e260a4541cd525d39d1b802618d26692c7ef1fcbb6ce5ce2e221121a7297d56324eff8515729
         | 
| 7 | 
            +
              data.tar.gz: 181d5051ecead6d8868853ee331c85b43f2a49fe2338d4662a38e8feb15c9f2c5ff0c298866b8b0e957fafd5f44cd13b97abebb4994f12fcdf8803e1de3c0301
         | 
    
        data/.gitignore
    ADDED
    
    
    
        data/.rspec
    ADDED
    
    
    
        data/.travis.yml
    ADDED
    
    
    
        data/CODE_OF_CONDUCT.md
    ADDED
    
    | @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            # Contributor Code of Conduct
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
         | 
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE.txt
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            The MIT License (MIT)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Copyright (c) 2015 namusyaka
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            +
            of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            +
            in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            +
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            +
            copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            +
            furnished to do so, subject to the following conditions:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            The above copyright notice and this permission notice shall be included in
         | 
| 13 | 
            +
            all copies or substantial portions of the Software.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            +
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            +
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            +
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         | 
| 20 | 
            +
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
         | 
| 21 | 
            +
            THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,52 @@ | |
| 1 | 
            +
            # Dinamo
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Dinamo is an simple ORM for Amazon DynamoDB for Ruby applications.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## Installation
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Add this line to your application's Gemfile:
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            ```ruby
         | 
| 10 | 
            +
            gem 'dinamo'
         | 
| 11 | 
            +
            ```
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            And then execute:
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                $ bundle
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            Or install it yourself as:
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                $ gem install dinamo
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            ## Usage
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            ### Sample
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            Your class must inherit `Dinamo::Model` in every Dinamo model.
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ```ruby
         | 
| 28 | 
            +
            class User < Dinamo::Model
         | 
| 29 | 
            +
              hash_key :id, type: :number
         | 
| 30 | 
            +
              range_key :kind, type: :string
         | 
| 31 | 
            +
              field :name, type: :string
         | 
| 32 | 
            +
            end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            user = User.new
         | 
| 35 | 
            +
            user.id   = 1
         | 
| 36 | 
            +
            user.kind = "developer"
         | 
| 37 | 
            +
            user.name = "namusyaka"
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            user.valid? #=> true
         | 
| 40 | 
            +
            user.errors #=> {}
         | 
| 41 | 
            +
            user.save #=> true
         | 
| 42 | 
            +
            ```
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            ## Contributing
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            Bug reports and pull requests are welcome on GitHub at https://github.com/namusyaka/dinamo. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
         | 
| 47 | 
            +
             | 
| 48 | 
            +
             | 
| 49 | 
            +
            ## License
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
         | 
| 52 | 
            +
             | 
    
        data/Rakefile
    ADDED
    
    
    
        data/bin/console
    ADDED
    
    | @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "bundler/setup"
         | 
| 4 | 
            +
            require "dinamo"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            # You can add fixtures and/or initialization code here to make experimenting
         | 
| 7 | 
            +
            # with your gem easier. You can also use a different console, if you like.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            # (If you use this, don't forget to add pry to your Gemfile!)
         | 
| 10 | 
            +
            # require "pry"
         | 
| 11 | 
            +
            # Pry.start
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            require "irb"
         | 
| 14 | 
            +
            IRB.start
         | 
    
        data/bin/setup
    ADDED
    
    
    
        data/dinamo.gemspec
    ADDED
    
    | @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            # coding: utf-8
         | 
| 2 | 
            +
            lib = File.expand_path('../lib', __FILE__)
         | 
| 3 | 
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 4 | 
            +
            require 'dinamo/version'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Gem::Specification.new do |spec|
         | 
| 7 | 
            +
              spec.name          = "dinamo"
         | 
| 8 | 
            +
              spec.version       = Dinamo::VERSION
         | 
| 9 | 
            +
              spec.authors       = ["namusyaka"]
         | 
| 10 | 
            +
              spec.email         = ["namusyaka@gmail.com"]
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              spec.summary       = "DynamoDB ORM for Ruby"
         | 
| 13 | 
            +
              spec.description   = "DynamoDB ORM for Ruby"
         | 
| 14 | 
            +
              spec.homepage      = "https://github.com/namusyaka/dinamo"
         | 
| 15 | 
            +
              spec.license       = "MIT"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
         | 
| 18 | 
            +
              # delete this section to allow pushing this gem to any host.
         | 
| 19 | 
            +
              if spec.respond_to?(:metadata)
         | 
| 20 | 
            +
                spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
         | 
| 21 | 
            +
              else
         | 
| 22 | 
            +
                raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              spec.files         = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
         | 
| 26 | 
            +
              spec.bindir        = "exe"
         | 
| 27 | 
            +
              spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
         | 
| 28 | 
            +
              spec.require_paths = ["lib"]
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              spec.add_dependency "activesupport", "~> 4.2.4"
         | 
| 31 | 
            +
              spec.add_dependency "aws-sdk",       "~> 2.1.26"
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              spec.add_development_dependency "bundler", "~> 1.10"
         | 
| 34 | 
            +
              spec.add_development_dependency "rake", "~> 10.0"
         | 
| 35 | 
            +
              spec.add_development_dependency "test-unit"
         | 
| 36 | 
            +
              spec.add_development_dependency "test-unit-rr"
         | 
| 37 | 
            +
              spec.add_development_dependency "test-unit-power_assert"
         | 
| 38 | 
            +
            end
         | 
| @@ -0,0 +1,89 @@ | |
| 1 | 
            +
            require 'dinamo/exceptions'
         | 
| 2 | 
            +
            require 'aws-sdk'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Dinamo
         | 
| 5 | 
            +
              class Adapter
         | 
| 6 | 
            +
                module Glue
         | 
| 7 | 
            +
                  def adapter_options
         | 
| 8 | 
            +
                    @adapter_options ||= {}
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def adapter
         | 
| 12 | 
            +
                    @adapter ||= {}
         | 
| 13 | 
            +
                    @adapter[table_name] ||= Dinamo::Adapter.new(table_name: table_name, **adapter_options)
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def initialize(table_name: nil, **options)
         | 
| 18 | 
            +
                  @options    = options
         | 
| 19 | 
            +
                  @database   = Aws::DynamoDB::Client.new
         | 
| 20 | 
            +
                  @table_name = table_name
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def get(**keys)
         | 
| 24 | 
            +
                  @database.get_item(table_name: @table_name, key: keys)
         | 
| 25 | 
            +
                rescue
         | 
| 26 | 
            +
                  handle_error! $!
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def insert(**keys)
         | 
| 30 | 
            +
                  @database.put_item(
         | 
| 31 | 
            +
                    table_name: @table_name,
         | 
| 32 | 
            +
                    item: keys,
         | 
| 33 | 
            +
                    return_values: "ALL_OLD"
         | 
| 34 | 
            +
                  )
         | 
| 35 | 
            +
                rescue
         | 
| 36 | 
            +
                  handle_error! $!
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def update(key, attributes)
         | 
| 40 | 
            +
                  find_or_abort!(**key)
         | 
| 41 | 
            +
                  @database.update_item(
         | 
| 42 | 
            +
                    table_name: @table_name,
         | 
| 43 | 
            +
                    key: key,
         | 
| 44 | 
            +
                    attribute_updates: serialize_attributes(attributes),
         | 
| 45 | 
            +
                    return_values: "ALL_NEW"
         | 
| 46 | 
            +
                  )
         | 
| 47 | 
            +
                rescue
         | 
| 48 | 
            +
                  handle_error! $!
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def delete(key)
         | 
| 52 | 
            +
                  find_or_abort!(**key)
         | 
| 53 | 
            +
                  @database.update_item(
         | 
| 54 | 
            +
                    table_name: @table_name,
         | 
| 55 | 
            +
                    key: key
         | 
| 56 | 
            +
                  )
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def serialize_attributes(attributes)
         | 
| 60 | 
            +
                  attributes.each_with_object({}) do |(key, value), new_attributes|
         | 
| 61 | 
            +
                    new_attributes[key] = { value: value, action: "PUT" }
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                def exist?(**keys)
         | 
| 66 | 
            +
                  !!get(**keys).item rescue false
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                private
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                def find_or_abort!(**key)
         | 
| 72 | 
            +
                  item = get(**key)
         | 
| 73 | 
            +
                  fail Exceptions::RecordNotFoundError,
         | 
| 74 | 
            +
                    "Corresponding record (%p) can not be found" % key unless item && item.item
         | 
| 75 | 
            +
                  item
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                def handle_error!(evar)
         | 
| 79 | 
            +
                  case evar
         | 
| 80 | 
            +
                  when Aws::DynamoDB::Errors::ValidationException
         | 
| 81 | 
            +
                    fail Dinamo::Exceptions::ValidationError, evar.message
         | 
| 82 | 
            +
                  when Aws::DynamoDB::Errors::ResourceNotFoundException
         | 
| 83 | 
            +
                    fail Dinamo::Exceptions::ResourceNotFoundError, evar.message
         | 
| 84 | 
            +
                  else
         | 
| 85 | 
            +
                    fail evar, evar.message
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
              end
         | 
| 89 | 
            +
            end
         | 
| @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            module Dinamo
         | 
| 2 | 
            +
              module Exceptions
         | 
| 3 | 
            +
                CastError             = Class.new(ArgumentError)
         | 
| 4 | 
            +
                PrimaryKeyError       = Class.new(ArgumentError)
         | 
| 5 | 
            +
                ValidationError       = Class.new(ArgumentError)
         | 
| 6 | 
            +
                RecordNotFoundError   = Class.new(ArgumentError)
         | 
| 7 | 
            +
                UnsupportedTypeError  = Class.new(ArgumentError)
         | 
| 8 | 
            +
                ResourceNotFoundError = Class.new(StandardError)
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
            end
         | 
| @@ -0,0 +1,166 @@ | |
| 1 | 
            +
            require 'active_support/core_ext/hash/indifferent_access'
         | 
| 2 | 
            +
            require 'dinamo/model/caster'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Dinamo
         | 
| 5 | 
            +
              class Model
         | 
| 6 | 
            +
                module Attributes
         | 
| 7 | 
            +
                  extend ActiveSupport::Concern
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  included do
         | 
| 10 | 
            +
                    before(:save) { self.attributes = self.class.caster.cast(attributes) }
         | 
| 11 | 
            +
                    after(:save) { changes_applied }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    before :initialize do |**attributes|
         | 
| 14 | 
            +
                      @attributes = {}.with_indifferent_access
         | 
| 15 | 
            +
                      silent_assign(**attributes)
         | 
| 16 | 
            +
                      self.class.define_attribute_methods(*attributes.keys)
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    before :attribute_update do |attribute, value|
         | 
| 20 | 
            +
                      pkey = primary_keys[attribute]
         | 
| 21 | 
            +
                      if persisted? && pkey && pkey != value
         | 
| 22 | 
            +
                        raise Exceptions::PrimaryKeyError, "%p cannot be modified" % attribute
         | 
| 23 | 
            +
                      end
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  module ClassMethods
         | 
| 28 | 
            +
                    def attribute_methods
         | 
| 29 | 
            +
                      @attribute_names ||= []
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    def attribute_method?(attr)
         | 
| 33 | 
            +
                      attribute_methods.include?(attr)
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    def attribute_method_already_implemented?(attr)
         | 
| 37 | 
            +
                      attribute_method?(attr) && respond_to?(attr) && respond_to?("#{attr}=")
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    def define_attribute_methods(*attrs)
         | 
| 41 | 
            +
                      attrs.each { |attr| define_attribute_method(attr) }
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    def define_attribute_method(attr)
         | 
| 45 | 
            +
                      return if attribute_method_already_implemented?(attr)
         | 
| 46 | 
            +
                      define_method(attr) { @attributes[attr] }
         | 
| 47 | 
            +
                      define_method("#{attr}=") do |val|
         | 
| 48 | 
            +
                        with_callback :attribute_update, attr, val do
         | 
| 49 | 
            +
                          @attributes[attr] = val
         | 
| 50 | 
            +
                        end
         | 
| 51 | 
            +
                      end
         | 
| 52 | 
            +
                      attribute_methods << attr
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    def primary_keys
         | 
| 56 | 
            +
                      @primary_keys ||= {}
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    def primary_key(kind, key, type: nil, **options)
         | 
| 60 | 
            +
                      name = :"@#{kind}_key"
         | 
| 61 | 
            +
                      var = instance_variable_get(name)
         | 
| 62 | 
            +
                      primary_keys[kind] = key
         | 
| 63 | 
            +
                      var ? var : instance_variable_set(name, key.to_s)
         | 
| 64 | 
            +
                      define_attribute_method key
         | 
| 65 | 
            +
                      supported_fields << Key.new(key.to_s, type: type, required: true, primary: true)
         | 
| 66 | 
            +
                    end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                    def supported_fields
         | 
| 69 | 
            +
                      @supported_fields ||= []
         | 
| 70 | 
            +
                    end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                    def required_fields
         | 
| 73 | 
            +
                      supported_fields.select(&:required?)
         | 
| 74 | 
            +
                    end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                    def field(key, type: nil, **options)
         | 
| 77 | 
            +
                      caster.associate(key, type) if type
         | 
| 78 | 
            +
                      supported_fields << Key.new(key.to_s, type: type, **options)
         | 
| 79 | 
            +
                      define_attribute_method(key) unless respond_to?(:"#{key}=")
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    def hash_key(key, **options)
         | 
| 83 | 
            +
                      primary_key :hash, key, **options
         | 
| 84 | 
            +
                    end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                    def range_key(key, **options)
         | 
| 87 | 
            +
                      primary_key :range, key, **options
         | 
| 88 | 
            +
                    end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                    def caster
         | 
| 91 | 
            +
                      @caster ||= Caster.new
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                    def cast(type, &block)
         | 
| 95 | 
            +
                      caster.register(type, &block)
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  attr_reader :attributes
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                  def [](key)
         | 
| 102 | 
            +
                    attributes[key]
         | 
| 103 | 
            +
                  end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  def []=(key, value)
         | 
| 106 | 
            +
                    self.attributes = { key => value }
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  def variable_attributes
         | 
| 110 | 
            +
                    keys = primary_keys.keys
         | 
| 111 | 
            +
                    attributes.select { |key, _| !keys.include?(key.to_sym) }
         | 
| 112 | 
            +
                  end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                  def attributes=(attrs)
         | 
| 115 | 
            +
                    attrs.each_pair do |key, val|
         | 
| 116 | 
            +
                      name = "#{key}="
         | 
| 117 | 
            +
                      self.class.define_attribute_method(key) unless respond_to?(name)
         | 
| 118 | 
            +
                      send(name, val)
         | 
| 119 | 
            +
                    end
         | 
| 120 | 
            +
                  end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                  def hash_key
         | 
| 123 | 
            +
                    attributes[self.class.primary_keys[:hash]]
         | 
| 124 | 
            +
                  end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                  def range_key
         | 
| 127 | 
            +
                    attributes[self.class.primary_keys[:range]]
         | 
| 128 | 
            +
                  end
         | 
| 129 | 
            +
                  
         | 
| 130 | 
            +
                  def silent_assign(new_attributes)
         | 
| 131 | 
            +
                    @attributes.merge!(new_attributes)
         | 
| 132 | 
            +
                  end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                  def ==(other)
         | 
| 135 | 
            +
                    return false unless other.kind_of?(Dinamo::Model)
         | 
| 136 | 
            +
                    hash_key == other.hash_key && range_key == other.range_key
         | 
| 137 | 
            +
                  end
         | 
| 138 | 
            +
                  alias equal? ==
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                  def primary_keys
         | 
| 141 | 
            +
                    @primary_keys ||= self.class.primary_keys.inject({}) do |keys, (_, key)|
         | 
| 142 | 
            +
                      keys.merge(key => attributes[key])
         | 
| 143 | 
            +
                    end
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  class Key
         | 
| 147 | 
            +
                    attr_reader :name, :type
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                    def initialize(name, type: nil, required: false, primary: false, **options)
         | 
| 150 | 
            +
                      @name = name
         | 
| 151 | 
            +
                      @type = type
         | 
| 152 | 
            +
                      @required = required
         | 
| 153 | 
            +
                      @primary = primary
         | 
| 154 | 
            +
                    end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                    def required?
         | 
| 157 | 
            +
                      @required
         | 
| 158 | 
            +
                    end
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                    def primary?
         | 
| 161 | 
            +
                      @primary
         | 
| 162 | 
            +
                    end
         | 
| 163 | 
            +
                  end
         | 
| 164 | 
            +
                end
         | 
| 165 | 
            +
              end
         | 
| 166 | 
            +
            end
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            module Dinamo
         | 
| 2 | 
            +
              class Model
         | 
| 3 | 
            +
                module Callback
         | 
| 4 | 
            +
                  extend ActiveSupport::Concern
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  def with_callback(kind, *args, &block)
         | 
| 7 | 
            +
                    invoke_callbacks(:before, kind, *args)
         | 
| 8 | 
            +
                    block.call
         | 
| 9 | 
            +
                  ensure
         | 
| 10 | 
            +
                    invoke_callbacks(:after, kind, *args)
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def invoke_callbacks(type, kind, *args)
         | 
| 14 | 
            +
                    ref = respond_to?(:callbacks) ? callbacks : self.class.callbacks
         | 
| 15 | 
            +
                    current = ref[type][kind]
         | 
| 16 | 
            +
                    return unless current
         | 
| 17 | 
            +
                    current.each { |callback| instance_exec(*args, &callback) }
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  module ClassMethods
         | 
| 21 | 
            +
                    def on(type, kind, &callback)
         | 
| 22 | 
            +
                      (callbacks[type][kind] ||= []) << callback
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    def before(kind, &callback)
         | 
| 26 | 
            +
                      on(:before, kind, &callback)
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    def after(kind, &callback)
         | 
| 30 | 
            +
                      on(:after, kind, &callback)
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    def callbacks
         | 
| 34 | 
            +
                      @callbacks ||= { before: {}, after: {} }
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| @@ -0,0 +1,75 @@ | |
| 1 | 
            +
            require 'delegate'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Dinamo
         | 
| 4 | 
            +
              class Model
         | 
| 5 | 
            +
                class Caster
         | 
| 6 | 
            +
                  attr_reader :types
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize
         | 
| 9 | 
            +
                    @types = {}
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def register(type, &block)
         | 
| 13 | 
            +
                    @types[type] = Any.new(type, &block)
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def cast(attributes)
         | 
| 17 | 
            +
                    attributes.each_with_object({}) do |(key, value), casted|
         | 
| 18 | 
            +
                      casted[key] =
         | 
| 19 | 
            +
                        if found = @types.find { |_, any| any.supported_key?(key) }
         | 
| 20 | 
            +
                          found.pop.cast(value)
         | 
| 21 | 
            +
                        else
         | 
| 22 | 
            +
                          value
         | 
| 23 | 
            +
                        end
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def supported_type?(type)
         | 
| 28 | 
            +
                    supported_types.include?(type)
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def supported_types
         | 
| 32 | 
            +
                    @types.keys
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def associate(key, type)
         | 
| 36 | 
            +
                    fail Exceptions::UnsupportedTypeError,
         | 
| 37 | 
            +
                      "%p type is not supported" % type unless @types.keys.include?(type)
         | 
| 38 | 
            +
                    current = @types[type]
         | 
| 39 | 
            +
                    current.support(key)
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  class Any
         | 
| 43 | 
            +
                    def initialize(type, &block)
         | 
| 44 | 
            +
                      @type = type
         | 
| 45 | 
            +
                      @block = block
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    def cast(value)
         | 
| 49 | 
            +
                      case @block.arity
         | 
| 50 | 
            +
                      when 0 then @block.call
         | 
| 51 | 
            +
                      when 1 then @block.call(value)
         | 
| 52 | 
            +
                      when 2 then @block.call(@type, value)
         | 
| 53 | 
            +
                      end
         | 
| 54 | 
            +
                    rescue
         | 
| 55 | 
            +
                      fail Exceptions::CastError,
         | 
| 56 | 
            +
                        "%p can not be casted into %p" % [value, @type]
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    def support(key)
         | 
| 60 | 
            +
                      keys << key.to_sym unless supported_key?(key)
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    def supported_key?(key)
         | 
| 64 | 
            +
                      keys.include?(key.to_sym)
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    private
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    def keys
         | 
| 70 | 
            +
                      @keys ||= []
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
            end
         | 
| @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            module Dinamo
         | 
| 2 | 
            +
              class Model
         | 
| 3 | 
            +
                module Dirty
         | 
| 4 | 
            +
                  extend ActiveSupport::Concern
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  included do
         | 
| 7 | 
            +
                    before :attribute_update do |attr, val|
         | 
| 8 | 
            +
                      previous_attribute = @attributes[attr]
         | 
| 9 | 
            +
                      unless previous_attribute == val
         | 
| 10 | 
            +
                        changed_attributes[attr]  = val
         | 
| 11 | 
            +
                        previous_attributes[attr] = previous_attribute
         | 
| 12 | 
            +
                      end
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
                    after(:save) { changes_applied }
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def changed
         | 
| 18 | 
            +
                    changed_attributes.keys
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def changed?
         | 
| 22 | 
            +
                    !changed_attributes.empty?
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def changes
         | 
| 26 | 
            +
                    map = changed.map { |attr| [attr, attribute_change(attr)] }
         | 
| 27 | 
            +
                    ActiveSupport::HashWithIndifferentAccess[map]
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def changed_attributes
         | 
| 31 | 
            +
                    @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def attribute_changed?(attr)
         | 
| 35 | 
            +
                    changed.include?(attr.to_s)
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def changes_applied
         | 
| 39 | 
            +
                    @previously_changed = changes
         | 
| 40 | 
            +
                    @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def clear_changes_information
         | 
| 44 | 
            +
                    @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
         | 
| 45 | 
            +
                    @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def clear_previous_attributes
         | 
| 49 | 
            +
                    @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  private
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  def attribute_change(attr)
         | 
| 55 | 
            +
                    return unless attribute_changed?(attr)
         | 
| 56 | 
            +
                    [previous_attributes[attr], changed_attributes[attr]]
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  def previous_attributes
         | 
| 60 | 
            +
                    @previous_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
            end
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            module Dinamo
         | 
| 2 | 
            +
              class Model
         | 
| 3 | 
            +
                class Errors < ::Hash
         | 
| 4 | 
            +
                  def add(attribute, message)
         | 
| 5 | 
            +
                    fetch(attribute) { self[attribute] = [] } << message
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def count
         | 
| 9 | 
            +
                    values.inject(0) { |all, value| all + value.length }
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def empty?
         | 
| 13 | 
            +
                    count.zero?
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -0,0 +1,144 @@ | |
| 1 | 
            +
            module Dinamo
         | 
| 2 | 
            +
              class Model
         | 
| 3 | 
            +
                module Persistence
         | 
| 4 | 
            +
                  extend ActiveSupport::Concern
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  included do
         | 
| 7 | 
            +
                    after(:save) { @new_record = !errors.empty? }
         | 
| 8 | 
            +
                    after(:initialize) { |**opts| self.new_record = true }
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  module ClassMethods
         | 
| 12 | 
            +
                    def get(**keys)
         | 
| 13 | 
            +
                      get!(**keys) rescue nil
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    def get!(**keys)
         | 
| 17 | 
            +
                      item = adapter.get(**keys).item 
         | 
| 18 | 
            +
                      fail Exceptions::RecordNotFoundError,
         | 
| 19 | 
            +
                        "Corresponding record (%p) can not be found" % keys unless item
         | 
| 20 | 
            +
                      new(**symbolize(item))
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    def create(attributes = nil, &block)
         | 
| 24 | 
            +
                      object = new(**attributes, &block)
         | 
| 25 | 
            +
                      object.new_record = true
         | 
| 26 | 
            +
                      object.with_callback :create do
         | 
| 27 | 
            +
                        object.save
         | 
| 28 | 
            +
                        object
         | 
| 29 | 
            +
                      end
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    def create!(attributes = nil, &block)
         | 
| 33 | 
            +
                      object = new(**attributes, &block)
         | 
| 34 | 
            +
                      object.new_record = true
         | 
| 35 | 
            +
                      object.with_callback :create do
         | 
| 36 | 
            +
                        object.save!
         | 
| 37 | 
            +
                        object
         | 
| 38 | 
            +
                      end
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    def symbolize(attrs)
         | 
| 42 | 
            +
                      attrs.each_with_object({}) do |(key, val), new_attrs|
         | 
| 43 | 
            +
                        new_attrs[key.to_sym] = val
         | 
| 44 | 
            +
                      end
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def new_record=(bool)
         | 
| 49 | 
            +
                    @new_record = bool
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def new_record?
         | 
| 53 | 
            +
                    @new_record
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  def destroy
         | 
| 57 | 
            +
                    returned_value =
         | 
| 58 | 
            +
                      if persisted?
         | 
| 59 | 
            +
                        self.class.adapter.delete(primary_keys)
         | 
| 60 | 
            +
                        true
         | 
| 61 | 
            +
                      else
         | 
| 62 | 
            +
                        false
         | 
| 63 | 
            +
                      end
         | 
| 64 | 
            +
                    (@destroyed = true) && freeze
         | 
| 65 | 
            +
                    returned_value
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def destroyed?
         | 
| 69 | 
            +
                    @destroyed
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  def persisted?
         | 
| 73 | 
            +
                    !(new_record? || destroyed?)
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  def save(*args)
         | 
| 77 | 
            +
                    !!save!(*args)
         | 
| 78 | 
            +
                  rescue Exceptions::ValidationError
         | 
| 79 | 
            +
                    false
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                  def save!(*args)
         | 
| 83 | 
            +
                    with_callback :save, *args do
         | 
| 84 | 
            +
                      !!create_or_update(*args)
         | 
| 85 | 
            +
                    end
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  def create_or_update(*args)
         | 
| 89 | 
            +
                    new_record? ? create_record(*args) : update_record(*args)
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                  def create_record(validate: true)
         | 
| 93 | 
            +
                    if !validate || valid?
         | 
| 94 | 
            +
                      self.class.adapter.insert(**self.class.symbolize(attributes))
         | 
| 95 | 
            +
                      self.new_record = false
         | 
| 96 | 
            +
                      self
         | 
| 97 | 
            +
                    else
         | 
| 98 | 
            +
                      fail Exceptions::ValidationError
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  def transaction(&block)
         | 
| 103 | 
            +
                    returned_value = block.call
         | 
| 104 | 
            +
                    unless returned_value
         | 
| 105 | 
            +
                      silent_assign(previous_attributes)
         | 
| 106 | 
            +
                      clear_previous_attributes
         | 
| 107 | 
            +
                    end
         | 
| 108 | 
            +
                    returned_value
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  def update(validate: false, **new_attributes)
         | 
| 112 | 
            +
                    transaction do
         | 
| 113 | 
            +
                      with_callback :update do
         | 
| 114 | 
            +
                        self.attributes = new_attributes
         | 
| 115 | 
            +
                        return save if changed?
         | 
| 116 | 
            +
                        true
         | 
| 117 | 
            +
                      end
         | 
| 118 | 
            +
                    end
         | 
| 119 | 
            +
                  rescue Exceptions::PrimaryKeyError
         | 
| 120 | 
            +
                    false
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  def update!(validate: false, **new_attributes)
         | 
| 124 | 
            +
                    transaction do
         | 
| 125 | 
            +
                      with_callback :update do
         | 
| 126 | 
            +
                        self.attributes = new_attributes
         | 
| 127 | 
            +
                        return save! if changed?
         | 
| 128 | 
            +
                        true
         | 
| 129 | 
            +
                      end
         | 
| 130 | 
            +
                    end
         | 
| 131 | 
            +
                  end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  def update_record(validate: true)
         | 
| 134 | 
            +
                    if !validate || valid?
         | 
| 135 | 
            +
                      symbolized = self.class.symbolize(variable_attributes)
         | 
| 136 | 
            +
                      updated_object = self.class.adapter.update(primary_keys, **symbolized)
         | 
| 137 | 
            +
                      self.attributes = updated_object.attributes
         | 
| 138 | 
            +
                    else
         | 
| 139 | 
            +
                      fail Exceptions::ValidationError
         | 
| 140 | 
            +
                    end
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
              end
         | 
| 144 | 
            +
            end
         | 
| @@ -0,0 +1,51 @@ | |
| 1 | 
            +
            require 'dinamo/model/errors'
         | 
| 2 | 
            +
            require 'dinamo/model/validator'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Dinamo
         | 
| 5 | 
            +
              class Model
         | 
| 6 | 
            +
                module Validation
         | 
| 7 | 
            +
                  extend ActiveSupport::Concern
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def valid?
         | 
| 10 | 
            +
                    validate
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def errors
         | 
| 14 | 
            +
                    @errors ||= Dinamo::Model::Errors.new
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def validate
         | 
| 18 | 
            +
                    errors.clear
         | 
| 19 | 
            +
                    validate_unsupported_fields
         | 
| 20 | 
            +
                    validate_required_attributes
         | 
| 21 | 
            +
                    validate_by_using_validators
         | 
| 22 | 
            +
                    errors.empty?
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  private
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def validate_by_using_validators
         | 
| 28 | 
            +
                    self.class.validators.each do |validator|
         | 
| 29 | 
            +
                      validator.validate(self)
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def validate_unsupported_fields
         | 
| 34 | 
            +
                    supported_fields = self.class.supported_fields.map(&:name)
         | 
| 35 | 
            +
                    attributes.each do |(attr, _)|
         | 
| 36 | 
            +
                      unless supported_fields.include?(attr.to_s)
         | 
| 37 | 
            +
                        message =  "%p attribute is not supported" % [attr, self.class]
         | 
| 38 | 
            +
                        errors.add(attr, message)
         | 
| 39 | 
            +
                      end
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def validate_required_attributes
         | 
| 44 | 
            +
                    self.class.required_fields.map(&:name).each do |attr|
         | 
| 45 | 
            +
                      message = "%p attribute is required" % attr
         | 
| 46 | 
            +
                      errors.add(attr, message) unless attributes.has_key?(attr)
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            class Dinamo::Model
         | 
| 2 | 
            +
              module Validation
         | 
| 3 | 
            +
                class PresenceValidator < EachValidator
         | 
| 4 | 
            +
                  def validate_each(record, key, value)
         | 
| 5 | 
            +
                    #p record
         | 
| 6 | 
            +
                    #p key
         | 
| 7 | 
            +
                    #p value
         | 
| 8 | 
            +
                    record.errors.add(key, :blank) if value.blank?
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                module ClassMethods
         | 
| 13 | 
            +
                  def validates_presence_of(*args)
         | 
| 14 | 
            +
                    validates_with PresenceValidator, adjust_validator_options(*args)
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
| @@ -0,0 +1,78 @@ | |
| 1 | 
            +
            require 'active_support/core_ext/array/extract_options'
         | 
| 2 | 
            +
            require 'active_support/core_ext/object/blank'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class Dinamo::Model
         | 
| 5 | 
            +
              module Validation
         | 
| 6 | 
            +
                class Validator
         | 
| 7 | 
            +
                  attr_reader :options
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def initialize(**options, &block)
         | 
| 10 | 
            +
                    @options = options || {}
         | 
| 11 | 
            +
                    @block = block
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def validate(record)
         | 
| 15 | 
            +
                    raise NotImplementedError
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                class EachValidator < Validator
         | 
| 20 | 
            +
                  attr_reader :attributes
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def initialize(attributes: [], **options)
         | 
| 23 | 
            +
                    @attributes = attributes
         | 
| 24 | 
            +
                    raise ArgumentError, ":attributes cannot be blank" if @attributes.empty?
         | 
| 25 | 
            +
                    super
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def validate(record)
         | 
| 29 | 
            +
                    attributes.each do |key, val|
         | 
| 30 | 
            +
                      value = record[key]
         | 
| 31 | 
            +
                      next if (value.nil? && options[:allow_nil]) ||
         | 
| 32 | 
            +
                        (value.blank? && options[:allow_blank])
         | 
| 33 | 
            +
                      validate_each(record, key, value)
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def validate_each(record, attribute, value)
         | 
| 38 | 
            +
                    raise NotImplementedError
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                module ClassMethods
         | 
| 43 | 
            +
                  def validates_with(*args, &block)
         | 
| 44 | 
            +
                    options = args.extract_options!
         | 
| 45 | 
            +
                    options[:class] = self
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    args.each do |validator_class|
         | 
| 48 | 
            +
                      validator = validator_class.new(options, &block)
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                      if validator.respond_to?(:attributes) && !validator.attributes.empty?
         | 
| 51 | 
            +
                        validator.attributes.each do |attribute|
         | 
| 52 | 
            +
                          _validators[attribute.to_sym] << validator
         | 
| 53 | 
            +
                        end
         | 
| 54 | 
            +
                      else
         | 
| 55 | 
            +
                        _validators[nil] << validator
         | 
| 56 | 
            +
                      end
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  def validators
         | 
| 61 | 
            +
                    _validators.values.flatten.uniq
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  def _validators
         | 
| 65 | 
            +
                    @_validators ||= Hash.new { |hash, key| hash[key] = [] }
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def adjust_validator_options(*args)
         | 
| 69 | 
            +
                    options = args.extract_options!.symbolize_keys
         | 
| 70 | 
            +
                    args.flatten!
         | 
| 71 | 
            +
                    options[:attributes] = args
         | 
| 72 | 
            +
                    options
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
              require 'dinamo/model/validations/presence'
         | 
| 78 | 
            +
            end
         | 
    
        data/lib/dinamo/model.rb
    ADDED
    
    | @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            require 'active_support/concern'
         | 
| 2 | 
            +
            require 'dinamo/adapter'
         | 
| 3 | 
            +
            require 'dinamo/exceptions'
         | 
| 4 | 
            +
            require 'dinamo/model/callback'
         | 
| 5 | 
            +
            require 'dinamo/model/validation'
         | 
| 6 | 
            +
            require 'dinamo/model/validator'
         | 
| 7 | 
            +
            require 'dinamo/model/attributes'
         | 
| 8 | 
            +
            require 'dinamo/model/persistence'
         | 
| 9 | 
            +
            require 'dinamo/model/dirty'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            module Dinamo
         | 
| 12 | 
            +
              class Model
         | 
| 13 | 
            +
                include Callback
         | 
| 14 | 
            +
                include Validation
         | 
| 15 | 
            +
                include Attributes
         | 
| 16 | 
            +
                include Persistence
         | 
| 17 | 
            +
                include Dirty
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                extend Dinamo::Adapter::Glue
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                class << self
         | 
| 22 | 
            +
                  def default_casters(&block)
         | 
| 23 | 
            +
                    @default_casters ||= block
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def inherited(klass)
         | 
| 27 | 
            +
                    klass.callbacks.merge!(callbacks)
         | 
| 28 | 
            +
                    klass.class_eval &default_casters
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def table_name
         | 
| 32 | 
            +
                    @table_name ||= self.name.split(/::/).last.underscore.pluralize
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def table_name=(name)
         | 
| 36 | 
            +
                    @table_name = name
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                default_casters do
         | 
| 41 | 
            +
                  cast(:list)       { |v| Array(v)           }
         | 
| 42 | 
            +
                  cast(:string)     { |v| v.to_s             }
         | 
| 43 | 
            +
                  cast(:number)     { |v| v.to_i             }
         | 
| 44 | 
            +
                  cast(:binary)     { |v| Array(v).pack(?m)  }
         | 
| 45 | 
            +
                  cast(:boolean)    { |v| !!v                }
         | 
| 46 | 
            +
                  cast(:number_set) { |v| Array(v).map(&:to_i) }
         | 
| 47 | 
            +
                  cast(:string_set) { |v| Array(v).map(&:to_s) }
         | 
| 48 | 
            +
                  cast(:binary_set) { |v| Array(v).map { |t| Array(t.to_s).pack(?m) } }
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def initialize(**attributes, &block)
         | 
| 52 | 
            +
                  with_callback :initialize, **attributes do
         | 
| 53 | 
            +
                    block.call(self) if block_given?
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
    
        data/lib/dinamo.rb
    ADDED
    
    
    
        metadata
    ADDED
    
    | @@ -0,0 +1,168 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: dinamo
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - namusyaka
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: exe
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2015-12-14 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: activesupport
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - ~>
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: 4.2.4
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - ~>
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: 4.2.4
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: aws-sdk
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - ~>
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: 2.1.26
         | 
| 34 | 
            +
              type: :runtime
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - ~>
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: 2.1.26
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: bundler
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - ~>
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '1.10'
         | 
| 48 | 
            +
              type: :development
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - ~>
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '1.10'
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: rake
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - ~>
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '10.0'
         | 
| 62 | 
            +
              type: :development
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - ~>
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: '10.0'
         | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: test-unit
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - '>='
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: '0'
         | 
| 76 | 
            +
              type: :development
         | 
| 77 | 
            +
              prerelease: false
         | 
| 78 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 | 
            +
                requirements:
         | 
| 80 | 
            +
                - - '>='
         | 
| 81 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                    version: '0'
         | 
| 83 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 84 | 
            +
              name: test-unit-rr
         | 
| 85 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 86 | 
            +
                requirements:
         | 
| 87 | 
            +
                - - '>='
         | 
| 88 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 89 | 
            +
                    version: '0'
         | 
| 90 | 
            +
              type: :development
         | 
| 91 | 
            +
              prerelease: false
         | 
| 92 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 93 | 
            +
                requirements:
         | 
| 94 | 
            +
                - - '>='
         | 
| 95 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 96 | 
            +
                    version: '0'
         | 
| 97 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 98 | 
            +
              name: test-unit-power_assert
         | 
| 99 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 100 | 
            +
                requirements:
         | 
| 101 | 
            +
                - - '>='
         | 
| 102 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 103 | 
            +
                    version: '0'
         | 
| 104 | 
            +
              type: :development
         | 
| 105 | 
            +
              prerelease: false
         | 
| 106 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 107 | 
            +
                requirements:
         | 
| 108 | 
            +
                - - '>='
         | 
| 109 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 110 | 
            +
                    version: '0'
         | 
| 111 | 
            +
            description: DynamoDB ORM for Ruby
         | 
| 112 | 
            +
            email:
         | 
| 113 | 
            +
            - namusyaka@gmail.com
         | 
| 114 | 
            +
            executables: []
         | 
| 115 | 
            +
            extensions: []
         | 
| 116 | 
            +
            extra_rdoc_files: []
         | 
| 117 | 
            +
            files:
         | 
| 118 | 
            +
            - .gitignore
         | 
| 119 | 
            +
            - .rspec
         | 
| 120 | 
            +
            - .travis.yml
         | 
| 121 | 
            +
            - CODE_OF_CONDUCT.md
         | 
| 122 | 
            +
            - Gemfile
         | 
| 123 | 
            +
            - LICENSE.txt
         | 
| 124 | 
            +
            - README.md
         | 
| 125 | 
            +
            - Rakefile
         | 
| 126 | 
            +
            - bin/console
         | 
| 127 | 
            +
            - bin/setup
         | 
| 128 | 
            +
            - dinamo.gemspec
         | 
| 129 | 
            +
            - lib/dinamo.rb
         | 
| 130 | 
            +
            - lib/dinamo/adapter.rb
         | 
| 131 | 
            +
            - lib/dinamo/exceptions.rb
         | 
| 132 | 
            +
            - lib/dinamo/model.rb
         | 
| 133 | 
            +
            - lib/dinamo/model/attributes.rb
         | 
| 134 | 
            +
            - lib/dinamo/model/callback.rb
         | 
| 135 | 
            +
            - lib/dinamo/model/caster.rb
         | 
| 136 | 
            +
            - lib/dinamo/model/dirty.rb
         | 
| 137 | 
            +
            - lib/dinamo/model/errors.rb
         | 
| 138 | 
            +
            - lib/dinamo/model/persistence.rb
         | 
| 139 | 
            +
            - lib/dinamo/model/validation.rb
         | 
| 140 | 
            +
            - lib/dinamo/model/validations/presence.rb
         | 
| 141 | 
            +
            - lib/dinamo/model/validator.rb
         | 
| 142 | 
            +
            - lib/dinamo/version.rb
         | 
| 143 | 
            +
            homepage: https://github.com/namusyaka/dinamo
         | 
| 144 | 
            +
            licenses:
         | 
| 145 | 
            +
            - MIT
         | 
| 146 | 
            +
            metadata:
         | 
| 147 | 
            +
              allowed_push_host: 'TODO: Set to ''http://mygemserver.com'''
         | 
| 148 | 
            +
            post_install_message: 
         | 
| 149 | 
            +
            rdoc_options: []
         | 
| 150 | 
            +
            require_paths:
         | 
| 151 | 
            +
            - lib
         | 
| 152 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 153 | 
            +
              requirements:
         | 
| 154 | 
            +
              - - '>='
         | 
| 155 | 
            +
                - !ruby/object:Gem::Version
         | 
| 156 | 
            +
                  version: '0'
         | 
| 157 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 158 | 
            +
              requirements:
         | 
| 159 | 
            +
              - - '>='
         | 
| 160 | 
            +
                - !ruby/object:Gem::Version
         | 
| 161 | 
            +
                  version: '0'
         | 
| 162 | 
            +
            requirements: []
         | 
| 163 | 
            +
            rubyforge_project: 
         | 
| 164 | 
            +
            rubygems_version: 2.0.14
         | 
| 165 | 
            +
            signing_key: 
         | 
| 166 | 
            +
            specification_version: 4
         | 
| 167 | 
            +
            summary: DynamoDB ORM for Ruby
         | 
| 168 | 
            +
            test_files: []
         |