remotable 0.1.2 → 0.2.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.
- data/README.mdown +47 -7
 - data/lib/remotable.rb +99 -3
 - data/lib/remotable/active_record_extender.rb +162 -68
 - data/lib/remotable/active_resource_fixes.rb +15 -0
 - data/lib/remotable/adapters/active_resource.rb +8 -19
 - data/lib/remotable/core_ext.rb +2 -1
 - data/lib/remotable/core_ext/object.rb +27 -0
 - data/lib/remotable/nosync.rb +0 -1
 - data/lib/remotable/validate_models.rb +23 -0
 - data/lib/remotable/version.rb +1 -1
 - data/lib/remotable/with_remote_model_proxy.rb +18 -0
 - data/remotable.gemspec +3 -0
 - data/test/active_resource_test.rb +332 -0
 - data/test/bespoke_test.rb +197 -0
 - data/test/factories/tenants.rb +8 -0
 - data/test/remotable_test.rb +48 -256
 - data/test/support/active_resource.rb +14 -12
 - data/test/support/bespoke.rb +57 -0
 - data/test/support/schema.rb +1 -0
 - data/test/test_helper.rb +13 -0
 - data/test/understanding_test.rb +16 -0
 - metadata +53 -9
 
    
        data/README.mdown
    CHANGED
    
    | 
         @@ -42,7 +42,9 @@ Specify the attributes of the local model that you want to keep in sync with the 
     | 
|
| 
       42 
42 
     | 
    
         
             
                              :id => :remote_id
         
     | 
| 
       43 
43 
     | 
    
         
             
                end
         
     | 
| 
       44 
44 
     | 
    
         | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
      
 45 
     | 
    
         
            +
            ### Remote Keys
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
            By default Remotable assumes that the local model and remote model are joined with the connection you might express in SQL this way: `local_model INNER JOIN remote_model ON local_model.id=remote_model.id`. But it is generally impractical to join on `local_model.id`.
         
     | 
| 
       46 
48 
     | 
    
         | 
| 
       47 
49 
     | 
    
         
             
            If you specify `attr_remote :id => :remote_id`, then the join will be on `local_model.remote_id=remote_model.id`, but you can also use a different attribute as the join key:
         
     | 
| 
       48 
50 
     | 
    
         | 
| 
         @@ -56,17 +58,39 @@ If you specify `attr_remote :id => :remote_id`, then the join will be on `local_ 
     | 
|
| 
       56 
58 
     | 
    
         | 
| 
       57 
59 
     | 
    
         
             
            Now, the join could be expressed this way: `local_model.slug=remote_model.slug`.
         
     | 
| 
       58 
60 
     | 
    
         | 
| 
      
 61 
     | 
    
         
            +
            If you must look up a remote model with more than one attribute, you can express a composite key this way:
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                class Event < ActiveRecord::Base
         
     | 
| 
      
 64 
     | 
    
         
            +
                  remote_model RemoteEvent
         
     | 
| 
      
 65 
     | 
    
         
            +
                  attr_remote :calendar_id,
         
     | 
| 
      
 66 
     | 
    
         
            +
                              :id => :remote_id
         
     | 
| 
      
 67 
     | 
    
         
            +
                  remote_key  [:calendar_id, :remote_id]
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
       59 
70 
     | 
    
         
             
            ### Finders
         
     | 
| 
       60 
71 
     | 
    
         | 
| 
       61 
     | 
    
         
            -
            For  
     | 
| 
      
 72 
     | 
    
         
            +
            For `:id` or whatever you chose to be the remote key, Remotable will create a finder method on the ActiveRecord model. These finders will _first_ look in the local database for the requested record and, if it isn't found, look for the resource remotely. If a finder finds the resource remotely, it creates a local copy and returns that.
         
     | 
| 
       62 
73 
     | 
    
         | 
| 
       63 
     | 
    
         
            -
             
     | 
| 
      
 74 
     | 
    
         
            +
            You can create additional finder with the `find_by` method:
         
     | 
| 
       64 
75 
     | 
    
         | 
| 
       65 
     | 
    
         
            -
                 
     | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
      
 76 
     | 
    
         
            +
                class Tenant < ActiveRecord::Base
         
     | 
| 
      
 77 
     | 
    
         
            +
                  remote_model RemoteTenant
         
     | 
| 
      
 78 
     | 
    
         
            +
                  attr_remote :slug,
         
     | 
| 
      
 79 
     | 
    
         
            +
                              :customer_name => :name,
         
     | 
| 
      
 80 
     | 
    
         
            +
                              :id => :remote_id
         
     | 
| 
      
 81 
     | 
    
         
            +
                  find_by :slug
         
     | 
| 
      
 82 
     | 
    
         
            +
                  find_by :name
         
     | 
| 
      
 83 
     | 
    
         
            +
                end
         
     | 
| 
       68 
84 
     | 
    
         | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
      
 85 
     | 
    
         
            +
            Remotable will create the following methods and assume the URI for the custom finders from the attribute. The example above will create the following methods:
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                find_by_remote_id(...)    # Looks in api_path/tenants/:id
         
     | 
| 
      
 88 
     | 
    
         
            +
                find_by_slug(...)         # Looks in api_path/tenants/by_slug/:slug
         
     | 
| 
      
 89 
     | 
    
         
            +
                find_by_name(...)         # Looks in api_path/tenants/by_name/:name
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
            Note that the finder methods are named with the _local_ attributes.
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
            You can specify a custom path with the `find_by` method:
         
     | 
| 
       70 
94 
     | 
    
         | 
| 
       71 
95 
     | 
    
         
             
                class Tenant < ActiveRecord::Base
         
     | 
| 
       72 
96 
     | 
    
         
             
                  remote_model RemoteTenant
         
     | 
| 
         @@ -76,6 +100,7 @@ You can specify a custom path with the `find_by` method or you can always write 
     | 
|
| 
       76 
100 
     | 
    
         
             
                  find_by :name, :path => "by_nombre/:name"
         
     | 
| 
       77 
101 
     | 
    
         
             
                end
         
     | 
| 
       78 
102 
     | 
    
         | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
       79 
104 
     | 
    
         
             
            When you use `find_by`, give the name of the _local_ attribute not the remote one (if they differ). Also, the name of the symbolic part of the path should match the local attribute name as well.
         
     | 
| 
       80 
105 
     | 
    
         | 
| 
       81 
106 
     | 
    
         
             
            ### Expiration
         
     | 
| 
         @@ -91,6 +116,21 @@ Whenever a remoted record is instantiated, Remotable checks the value of its `ex 
     | 
