clearwater 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/lib/clearwater.rb +7 -0
 - data/lib/clearwater/version.rb +3 -0
 - data/opal/clearwater.rb +4 -0
 - data/opal/clearwater/api_client.rb +90 -0
 - data/opal/clearwater/application.rb +128 -0
 - data/opal/clearwater/application_registry.rb +23 -0
 - data/opal/clearwater/cached_render.rb +15 -0
 - data/opal/clearwater/cgi.rb +14 -0
 - data/opal/clearwater/component.rb +205 -0
 - data/opal/clearwater/link.rb +122 -0
 - data/opal/clearwater/model.rb +33 -0
 - data/opal/clearwater/router.rb +98 -0
 - data/opal/clearwater/router/route.rb +43 -0
 - data/opal/clearwater/router/route_collection.rb +52 -0
 - data/opal/clearwater/store.rb +99 -0
 - data/opal/clearwater/virtual_dom.rb +108 -0
 - data/opal/clearwater/virtual_dom/js/virtual_dom.js +1663 -0
 - metadata +174 -0
 
| 
         @@ -0,0 +1,122 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'clearwater/component'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'browser/history'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            # TODO: Remove this once opal-browser supports coordinates natively
         
     | 
| 
      
 5 
     | 
    
         
            +
            # for touch events.
         
     | 
| 
      
 6 
     | 
    
         
            +
            module Browser
         
     | 
| 
      
 7 
     | 
    
         
            +
              class Event
         
     | 
| 
      
 8 
     | 
    
         
            +
                class Touch
         
     | 
| 
      
 9 
     | 
    
         
            +
                  def x
         
     | 
| 
      
 10 
     | 
    
         
            +
                    `#@native.pageX`
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  def y
         
     | 
| 
      
 14 
     | 
    
         
            +
                    `#@native.pageY`
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
            end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            class Link
         
     | 
| 
      
 21 
     | 
    
         
            +
              include Clearwater::Component
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              attr_reader :attributes, :content
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              def initialize(attributes={}, content=nil)
         
     | 
| 
      
 26 
     | 
    
         
            +
                # Augment the onclick passed in by the user, don't replace it.
         
     | 
| 
      
 27 
     | 
    
         
            +
                @onclick = attributes.dup.delete(:onclick)
         
     | 
| 
      
 28 
     | 
    
         
            +
                @attributes = attributes.merge(
         
     | 
| 
      
 29 
     | 
    
         
            +
                  onclick: method(:handle_click),
         
     | 
| 
      
 30 
     | 
    
         
            +
                  ontouchstart: method(:handle_touch),
         
     | 
| 
      
 31 
     | 
    
         
            +
                  key: attributes[:href]
         
     | 
| 
      
 32 
     | 
    
         
            +
                )
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                check_active attributes[:href]
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                @content = content
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
              def handle_click event
         
     | 
| 
      
 40 
     | 
    
         
            +
                if @onclick
         
     | 
| 
      
 41 
     | 
    
         
            +
                  @onclick.call event
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                if touch?
         
     | 
| 
      
 45 
     | 
    
         
            +
                  event.prevent
         
     | 
| 
      
 46 
     | 
    
         
            +
                  return
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                if event.prevented?
         
     | 
| 
      
 50 
     | 
    
         
            +
                  warn "You are preventing the default behavior of a `Link` component. " +
         
     | 
| 
      
 51 
     | 
    
         
            +
                       "In this case, you could just use an `a` element."
         
     | 
| 
      
 52 
     | 
    
         
            +
                else
         
     | 
| 
      
 53 
     | 
    
         
            +
                  navigate event
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
              end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
              def handle_touch event
         
     | 
| 
      
 58 
     | 
    
         
            +
                # All links will treat this as touch because this is a touch device
         
     | 
| 
      
 59 
     | 
    
         
            +
                @@touch = true
         
     | 
| 
      
 60 
     | 
    
         
            +
                moved = false
         
     | 
| 
      
 61 
     | 
    
         
            +
                x_start = event.x
         
     | 
| 
      
 62 
     | 
    
         
            +
                y_start = event.y
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                touch_move_handler = proc do |event|
         
     | 