|
| 
       91 
116 
     | 
    
         | 
| 
       92 
117 
     | 
    
         
             
            Remotable checks class you hand to `remote_model` to see what it inherits from. Remotable checks this to use the correct adapter with the remote model. Currently, the only adapter included in Remotable is for ActiveResource::Base.
         
     | 
| 
       93 
118 
     | 
    
         | 
| 
      
 119 
     | 
    
         
            +
            ### Custom Backends / Adapters
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
            You can write your own backends for Remotable. Just hand `remote_model` an object which responds to two methods: `new_resource` and `find_by`.
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
             * `new_resource` should take 0 arguments and return an uninitialized object which represents a remote resource.
         
     | 
| 
      
 124 
     | 
    
         
            +
             * `find_by` — either `find_by(path)` or `find_by(remote_attr, value)` — should take either 1 or 2 arguments. If it takes 1 argument, it will be passed the relative path of a remote resource. If it takes 2 arguments, it will be passed an attribute name and value to be used to look up a remote resource. `find_by` should return either a single remote resource or nil (if none could be found).
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
            The instances of a remote resource must also respond to certain methods. Instance should respond to:
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
             * `save` (return true if successful, false if not)
         
     | 
| 
      
 129 
     | 
    
         
            +
             * `destroy`
         
     | 
| 
      
 130 
     | 
    
         
            +
             * `errors` (a hash of errors to be populated by an unsuccessful save)
         
     | 
| 
      
 131 
     | 
    
         
            +
             * the getters and setters for all attributes which will be synchronized remotely
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
       94 
134 
     | 
    
         
             
            ## Development
         
     | 
| 
       95 
135 
     | 
    
         | 
| 
       96 
136 
     | 
    
         
             
            ### To Do
         
     | 
    
        data/lib/remotable.rb
    CHANGED
    
    | 
         @@ -1,5 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require "remotable/version"
         
     | 
| 
       2 
2 
     | 
    
         
             
            require "remotable/nosync"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "remotable/validate_models"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "remotable/with_remote_model_proxy"
         
     | 
| 
       3 
5 
     | 
    
         | 
| 
       4 
6 
     | 
    
         | 
| 
       5 
7 
     | 
    
         
             
            # Remotable keeps a locally-stored ActiveRecord
         
     | 
| 
         @@ -30,22 +32,116 @@ require "remotable/nosync" 
     | 
|
| 
       30 
32 
     | 
    
         
             
            #
         
     | 
| 
       31 
33 
     | 
    
         
             
            module Remotable
         
     | 
| 
       32 
34 
     | 
    
         
             
              extend Nosync
         
     | 
| 
      
 35 
     | 
    
         
            +
              extend ValidateModels
         
     | 
| 
       33 
36 
     | 
    
         | 
| 
      
 37 
     | 
    
         
            +
              # By default, Remotable will validate the models you
         
     | 
| 
      
 38 
     | 
    
         
            +
              # supply it via +remote_model+. You can set validate_models
         
     | 
| 
      
 39 
     | 
    
         
            +
              # to false to skip this validation. It is recommended that
         
     | 
| 
      
 40 
     | 
    
         
            +
              # you keep validation on in development and test environments,
         
     | 
| 
      
 41 
     | 
    
         
            +
              # but turn it off in production.
         
     | 
| 
      
 42 
     | 
    
         
            +
              self.validate_models = true
         
     | 
| 
       34 
43 
     | 
    
         | 
| 
       35 
44 
     | 
    
         | 
| 
      
 45 
     | 
    
         
            +
              # == remote_model( model [optional] )
         
     | 
| 
      
 46 
     | 
    
         
            +
              # 
         
     | 
| 
      
 47 
     | 
    
         
            +
              # When called without arguments, this method returns
         
     | 
| 
      
 48 
     | 
    
         
            +
              # the remote model connected to this local ActiveRecord
         
     | 
| 
      
 49 
     | 
    
         
            +
              # model.
         
     | 
| 
      
 50 
     | 
    
         
            +
              #
         
     | 
| 
      
 51 
     | 
    
         
            +
              # When called with an argument, it extends the ActiveRecord
         
     | 
| 
      
 52 
     | 
    
         
            +
              # model on which it is called.
         
     | 
| 
      
 53 
     | 
    
         
            +
              #
         
     | 
| 
      
 54 
     | 
    
         
            +
              # <tt>model</tt> can be a class that inherits from any
         
     | 
| 
      
 55 
     | 
    
         
            +
              # of these API consumers:
         
     | 
| 
      
 56 
     | 
    
         
            +
              #
         
     | 
| 
      
 57 
     | 
    
         
            +
              #  * ActiveResource
         
     | 
| 
      
 58 
     | 
    
         
            +
              # 
         
     | 
| 
      
 59 
     | 
    
         
            +
              # <tt>model</tt> can be any object that responds
         
     | 
| 
      
 60 
     | 
    
         
            +
              # to these two methods for getting a resource:
         
     | 
| 
      
 61 
     | 
    
         
            +
              #
         
     | 
| 
      
 62 
     | 
    
         
            +
              #  * +find_by(path)+ or +find_by(remote_attr, value)+
         
     | 
| 
      
 63 
     | 
    
         
            +
              #      +find_by+ can be defined to take either one argument or two.
         
     | 
| 
      
 64 
     | 
    
         
            +
              #      If it takes one argument, it will be passed path.
         
     | 
| 
      
 65 
     | 
    
         
            +
              #      If it takes two, it will be passed remote_attr and value.
         
     | 
| 
      
 66 
     | 
    
         
            +
              #  * +new_resource+
         
     | 
| 
      
 67 
     | 
    
         
            +
              # 
         
     | 
| 
      
 68 
     | 
    
         
            +
              # Resources must respond to:
         
     | 
| 
      
 69 
     | 
    
         
            +
              #
         
     | 
| 
      
 70 
     | 
    
         
            +
              #  * +save+ (return true on success and false on failure)
         
     | 
| 
      
 71 
     | 
    
         
            +
              #  * +destroy+
         
     | 
| 
      
 72 
     | 
    
         
            +
              #  * +errors+ (returning a hash of error messages by attribute)
         
     | 
| 
      
 73 
     | 
    
         
            +
              #  * getters and setters for each attribute
         
     | 
| 
      
 74 
     | 
    
         
            +
              #
         
     | 
| 
       36 
75 
     | 
    
         
             
              def remote_model(*args)
         
     | 
| 
       37 
76 
     | 
    
         
             
                if args.any?
         
     | 
| 
       38 
77 
     | 
    
         
             
                  @remote_model = args.first
         
     | 
| 
       39 
78 
     | 
    
         | 
| 
       40 
     | 
    
         
            -
                   
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
      
 79 
     | 
    
         
            +
                  @__remotable_included ||= begin
         
     | 
| 
      
 80 
     | 
    
         
            +
                    require "remotable/active_record_extender"
         
     | 
| 
      
 81 
     | 
    
         
            +
                    include Remotable::ActiveRecordExtender
         
     | 
| 
      
 82 
     | 
    
         
            +
                    true
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
                  
         
     | 
| 
      
 85 
     | 
    
         
            +
                  extend_remote_model(@remote_model)
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
                @remote_model
         
     | 
| 
      
 88 
     | 
    
         
            +
              end
         
     | 
| 
      
 89 
     | 
    
         
            +
              
         
     | 
| 
      
 90 
     | 
    
         
            +
              
         
     | 
| 
      
 91 
     | 
    
         
            +
              
         
     | 
| 
      
 92 
     | 
    
         
            +
              def with_remote_model(model)
         
     | 
| 
      
 93 
     | 
    
         
            +
                if block_given?
         
     | 
| 
      
 94 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 95 
     | 
    
         
            +
                    original = self.remote_model
         
     | 
| 
      
 96 
     | 
    
         
            +
                    self.remote_model(model)
         
     | 
| 
      
 97 
     | 
    
         
            +
                    yield
         
     | 
| 
      
 98 
     | 
    
         
            +
                  ensure
         
     | 
| 
      
 99 
     | 
    
         
            +
                    self.remote_model(original)
         
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
       42 
101 
     | 
    
         
             
                else
         
     | 
| 
       43 
     | 
    
         
            -
                   
     | 
| 
      
 102 
     | 
    
         
            +
                  WithRemoteModelProxy.new(self, model)
         
     | 
| 
       44 
103 
     | 
    
         
             
                end
         
     | 
| 
       45 
104 
     | 
    
         
             
              end
         
     | 
| 
       46 
105 
     | 
    
         | 
| 
       47 
106 
     | 
    
         | 
| 
       48 
107 
     | 
    
         | 
| 
      
 108 
     | 
    
         
            +
              REQUIRED_CLASS_METHODS = [:find_by, :new_resource]
         
     | 
| 
      
 109 
     | 
    
         
            +
              REQUIRED_INSTANCE_METHODS = [:save, :errors, :destroy]
         
     | 
| 
      
 110 
     | 
    
         
            +
              
         
     | 
| 
      
 111 
     | 
    
         
            +
              class InvalidRemoteModel < ArgumentError; end
         
     | 
| 
      
 112 
     | 
    
         
            +
              
         
     | 
| 
      
 113 
     | 
    
         
            +
              
         
     | 
| 
      
 114 
     | 
    
         
            +
              
         
     | 
| 
      
 115 
     | 
    
         
            +
            private
         
     | 
| 
      
 116 
     | 
    
         
            +
              
         
     | 
| 
      
 117 
     | 
    
         
            +
              def extend_remote_model(remote_model)
         
     | 
| 
      
 118 
     | 
    
         
            +
                if remote_model.is_a?(Class) and (remote_model < ActiveResource::Base)
         
     | 
| 
      
 119 
     | 
    
         
            +
                  require "remotable/adapters/active_resource"
         
     | 
| 
      
 120 
     | 
    
         
            +
                  remote_model.send(:include, Remotable::Adapters::ActiveResource)
         
     | 
| 
      
 121 
     | 
    
         
            +
                
         
     | 
| 
      
 122 
     | 
    
         
            +
                #
         
     | 
| 
      
 123 
     | 
    
         
            +
                # Adapters for other API consumers can be implemented here
         
     | 
| 
      
 124 
     | 
    
         
            +
                #
         
     | 
| 
      
 125 
     | 
    
         
            +
                
         
     | 
| 
      
 126 
     | 
    
         
            +
                else
         
     | 
| 
      
 127 
     | 
    
         
            +
                  assert_that_remote_model_meets_api_requirements!(remote_model) if Remotable.validate_models?
         
     | 
| 
      
 128 
     | 
    
         
            +
                end
         
     | 
| 
      
 129 
     | 
    
         
            +
              end
         
     | 
| 
      
 130 
     | 
    
         
            +
              
         
     | 
| 
      
 131 
     | 
    
         
            +
              def assert_that_remote_model_meets_api_requirements!(model)
         
     | 
| 
      
 132 
     | 
    
         
            +
                unless model.respond_to_all?(REQUIRED_CLASS_METHODS)
         
     | 
| 
      
 133 
     | 
    
         
            +
                  raise InvalidRemoteModel,
         
     | 
| 
      
 134 
     | 
    
         
            +
                    "#{model} cannot be used as a remote model with Remotable " <<
         
     | 
| 
      
 135 
     | 
    
         
            +
                    "because it does not define these methods: #{model.does_not_respond_to(REQUIRED_CLASS_METHODS).join(", ")}."
         
     | 
| 
      
 136 
     | 
    
         
            +
                end
         
     | 
| 
      
 137 
     | 
    
         
            +
                instance = model.new_resource
         
     | 
| 
      
 138 
     | 
    
         
            +
                unless instance.respond_to_all?(REQUIRED_INSTANCE_METHODS)
         
     | 
| 
      
 139 
     | 
    
         
            +
                  raise InvalidRemoteModel,
         
     | 
| 
      
 140 
     | 
    
         
            +
                    "#{instance.class} cannot be used as a remote resource with Remotable " <<
         
     | 
| 
      
 141 
     | 
    
         
            +
                    "because it does not define these methods: #{instance.does_not_respond_to(REQUIRED_INSTANCE_METHODS).join(", ")}."
         
     | 
| 
      
 142 
     | 
    
         
            +
                end
         
     | 
| 
      
 143 
     | 
    
         
            +
              end
         
     | 
| 
      
 144 
     | 
    
         
            +
              
         
     | 
| 
       49 
145 
     | 
    
         
             
            end
         
     | 
| 
       50 