| 
      
 65 
     | 
    
         
            +
                  x_now = event.x
         
     | 
| 
      
 66 
     | 
    
         
            +
                  y_now = event.y
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  # Count this gesture as a non-click if user moves over 30px
         
     | 
| 
      
 69 
     | 
    
         
            +
                  if ((x_now - x_start) ** 2 + (y_now - y_start) ** 2) ** 0.5 > 30
         
     | 
| 
      
 70 
     | 
    
         
            +
                    moved = true
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                touch_end_handler = proc do
         
     | 
| 
      
 75 
     | 
    
         
            +
                  unless moved
         
     | 
| 
      
 76 
     | 
    
         
            +
                    @onclick.call event if @onclick
         
     | 
| 
      
 77 
     | 
    
         
            +
                    navigate event
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                  $document.off 'touchmove', &touch_move_handler
         
     | 
| 
      
 81 
     | 
    
         
            +
                  $document.off 'touchend', &touch_end_handler
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                $document.on 'touchmove', &touch_move_handler
         
     | 
| 
      
 85 
     | 
    
         
            +
                $document.on 'touchend', &touch_end_handler
         
     | 
| 
      
 86 
     | 
    
         
            +
              end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
              def navigate event
         
     | 
| 
      
 89 
     | 
    
         
            +
                unless event.meta? || event.shift? || event.ctrl? || event.alt?
         
     | 
| 
      
 90 
     | 
    
         
            +
                  event.prevent
         
     | 
| 
      
 91 
     | 
    
         
            +
                  if href != $window.location.path
         
     | 
| 
      
 92 
     | 
    
         
            +
                    $window.history.push href
         
     | 
| 
      
 93 
     | 
    
         
            +
                    call
         
     | 
| 
      
 94 
     | 
    
         
            +
                    $window.scroll.to x: 0, y: 0
         
     | 
| 
      
 95 
     | 
    
         
            +
                  end
         
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
              end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
              def href
         
     | 
| 
      
 100 
     | 
    
         
            +
                attributes[:href]
         
     | 
| 
      
 101 
     | 
    
         
            +
              end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
              def render
         
     | 
| 
      
 104 
     | 
    
         
            +
                a(attributes, content)
         
     | 
| 
      
 105 
     | 
    
         
            +
              end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
              def touch?
         
     | 
| 
      
 108 
     | 
    
         
            +
                !!@@touch
         
     | 
| 
      
 109 
     | 
    
         
            +
              end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
              def check_active href
         
     | 
| 
      
 112 
     | 
    
         
            +
                if $window.location.path == href
         
     | 
| 
      
 113 
     | 
    
         
            +
                  class_name = (
         
     | 
| 
      
 114 
     | 
    
         
            +
                    @attributes.delete(:class_name) ||
         
     | 
| 
      
 115 
     | 
    
         
            +
                    @attributes.delete(:class) ||
         
     | 
| 
      
 116 
     | 
    
         
            +
                    @attributes.delete(:className)
         
     | 
| 
      
 117 
     | 
    
         
            +
                  ).to_s
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                  @attributes[:className] = "#{class_name} active"
         
     | 
| 
      
 120 
     | 
    
         
            +
                end
         
     | 
| 
      
 121 
     | 
    
         
            +
              end
         
     | 
| 
      
 122 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,33 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "clearwater/binding"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Clearwater
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Model
         
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize attributes={}
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @_bindings = Hash.new { |h,k| h[k] = [] }
         
     | 
| 
      
 7 
     | 
    
         
            +
                  self.class.attributes.each do |attr|
         
     | 
| 
      
 8 
     | 
    
         
            +
                    public_send "#{attr}=", attributes[attr]
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def add_binding attribute, &block
         
     | 
| 
      
 13 
     | 
    
         
            +
                  binding = Binding.new(self, attribute, &block)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @_bindings[attribute].delete_if(&:dead?)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @_bindings[attribute] << binding
         
     | 
| 
      
 16 
     | 
    
         
            +
                  binding
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def self.attributes *args
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @attributes ||= []
         
     | 
| 
      
 21 
     | 
    
         
            +
                  args.each do |attr|
         
     | 