146 
     | 
    
         | 
| 
       51 
147 
     | 
    
         | 
| 
         @@ -1,5 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require "remotable/core_ext"
         
     | 
| 
       2 
2 
     | 
    
         
             
            require "active_support/concern"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "active_support/core_ext/array/wrap"
         
     | 
| 
       3 
4 
     | 
    
         | 
| 
       4 
5 
     | 
    
         | 
| 
       5 
6 
     | 
    
         
             
            module Remotable
         
     | 
| 
         @@ -22,12 +23,9 @@ module Remotable 
     | 
|
| 
       22 
23 
     | 
    
         | 
| 
       23 
24 
     | 
    
         
             
                  validates_presence_of :expires_at
         
     | 
| 
       24 
25 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
                   
     | 
| 
       26 
     | 
    
         
            -
                  @ 
     | 
| 
       27 
     | 
    
         
            -
                  @ 
     | 
| 
       28 
     | 
    
         
            -
                  @expires_after = 1.day
         
     | 
| 
       29 
     | 
    
         
            -
                  
         
     | 
| 
       30 
     | 
    
         
            -
                  extend_remote_model
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @remote_attribute_map ||= default_remote_attributes.map_to_self
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @local_attribute_routes ||= {}
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @expires_after ||= 1.day
         
     | 
| 
       31 
29 
     | 
    
         
             
                end
         
     | 
| 
       32 
30 
     | 
    
         | 
| 
       33 
31 
     | 
    
         | 
| 
         @@ -39,24 +37,44 @@ module Remotable 
     | 
|
| 
       39 
37 
     | 
    
         
             
                    Remotable.nosync? || super
         
     | 
| 
       40 
38 
     | 
    
         
             
                  end
         
     | 
| 
       41 
39 
     | 
    
         | 
| 
      
 40 
     | 
    
         
            +
                  # Sets the key with which a resource is identified remotely.
         
     | 
| 
      
 41 
     | 
    
         
            +
                  # If no remote key is set, the remote key is assumed to be :id.
         
     | 
| 
      
 42 
     | 
    
         
            +
                  # Which could be explicitly set like this:
         
     | 
| 
      
 43 
     | 
    
         
            +
                  #
         
     | 
| 
      
 44 
     | 
    
         
            +
                  #     remote_key :id
         
     | 
| 
      
 45 
     | 
    
         
            +
                  #
         
     | 
| 
      
 46 
     | 
    
         
            +
                  # It can can be a composite key:
         
     | 
| 
      
 47 
     | 
    
         
            +
                  #
         
     | 
| 
      
 48 
     | 
    
         
            +
                  #     remote_key [:calendar_id, :id]
         
     | 
| 
      
 49 
     | 
    
         
            +
                  #
         
     | 
| 
      
 50 
     | 
    
         
            +
                  # You can also supply a path for the remote key which will
         
     | 
| 
      
 51 
     | 
    
         
            +
                  # be passed to +fetch_with+:
         
     | 
| 
      
 52 
     | 
    
         
            +
                  #
         
     | 
| 
      
 53 
     | 
    
         
            +
                  #     remote_key [:calendar_id, :id], :path => "calendars/:calendar_id/events/:id"
         
     | 
| 
      
 54 
     | 
    
         
            +
                  #
         
     | 
| 
       42 
55 
     | 
    
         
             
                  def remote_key(*args)
         
     | 
| 
       43 
56 
     | 
    
         
             
                    if args.any?
         
     | 
| 
       44 
     | 
    
         
            -
                      remote_key = args. 
     | 
| 
       45 
     | 
    
         
            -
                       
     | 
| 
      
 57 
     | 
    
         
            +
                      remote_key = args.shift
         
     | 
| 
      
 58 
     | 
    
         
            +
                      options = args.shift || {}
         
     | 
| 
      
 59 
     | 
    
         
            +
                      
         
     | 
| 
      
 60 
     | 
    
         
            +
                      # remote_key may be a composite of several attributes
         
     | 
| 
      
 61 
     | 
    
         
            +
                      # ensure that all of the attributs have been defined
         
     | 
| 
      
 62 
     | 
    
         
            +
                      Array.wrap(remote_key).each do |attribute|
         
     | 
| 
      
 63 
     | 
    
         
            +
                        raise(":#{attribute} is not the name of a remote attribute") unless remote_attribute_names.member?(attribute)
         
     | 
| 
      
 64 
     | 
    
         
            +
                      end
         
     | 
| 
      
 65 
     | 
    
         
            +
                      
         
     | 
| 
      
 66 
     | 
    
         
            +
                      # Set up a finder method for the remote_key
         
     | 
| 
      
 67 
     | 
    
         
            +
                      fetch_with(local_key(remote_key), options)
         
     | 
| 
      
 68 
     | 
    
         
            +
                      
         
     | 
| 
       46 
69 
     | 
    
         
             
                      @remote_key = remote_key
         
     | 
| 
       47 
     | 
    
         
            -
                      fetch_with(remote_key)
         
     | 
| 
       48 
     | 
    
         
            -
                      remote_key
         
     | 
| 
       49 
70 
     | 
    
         
             
                    else
         
     | 
| 
       50 
71 
     | 
    
         
             
                      @remote_key || generate_default_remote_key
         
     | 
| 
       51 
72 
     | 
    
         
             
                    end
         
     | 
| 
       52 
73 
     | 
    
         
             
                  end
         
     | 
| 
       53 
74 
     | 
    
         | 
| 
       54 
75 
     | 
    
         
             
                  def expires_after(*args)
         
     | 
| 
       55 
     | 
    
         
            -
                    if args.any?
         
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
                    else
         
     | 
| 
       58 
     | 
    
         
            -
                      @expires_after
         
     | 
| 
       59 
     | 
    
         
            -
                    end
         
     | 
| 
      
 76 
     | 
    
         
            +
                    @expires_after = args.first if args.any?
         
     | 
| 
      
 77 
     | 
    
         
            +
                    @expires_after
         
     | 
| 
       60 
78 
     | 
    
         
             
                  end
         
     | 
| 
       61 
79 
     | 
    
         | 
| 
       62 
80 
     | 
    
         
             
                  def attr_remote(*attrs)
         
     | 
| 
         @@ -64,23 +82,29 @@ module Remotable 
     | 
|
| 
       64 
82 
     | 
    
         
             
                    map = attrs.map_to_self.merge(map)
         
     | 
| 
       65 
83 
     | 
    
         
             
                    @remote_attribute_map = map
         
     | 
| 
       66 
84 
     | 
    
         | 
| 
       67 
     | 
    
         
            -
                     
     | 
| 
       68 
     | 
    
         
            -
                     
     | 
| 
      
 85 
     | 
    
         
            +
                    assert_that_remote_resource_responds_to_remote_attributes!(remote_model) if Remotable.validate_models?
         
     | 
| 
      
 86 
     | 
    
         
            +
                    
         
     | 
| 
      
 87 
     | 
    
         
            +
                    # Reset routes
         
     | 
| 
      
 88 
     | 
    
         
            +
                    @local_attribute_routes = {}
         
     | 
| 
       69 
89 
     | 
    
         
             
                  end
         
     | 
| 
       70 
90 
     | 
    
         | 
| 
       71 
     | 
    
         
            -
                  def fetch_with( 
     | 
| 
       72 
     | 
    
         
            -
                     
     | 
| 
       73 
     | 
    
         
            -
                    @remote_attribute_routes.merge!(remote_keys_and_routes)
         
     | 
| 
      
 91 
     | 
    
         
            +
                  def fetch_with(local_key, options={})
         
     | 
| 
      
 92 
     | 
    
         
            +
                    @local_attribute_routes.merge!(local_key => options[:path])
         
     | 
| 
       74 
93 
     | 
    
         
             
                  end
         
     | 
| 
       75 
94 
     | 
    
         
             
                  alias :find_by :fetch_with
         
     | 
| 
       76 
95 
     | 
    
         | 
| 
       77 
96 
     | 
    
         | 
| 
       78 
97 
     | 
    
         | 
| 
       79 
98 
     | 
    
         
             
                  attr_reader :remote_attribute_map,
         
     | 
| 
       80 
     | 
    
         
            -
                              : 
     | 
| 
      
 99 
     | 
    
         
            +
                              :local_attribute_routes
         
     | 
| 
       81 
100 
     | 
    
         | 
| 
       82 
     | 
    
         
            -
                  def local_key
         
     | 
| 
       83 
     | 
    
         
            -
                     
     | 
| 
      
 101 
     | 
    
         
            +
                  def local_key(remote_key=nil)
         
     | 
| 
      
 102 
     | 
    
         
            +
                    remote_key ||= self.remote_key
         
     | 
| 
      
 103 
     | 
    
         
            +
                    if remote_key.is_a?(Array)
         
     | 
| 
      
 104 
     | 
    
         
            +
                      remote_key.map(&method(:local_attribute_name))
         
     | 
| 
      
 105 
     | 
    
         
            +
                    else
         
     | 
| 
      
 106 
     | 
    
         
            +
                      local_attribute_name(remote_key)
         
     | 
| 
      
 107 
     | 
    
         
            +
                    end
         
     | 
| 
       84 
108 
     | 
    
         
             
                  end
         
     | 
| 
       85 
109 
     | 
    
         | 
| 
       86 
110 
     | 
    
         
             
                  def remote_attribute_names
         
     | 
| 
         @@ -99,17 +123,18 @@ module Remotable 
     | 
|
| 
       99 
123 
     | 
    
         
             
                    remote_attribute_map[remote_attr] || remote_attr
         
     | 
| 
       100 
124 
     | 
    
         
             
                  end
         
     | 
| 
       101 
125 
     | 
    
         | 
| 
       102 
     | 
    
         
            -
                  def route_for( 
     | 
| 
       103 
     | 
    
         
            -
                     
     | 
| 
       104 
     | 
    
         
            -
                     
     | 
| 
      
 126 
     | 
    
         
            +
                  def route_for(remote_key)
         
     | 
| 
      
 127 
     | 
    
         
            +
                    local_key = self.local_key(remote_key)
         
     | 
| 
      
 128 
     | 
    
         
            +
                    local_attribute_routes[local_key] || default_route_for(local_key, remote_key)
         
     | 
| 
       105 
129 
     | 
    
         
             
                  end
         
     | 
| 
       106 
130 
     | 
    
         | 
| 
       107 
131 
     | 
    
         
             
                  def default_route_for(local_key, remote_key=nil)
         
     | 
| 
      
 132 
     | 
    
         
            +
                    puts "local_key: #{local_key}; remote_key: #{remote_key}"
         
     | 
| 
       108 
133 
     | 
    
         
             
                    remote_key ||= remote_attribute_name(local_key)
         
     | 
| 
       109 
134 
     | 
    
         
             
                    if remote_key.to_s == primary_key
         
     | 
| 
       110 
135 
     | 
    
         
             
                      ":#{local_key}"
         
     | 
| 
       111 
136 
     | 
    
         
             
                    else
         
     | 
| 
       112 
     | 
    
         
            -
                      "by_#{ 
     | 
| 
      
 137 
     | 
    
         
            +
                      "by_#{local_key}/:#{local_key}"
         
     | 
| 
       113 
138 
     | 
    
         
             
                    end
         
     | 
| 
       114 
139 
     | 
    
         
             
                  end
         
     | 
| 
       115 
140 
     | 
    
         | 
| 
         @@ -128,26 +153,55 @@ module Remotable 
     | 
|
| 
       128 
153 
     | 
    
         | 
| 
       129 
154 
     | 
    
         | 
| 
       130 
155 
     | 
    
         | 
| 
      
 156 
     | 
    
         
            +
                  def respond_to?(method_sym, include_private=false)
         
     | 
| 
      
 157 
     | 
    
         
            +
                    return true if recognize_remote_finder_method(method_sym)
         
     | 
| 
      
 158 
     | 
    
         
            +
                    super(method_sym, include_private)
         
     | 
| 
      
 159 
     | 
    
         
            +
                  end
         
     | 
| 
      
 160 
     | 
    
         
            +
                  
         
     | 
| 
       131 
161 
     | 
    
         
             
                  def method_missing(method_sym, *args, &block)
         
     | 
| 
       132 
     | 
    
         
            -
                     
     | 
| 
       133 
     | 
    
         
            -
                    
         
     | 
| 
       134 
     | 
    
         
            -
             
     | 
| 
       135 
     | 
    
         
            -
                       
     | 
| 
       136 
     | 
    
         
            -
                      remote_attr = remote_attribute_name(local_attr)
         
     | 
| 
       137 
     | 
    
         
            -
                      
         
     | 
| 
       138 
     | 
    
         
            -
                      remote_key # Make sure we've figured out the remote
         
     | 
| 
       139 
     | 
    
         
            -
                                 # primary key if we're evaluating a finder
         
     | 
| 
      
 162 
     | 
    
         
            +
                    method_details = recognize_remote_finder_method(method_sym)
         
     | 
| 
      
 163 
     | 
    
         
            +
                    if method_details
         
     | 
| 
      
 164 
     | 
    
         
            +
                      local_attributes = method_details[:local_attributes]
         
     | 
| 
      
 165 
     | 
    
         
            +
                      values = args
         
     | 
| 
       140 
166 
     | 
    
         | 
| 
       141 
     | 
    
         
            -
                       
     | 
| 
       142 
     | 
    
         
            -
                         
     | 
| 
       143 
     | 
    
         
            -
                                         fetch_by(remote_attr, value)
         
     | 
| 
       144 
     | 
    
         
            -
                        
         
     | 
| 
       145 
     | 
    
         
            -
                        raise ActiveRecord::RecordNotFound if local_resource.nil? && bang
         
     | 
| 
       146 
     | 
    
         
            -
                        return local_resource
         
     | 
| 
      
 167 
     | 
    
         
            +
                      unless values.length == local_attributes.length
         
     | 
| 
      
 168 
     | 
    
         
            +
                        raise ArgumentError, "#{method_sym} was called with #{values.length} but #{local_attributes.length} was expected"
         
     | 
| 
       147 
169 
     | 
    
         
             
                      end
         
     | 
| 
      
 170 
     | 
    
         
            +
                      
         
     | 
| 
      
 171 
     | 
    
         
            +
                      local_resource = ((0...local_attributes.length).inject(scoped) do |scope, i|
         
     | 
| 
      
 172 
     | 
    
         
            +
                        scope.where(local_attributes[i] => values[i])
         
     | 
| 
      
 173 
     | 
    
         
            +
                      end).first || fetch_by(method_details[:remote_key], *values)
         
     | 
| 
      
 174 
     | 
    
         
            +
                      
         
     | 
| 
      
 175 
     | 
    
         
            +
                      raise ActiveRecord::RecordNotFound if local_resource.nil? && (method_sym =~ /!$/)
         
     | 
| 
      
 176 
     | 
    
         
            +
                      local_resource
         
     | 
| 
      
 177 
     | 
    
         
            +
                    else
         
     | 
| 
      
 178 
     | 
    
         
            +
                      super(method_sym, *args, &block)
         
     | 
| 
      
 179 
     | 
    
         
            +
                    end
         
     | 
| 
      
 180 
     | 
    
         
            +
                  end
         
     | 
| 
      
 181 
     | 
    
         
            +
                  
         
     | 
| 
      
 182 
     | 
    
         
            +
                  # If the missing method IS a Remotable finder method,
         
     | 
| 
      
 183 
     | 
    
         
            +
                  # returns the remote key (may be a composite key).
         
     | 
| 
      
 184 
     | 
    
         
            +
                  # Otherwise, returns false.
         
     | 
| 
      
 185 
     | 
    
         
            +
                  def recognize_remote_finder_method(method_sym)
         
     | 
| 
      
 186 
     | 
    
         
            +
                    method_name = method_sym.to_s
         
     | 
| 
      
 187 
     | 
    
         
            +
                    return false unless method_name =~ /find_by_([^!]*)(!?)/
         
     | 
| 
      
 188 
     | 
    
         
            +
                    
         
     | 
| 
      
 189 
     | 
    
         
            +
                    local_attributes = $1.split("_and_").map(&:to_sym)
         
     | 
| 
      
 190 
     | 
    
         
            +
                    remote_attributes = local_attributes.map(&method(:remote_attribute_name))
         
     | 
| 
      
 191 
     | 
    
         
            +
                    
         
     | 
| 
      
 192 
     | 
    
         
            +
                    local_key, remote_key = if local_attributes.length == 1
         
     | 
| 
      
 193 
     | 
    
         
            +
                      [local_attributes[0], remote_attributes[0]]
         
     | 
| 
      
 194 
     | 
    
         
            +
                    else
         
     | 
| 
      
 195 
     | 
    
         
            +
                      [local_attributes, remote_attributes]
         
     | 
| 
       148 
196 
     | 
    
         
             
                    end
         
     | 
| 
       149 
197 
     | 
    
         | 
| 
       150 
     | 
    
         
            -
                     
     | 
| 
      
 198 
     | 
    
         
            +
                    generate_default_remote_key # <- Make sure we've figured out the remote
         
     | 
| 
      
 199 
     | 
    
         
            +
                                                #    primary key if we're evaluating a finder
         
     | 
| 
      
 200 
     | 
    
         
            +
                    
         
     | 
| 
      
 201 
     | 
    
         
            +
                    return false unless local_attribute_routes.key?(local_key)
         
     | 
| 
      
 202 
     | 
    
         
            +
                    
         
     | 
| 
      
 203 
     | 
    
         
            +
                    { :local_attributes => local_attributes,
         
     | 
| 
      
 204 
     | 
    
         
            +
                      :remote_key => remote_key }
         
     | 
| 
       151 
205 
     | 
    
         
             
                  end
         
     | 
| 
       152 
206 
     | 
    
         | 
| 
       153 
207 
     | 
    
         | 
| 
         @@ -161,56 +215,90 @@ module Remotable 
     | 
|
| 
       161 
215 
     | 
    
         
             
                  # Looks the resource up remotely, by the given attribute
         
     | 
| 
       162 
216 
     | 
    
         
             
                  # If the resource is found, wraps it in a new local resource
         
     | 
| 
       163 
217 
     | 
    
         
             
                  # and returns that.
         
     | 
| 
       164 
     | 
    
         
            -
                  def fetch_by(remote_attr,  
     | 
| 
       165 
     | 
    
         
            -
                    remote_resource =  
     | 
| 
      
 218 
     | 
    
         
            +
                  def fetch_by(remote_attr, *values)
         
     | 
| 
      
 219 
     | 
    
         
            +
                    remote_resource = find_remote_resource_by(remote_attr, *values)
         
     | 
| 
       166 
220 
     | 
    
         
             
                    remote_resource && new_from_remote(remote_resource)
         
     | 
| 
       167 
221 
     | 
    
         
             
                  end
         
     | 
| 
       168 
222 
     | 
    
         | 
| 
      
 223 
     | 
    
         
            +
                  # Looks the resource up remotely;
         
     | 
| 
      
 224 
     | 
    
         
            +
                  # Returns the remote resource.
         
     | 
| 
      
 225 
     | 
    
         
            +
                  def find_remote_resource_by(remote_attr, *values)
         
     | 
| 
      
 226 
     | 
    
         
            +
                    find_by = remote_model.method(:find_by)
         
     | 
| 
      
 227 
     | 
    
         
            +
                    case find_by.arity
         
     | 
| 
      
 228 
     | 
    
         
            +
                    when 1; find_by.call(remote_path_for(remote_attr, *values))
         
     | 
| 
      
 229 
     | 
    
         
            +
                    when 2; find_by.call(remote_attr, *values)
         
     | 
| 
      
 230 
     | 
    
         
            +
                    else
         
     | 
| 
      
 231 
     | 
    
         
            +
                      raise InvalidRemoteModel, "#{remote_model}.find_by should take either 1 or 2 parameters"
         
     | 
| 
      
 232 
     | 
    
         
            +
                    end
         
     | 
| 
      
 233 
     | 
    
         
            +
                  end
         
     | 
| 
      
 234 
     | 
    
         
            +
                  
         
     | 
| 
      
 235 
     | 
    
         
            +
                  def remote_path_for(remote_key, *values)
         
     | 
| 
      
 236 
     | 
    
         
            +
                    route = route_for(remote_key)
         
     | 
| 
      
 237 
     | 
    
         
            +
                    local_key = self.local_key(remote_key)
         
     | 
| 
      
 238 
     | 
    
         
            +
                    
         
     | 
| 
      
 239 
     | 
    
         
            +
                    if remote_key.is_a?(Array)
         
     | 
| 
      
 240 
     | 
    
         
            +
                      remote_path_for_composite_key(route, local_key, values)
         
     | 
| 
      
 241 
     | 
    
         
            +
                    else
         
     | 
| 
      
 242 
     | 
    
         
            +
                      remote_path_for_simple_key(route, local_key, values.first)
         
     | 
| 
      
 243 
     | 
    
         
            +
                    end
         
     | 
| 
      
 244 
     | 
    
         
            +
                  end
         
     | 
| 
      
 245 
     | 
    
         
            +
                  
         
     | 
| 
      
 246 
     | 
    
         
            +
                  def remote_path_for_simple_key(route, local_key, value)
         
     | 
| 
      
 247 
     | 
    
         
            +
                    route.gsub(/:#{local_key}/, value.to_s)
         
     | 
| 
      
 248 
     | 
    
         
            +
                  end
         
     | 
| 
      
 249 
     | 
    
         
            +
                  
         
     | 
| 
      
 250 
     | 
    
         
            +
                  def remote_path_for_composite_key(route, local_key, values)
         
     | 
| 
      
 251 
     | 
    
         
            +
                    unless values.length == local_key.length
         
     | 
| 
      
 252 
     | 
    
         
            +
                      raise ArgumentError, "local_key has #{local_key.length} attributes but values has #{values.length}"
         
     | 
| 
      
 253 
     | 
    
         
            +
                    end
         
     | 
| 
      
 254 
     | 
    
         
            +
                    
         
     | 
| 
      
 255 
     | 
    
         
            +
                    (0...values.length).inject(route) do |route, i|
         
     | 
| 
      
 256 
     | 
    
         
            +
                      route.gsub(/:#{local_key[i]}/, values[i].to_s)
         
     | 
| 
      
 257 
     | 
    
         
            +
                    end
         
     | 
| 
      
 258 
     | 
    
         
            +
                  end
         
     | 
| 
      
 259 
     | 
    
         
            +
                  
         
     | 
| 
       169 
260 
     | 
    
         | 
| 
       170 
261 
     | 
    
         | 
| 
       171 
262 
     | 
    
         
             
                private
         
     | 
| 
       172 
263 
     | 
    
         | 
| 
       173 
264 
     | 
    
         | 
| 
       174 
265 
     | 
    
         | 
| 
       175 
     | 
    
         
            -
                  def  
     | 
| 
       176 
     | 
    
         
            -
                     
     | 
| 
       177 
     | 
    
         
            -
                      require "remotable/adapters/active_resource"
         
     | 
| 
       178 
     | 
    
         
            -
                      remote_model.send(:include, Remotable::Adapters::ActiveResource)
         
     | 
| 
       179 
     | 
    
         
            -
                      remote_model.local_model = self
         
     | 
| 
       180 
     | 
    
         
            -
                    
         
     | 
| 
       181 
     | 
    
         
            -
                    # !todo
         
     | 
| 
       182 
     | 
    
         
            -
                    # Adapters for other API consumers can be implemented here
         
     | 
| 
       183 
     | 
    
         
            -
                    #
         
     | 
| 
       184 
     | 
    
         
            -
                    
         
     | 
| 
       185 
     | 
    
         
            -
                    else
         
     | 
| 
       186 
     | 
    
         
            -
                      raise("#{remote_model} is not a recognized remote resource")
         
     | 
| 
       187 
     | 
    
         
            -
                    end
         
     | 
| 
      
 266 
     | 
    
         
            +
                  def default_remote_attributes
         
     | 
| 
      
 267 
     | 
    
         
            +
                    column_names - %w{id created_at updated_at expires_at}
         
     | 
| 
       188 
268 
     | 
    
         
             
                  end
         
     | 
| 
       189 
269 
     | 
    
         | 
| 
       190 
270 
     | 
    
         | 
| 
       191 
     | 
    
         
            -
                   
     | 
| 
       192 
     | 
    
         
            -
             
     | 
| 
       193 
     | 
    
         
            -
                     
     | 
| 
       194 
     | 
    
         
            -
             
     | 
| 
       195 
     | 
    
         
            -
             
     | 
| 
      
 271 
     | 
    
         
            +
                  
         
     | 
| 
      
 272 
     | 
    
         
            +
                  def assert_that_remote_resource_responds_to_remote_attributes!(model)
         
     | 
| 
      
 273 
     | 
    
         
            +
                    # Skip this for ActiveResource because it won't define a method until it has
         
     | 
| 
      
 274 
     | 
    
         
            +
                    # loaded an JSON for that method
         
     | 
| 
      
 275 
     | 
    
         
            +
                    return if model.is_a?(Class) and (model < ActiveResource::Base) 
         
     | 
| 
      
 276 
     | 
    
         
            +
                    
         
     | 
| 
      
 277 
     | 
    
         
            +
                    instance = model.new_resource
         
     | 
| 
      
 278 
     | 
    
         
            +
                    attr_getters_and_setters = remote_attribute_names + remote_attribute_names.map {|attr| :"#{attr}="}
         
     | 
| 
      
 279 
     | 
    
         
            +
                    unless instance.respond_to_all?(attr_getters_and_setters)
         
     | 
| 
      
 280 
     | 
    
         
            +
                      raise InvalidRemoteModel,
         
     | 
| 
      
 281 
     | 
    
         
            +
                        "#{instance.class} does not respond to getters and setters " <<
         
     | 
| 
      
 282 
     | 
    
         
            +
                        "for each remote attribute (not implemented: #{instance.does_not_respond_to(attr_getters_and_setters).sort.join(", ")}).\n"
         
     | 
| 
       196 
283 
     | 
    
         
             
                    end
         
     | 
| 
       197 
284 
     | 
    
         
             
                  end
         
     | 
| 
       198 
285 
     | 
    
         | 
| 
       199 
286 
     | 
    
         | 
| 
      
 287 
     | 
    
         
            +
                  
         
     | 
| 
       200 
288 
     | 
    
         
             
                  def generate_default_remote_key
         
     | 
| 
      
 289 
     | 
    
         
            +
                    return @remote_key if @remote_key
         
     | 
| 
       201 
290 
     | 
    
         
             
                    raise("No remote key supplied and :id is not a remote attribute") unless remote_attribute_names.member?(:id)
         
     | 
| 
       202 
291 
     | 
    
         
             
                    remote_key(:id)
         
     | 
| 
       203 
292 
     | 
    
         
             
                  end
         
     | 
| 
       204 
293 
     | 
    
         | 
| 
       205 
294 
     | 
    
         | 
| 
      
 295 
     | 
    
         
            +
                  
         
     | 
| 
       206 
296 
     | 
    
         
             
                  def new_from_remote(remote_resource)
         
     | 
| 
       207 
297 
     | 
    
         
             
                    record = self.new
         
     | 
| 
       208 
298 
     | 
    
         
             
                    record.instance_variable_set(:@remote_resource, remote_resource)
         
     | 
| 
       209 
299 
     | 
    
         
             
                    record.pull_remote_data!
         
     | 
| 
       210 
300 
     | 
    
         
             
                  end
         
     | 
| 
       211 
301 
     | 
    
         | 
| 
       212 
     | 
    
         
            -
                  
         
     | 
| 
       213 
     | 
    
         
            -
                  
         
     | 
| 
       214 
302 
     | 
    
         
             
                end
         
     | 
| 
       215 
303 
     | 
    
         | 
| 
       216 
304 
     | 
    
         | 
| 
         @@ -224,6 +312,7 @@ module Remotable 
     | 
|
| 
       224 
312 
     | 
    
         
             
                         :local_attribute_names,
         
     | 
| 
       225 
313 
     | 
    
         
             
                         :local_attribute_name,
         
     | 
| 
       226 
314 
     | 
    
         
             
                         :expires_after,
         
     | 
| 
      
 315 
     | 
    
         
            +
                         :find_remote_resource_by,
         
     | 
| 
       227 
316 
     | 
    
         
             
                         :to => "self.class"
         
     | 
| 
       228 
317 
     | 
    
         | 
| 
       229 
318 
     | 
    
         
             
                def expired?
         
     | 
| 
         @@ -256,7 +345,10 @@ module Remotable 
     | 
|
| 
       256 
345 
     | 
    
         | 
| 
       257 
346 
     | 
    
         
             
                def fetch_remote_resource
         
     | 
| 
       258 
347 
     | 
    
         
             
                  fetch_value = self[local_key]
         
     | 
| 
       259 
     | 
    
         
            -
                   
     | 
| 
      
 348 
     | 
    
         
            +
                  # puts "local_key", local_key.inspect, "",
         
     | 
| 
      
 349 
     | 
    
         
            +
                  #      "remote_key", remote_key.inspect, "",
         
     | 
| 
      
 350 
     | 
    
         
            +
                  #      "fetch_value", fetch_value
         
     | 
| 
      
 351 
     | 
    
         
            +
                  find_remote_resource_by(remote_key, fetch_value)
         
     | 
| 
       260 
352 
     | 
    
         
             
                end
         
     | 
| 
       261 
353 
     | 
    
         | 
| 
       262 
354 
     | 
    
         
             
                def merge_remote_data!(remote_resource)
         
     | 
| 
         @@ -285,7 +377,7 @@ module Remotable 
     | 
|
| 
       285 
377 
     | 
    
         
             
                end
         
     | 
| 
       286 
378 
     | 
    
         | 
| 
       287 
379 
     | 
    
         
             
                def create_remote_resource
         
     | 
| 
       288 
     | 
    
         
            -
                  @remote_resource = remote_model. 
     | 
| 
      
 380 
     | 
    
         
            +
                  @remote_resource = remote_model.new_resource
         
     | 
| 
       289 
381 
     | 
    
         
             
                  merge_local_data(@remote_resource)
         
     | 
| 
       290 
382 
     | 
    
         | 
| 
       291 
383 
     | 
    
         
             
                  if @remote_resource.save
         
     | 
| 
         @@ -323,8 +415,10 @@ module Remotable 
     | 
|
| 
       323 
415 
     | 
    
         | 
| 
       324 
416 
     | 
    
         | 
| 
       325 
417 
     | 
    
         
             
                def merge_remote_errors(errors)
         
     | 
| 
       326 
     | 
    
         
            -
                  errors.each do |attribute,  
     | 
| 
       327 
     | 
    
         
            -
                     
     | 
| 
      
 418 
     | 
    
         
            +
                  errors.each do |attribute, messages|
         
     | 
| 
      
 419 
     | 
    
         
            +
                    Array.wrap(messages).each do |message|
         
     | 
| 
      
 420 
     | 
    
         
            +
                      self.errors.add(local_attribute_name(attribute), message)
         
     | 
| 
      
 421 
     | 
    
         
            +
                    end
         
     | 
| 
       328 
422 
     | 
    
         
             
                  end
         
     | 
| 
       329 
423 
     | 
    
         
             
                  self
         
     | 
| 
       330 
424 
     | 
    
         
             
                end
         
     |