| 
      
 22 
     | 
    
         
            +
                    attr_reader attr
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    define_method "#{attr}=" do |value|
         
     | 
| 
      
 25 
     | 
    
         
            +
                      instance_variable_set "@#{attr}", value
         
     | 
| 
      
 26 
     | 
    
         
            +
                      @_bindings[attr].each(&:call)
         
     | 
| 
      
 27 
     | 
    
         
            +
                      @_bindings[attr].delete_if(&:dead?)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    end
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @attributes.concat args
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,98 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "clearwater/router/route_collection"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Clearwater
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Router
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_reader :window, :location, :history
         
     | 
| 
      
 6 
     | 
    
         
            +
                attr_accessor :application
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def initialize options={}, &block
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @window   = options.fetch(:window)   { Native(`window`)  }
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @location = options.fetch(:location) { window[:location] }
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @history  = options.fetch(:history)  { window[:history]  }
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @routes   = RouteCollection.new(self)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @application = options[:application]
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  add_routes(&block) if block_given?
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def add_routes &block
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @routes.instance_exec(&block)
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def routes_for_path path
         
     | 
| 
      
 23 
     | 
    
         
            +
                  parts = path.split("/").reject(&:empty?)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @routes[parts]
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def canonical_path_for_path path
         
     | 
| 
      
 28 
     | 
    
         
            +
                  routes_for_path(path).map { |r| "/#{r.key}" }.join
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                def targets_for_path path
         
     | 
| 
      
 32 
     | 
    
         
            +
                  routes_for_path(path).map(&:target)
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                def params_for_path path
         
     | 
| 
      
 36 
     | 
    
         
            +
                  path_parts = path.split("/").reject(&:empty?)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  canonical_parts = canonical_path_for_path(path).split("/").reject(&:empty?)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  params = {}
         
     | 
| 
      
 39 
     | 
    
         
            +
                  canonical_parts.each_with_object(params)
         
     | 
| 
      
 40 
     | 
    
         
            +
                                 .each_with_index { |(part, params), index|
         
     | 
| 
      
 41 
     | 
    
         
            +
                    if part.start_with? ":"
         
     | 
| 
      
 42 
     | 
    
         
            +
                      param = part[1..-1].to_sym
         
     | 
| 
      
 43 
     | 
    
         
            +
                      params[param] = path_parts[index]
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
                  }
         
     | 
| 
      
 46 
     | 
    
         
            +
                  params
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                def canonical_path
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                def current_path
         
     | 
| 
      
 53 
     | 
    
         
            +
                  location[:pathname]
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                def navigate_to path
         
     | 
| 
      
 57 
     | 
    
         
            +
                  push_state path
         
     | 
| 
      
 58 
     | 
    
         
            +
                  set_outlets
         
     | 
| 
      
 59 
     | 
    
         
            +
                  render_application
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                def navigate_to_remote path
         
     | 
| 
      
 63 
     | 
    
         
            +
                  location[:href] = path
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                def current_url
         
     | 
| 
      
 67 
     | 
    
         
            +
                  location[:href]
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                def back
         
     | 
| 
      
 71 
     | 
    
         
            +
                  history.back
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                def set_outlets targets=targets_for_path(current_path)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  if targets.any?
         
     | 
| 
      
 76 
     | 
    
         
            +
                    (targets.count).times do |index|
         
     | 
| 
      
 77 
     | 
    
         
            +
                      targets[index].outlet = targets[index + 1]
         
     | 
| 
      
 78 
     | 
    
         
            +
                    end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                    application && application.component.outlet = targets.first
         
     | 
| 
      
 81 
     | 
    
         
            +
                  else
         
     | 
| 
      
 82 
     | 
    
         
            +
                    application && application.component.outlet = nil
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
                end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                private
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                def push_state path
         
     | 
| 
      
 89 
     | 
    
         
            +
                  history.pushState({}, nil, path)
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                def render_application
         
     | 
| 
      
 93 
     | 
    
         
            +
                  if application && application.component
         
     | 
| 
      
 94 
     | 
    
         
            +
                    application.component.call
         
     | 
| 
      
 95 
     | 
    
         
            +
                  end
         
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
              end
         
     | 
| 
      
 98 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,43 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "clearwater/router/route_collection"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Clearwater
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Router
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Route
         
     | 
| 
      
 6 
     | 
    
         
            +
                  attr_reader :target, :key, :parent
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def initialize options={}
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @key = options.fetch(:key)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @target = options.fetch(:target)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @parent = options.fetch(:parent)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  def route *args, &block
         
     | 
| 
      
 15 
     | 
    
         
            +
                    nested_routes.route *args, &block
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def canonical_path
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @canonical_path ||= "#{parent.canonical_path}/#{key}".gsub("//", "/")
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def match key, other_parts=[]
         
     | 
| 
      
 23 
     | 
    
         
            +
                    if key && (key == self.key || param_key?)
         
     | 
| 
      
 24 
     | 
    
         
            +
                      if Array(other_parts).any?
         
     | 
| 
      
 25 
     | 
    
         
            +
                        [self, nested_routes[other_parts]]
         
     | 
| 
      
 26 
     | 
    
         
            +
                      else
         
     | 
| 
      
 27 
     | 
    
         
            +
                        self
         
     | 
| 
      
 28 
     | 
    
         
            +
                      end
         
     | 
| 
      
 29 
     | 
    
         
            +
                    end
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  private
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  def param_key?
         
     | 
| 
      
 35 
     | 
    
         
            +
                    @param_key ||= key.start_with? ":"
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  def nested_routes
         
     | 
| 
      
 39 
     | 
    
         
            +
                    @nested_routes ||= RouteCollection.new(self)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,52 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "clearwater/router/route"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Clearwater
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Router
         
     | 
| 
      
 5 
     | 
    
         
            +
                class RouteCollection
         
     | 
| 
      
 6 
     | 
    
         
            +
                  attr_reader :router
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def initialize parent
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @routes = []
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @parent = parent
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @router = loop do
         
     | 
| 
      
 12 
     | 
    
         
            +
                      break parent if parent.is_a? Router
         
     | 
| 
      
 13 
     | 
    
         
            +
                      parent = parent.parent
         
     | 
| 
      
 14 
     | 
    
         
            +
                    end
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def route route_options, &block
         
     | 
| 
      
 18 
     | 
    
         
            +
                    route_key = route_options.keys.first.to_s
         
     | 
| 
      
 19 
     | 
    
         
            +
                    target = route_options.delete(route_key)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    target.router = router
         
     | 
| 
      
 21 
     | 
    
         
            +
                    options = {
         
     | 
| 
      
 22 
     | 
    
         
            +
                      key: route_key,
         
     | 
| 
      
 23 
     | 
    
         
            +
                      target: target,
         
     | 
| 
      
 24 
     | 
    
         
            +
                      parent: parent,
         
     | 
| 
      
 25 
     | 
    
         
            +
                    }.merge(route_options)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    route = Route.new(options)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    route.instance_exec(&block) if block_given?
         
     | 
| 
      
 28 
     | 
    
         
            +
                    @routes << route
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  def namespace path
         
     | 
| 
      
 32 
     | 
    
         
            +
                    @namespace = path
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  def [] route_names
         
     | 
| 
      
 36 
     | 
    
         
            +
                    if route_names.any? && route_names.first == @namespace
         
     | 
| 
      
 37 
     | 
    
         
            +
                      route_names = route_names[1..-1]
         
     | 
| 
      
 38 
     | 
    
         
            +
                    end
         
     | 
| 
      
 39 
     | 
    
         
            +
                    routes = @routes.map { |r|
         
     | 
| 
      
 40 
     | 
    
         
            +
                      r.match route_names.first, route_names[1..-1]
         
     | 
| 
      
 41 
     | 
    
         
            +
                    }
         
     | 
| 
      
 42 
     | 
    
         
            +
                    routes.compact!
         
     | 
| 
      
 43 
     | 
    
         
            +
                    routes.flatten!
         
     | 
| 
      
 44 
     | 
    
         
            +
                    routes
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  private
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  attr_reader :parent
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
              end
         
     | 
| 
      
 52 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,99 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "json"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Clearwater
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Store
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_reader :mapping, :identity_map
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                def initialize options={}
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @url = options.fetch(:url) { "/api/:model/:id" }
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @mapping = Mapping.new(options.fetch(:mapping) { {} })
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @protocol = options.fetch(:protocol) { HTTP }
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @identity_map = IdentityMap.new
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                def all klass
         
     | 
| 
      
 15 
     | 
    
         
            +
                  deserialized = api(:get, url_for(klass)).body
         
     | 
| 
      
 16 
     | 
    
         
            +
                  models = JSON.parse(deserialized).map do |attributes|
         
     | 
| 
      
 17 
     | 
    
         
            +
                    deserialize(klass, attributes)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def find klass, id
         
     | 
| 
      
 22 
     | 
    
         
            +
                  identity_map.fetch(klass, id) do
         
     | 
| 
      
 23 
     | 
    
         
            +
                    serialized = api(:get, url_for(klass, id)).body
         
     | 
| 
      
 24 
     | 
    
         
            +
                    identity_map[klass][id] = deserialize(klass, JSON.parse(serialized))
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def save model
         
     | 
| 
      
 29 
     | 
    
         
            +
                  method = persisted?(model) ? :patch : :post
         
     | 
| 
      
 30 
     | 
    
         
            +
                  response = api method, url_for_model(model)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  if method == :post && response.ok
         
     | 
| 
      
 32 
     | 
    
         
            +
                    attributes = JSON.parse(response.body)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    model.instance_variable_set :@id, attributes[:id]
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def delete model
         
     | 
| 
      
 38 
     | 
    
         
            +
                  api :delete, url_for_model(model)
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def persisted? model
         
     | 
| 
      
 42 
     | 
    
         
            +
                  !!model.id
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                private
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def api method, url
         
     | 
| 
      
 48 
     | 
    
         
            +
                  @protocol.public_send method, url
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def url_for klass, id=nil
         
     | 
| 
      
 52 
     | 
    
         
            +
                  @url.gsub(":model", mapping[klass])
         
     | 
| 
      
 53 
     | 
    
         
            +
                      .gsub(":id", id.to_s)
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                def url_for_model model
         
     | 
| 
      
 57 
     | 
    
         
            +
                  url_for(model.class, model.id)
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                def url_for_class klass, id
         
     | 
| 
      
 61 
     | 
    
         
            +
                  url_for(klass, nil)
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                def deserialize klass, attributes
         
     | 
| 
      
 65 
     | 
    
         
            +
                  model = klass.allocate
         
     | 
| 
      
 66 
     | 
    
         
            +
                  attributes.each do |attr, value|
         
     | 
| 
      
 67 
     | 
    
         
            +
                    model.instance_variable_set "@#{attr}", value
         
     | 
| 
      
 68 
     | 
    
         
            +
                  end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                  model
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
              end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
              class Mapping
         
     | 
| 
      
 75 
     | 
    
         
            +
                def initialize mappings={}
         
     | 
| 
      
 76 
     | 
    
         
            +
                  @custom_mappings = mappings
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                def [] klass
         
     | 
| 
      
 80 
     | 
    
         
            +
                  @custom_mappings.fetch(klass) { |*args|
         
     | 
| 
      
 81 
     | 
    
         
            +
                    klass.name.downcase.gsub("::", "/") + "s"
         
     | 
| 
      
 82 
     | 
    
         
            +
                  }
         
     | 
| 
      
 83 
     | 
    
         
            +
                end
         
     | 
| 
      
 84 
     | 
    
         
            +
              end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
              class IdentityMap
         
     | 
| 
      
 87 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 88 
     | 
    
         
            +
                  @map = Hash.new { |h, k| h[k] = {} }
         
     | 
| 
      
 89 
     | 
    
         
            +
                end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                def [] key
         
     | 
| 
      
 92 
     | 
    
         
            +
                  @map[key]
         
     | 
| 
      
 93 
     | 
    
         
            +
                end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                def fetch(klass, id, &block)
         
     | 
| 
      
 96 
     | 
    
         
            +
                  self[klass].fetch(id, &block)
         
     | 
| 
      
 97 
     | 
    
         
            +
                end
         
     | 
| 
      
 98 
     | 
    
         
            +
              end
         
     | 
| 
      
 99 
     | 
    
         
            +
            end
         
     |