Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: ff0163c25f
Fetching contributors…

Cannot retrieve contributors at this time

file 218 lines (186 sloc) 7.086 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
module RemoteModule
  class RemoteModel
    HTTP_METHODS = [:get, :post, :put, :delete]

    class << self
      # These three methods (has_one/many/ + belongs_to)
      # map a symbol to a class for method_missing lookup
      # for each :symbol in params.
      # Can also be used to view the current mappings:
      # EX
      # Question.has_one
      # => {:user => User}

      # EX
      # self.has_one :question, :answer, :camel_case
      # => {:question => Question, :answer => Answer, :camel_case => CamelCase}
      def has_one(params = [])
        make_fn_lookup "has_one", params, singular_klass_str_lambda
      end

      # EX
      # self.has_many :questions, :answers, :camel_cases
      # => {:questions => Question, :answers => Answer, :camel_cases => CamelCase}
      def has_many(params = [])
        make_fn_lookup "has_many", params, lambda { |sym| sym.to_s.singularize.split("_").collect {|s| s.capitalize}.join }
      end

      # EX
      # self.belongs_to :question, :answer, :camel_case
      # => {:question => Question, :answer => Answer, :camel_case => CamelCase}
      def belongs_to(params = [])
        make_fn_lookup "belongs_to", params, singular_klass_str_lambda
      end

      def pluralize
        self.to_s.pluralize
      end

      def method_missing(method, *args, &block)
        if self.custom_urls.has_key? method
          return self.custom_urls[method].format(args && args[0], self)
        end

        super
      end

      private
      # This is kind of neat.
      # Because models can be mutually dependent (User has a Question, Question has a User),
      # sometimes RubyMotion hasn't loaded the classes when this is run.
      # SO we check to see if the class is loaded; if not, then we just add it to the
      # namespace to make everything run smoothly and assume that by the time the app is running,
      # all the classes have been loaded.
      def make_klass(klass_str)
        begin
          klass = Object.const_get(klass_str)
        rescue NameError => e
          klass = Object.const_set(klass_str, Class.new(RemoteModule::RemoteModel))
        end
      end

      def singular_klass_str_lambda
        lambda { |sym| sym.to_s.split("_").collect {|s| s.capitalize}.join }
      end

      # How we fake define_method, essentially.
      # ivar_suffix -> what is the new @ivar called
      # params -> the :symbols to map to classes
      # transform -> how we transform the :symbol into a class name
      def make_fn_lookup(ivar_suffix, params, transform)
        ivar = "@" + ivar_suffix
        if !instance_variable_defined? ivar
          instance_variable_set(ivar, {})
        end
        
        sym_to_klass_sym = {}
        if params.class == Symbol
          sym_to_klass_sym[params] = transform.call(params)
        elsif params.class == Array
          params.each {|klass_sym|
            sym_to_klass_sym[klass_sym] = transform.call(klass_sym)
          }
        else
          params.each { |fn_sym, klass_sym| params[fn_sym] = singular_klass_str_lambda.call(klass_sym) }
          sym_to_klass_sym = params
        end

        sym_to_klass_sym.each do |relation_sym, klass_sym|
            klass_str = klass_sym.to_s
            instance_variable_get(ivar)[relation_sym] = make_klass(klass_str)
          end

        instance_variable_get(ivar)
      end
    end

    def initialize(params = {})
      update_attributes(params)
    end

    def update_attributes(params = {})
      attributes = self.methods - Object.methods
      params.each do |key, value|
        if attributes.member?((key.to_s + "=:").to_sym)
          self.send((key.to_s + "=:").to_sym, value)
        end
      end
    end

    def remote_model_methods
      methods = []
      [self.class.has_one, self.class.has_many, self.class.belongs_to].each {|fn_hash|
        methods += fn_hash.collect {|sym, klass|
          [sym, (sym.to_s + "=:").to_sym, ("set" + sym.to_s.capitalize).to_sym]
        }.flatten
      }
      methods + RemoteModule::RemoteModel::HTTP_METHODS
    end

    def methods
      super + remote_model_methods
    end

    def respond_to?(symbol, include_private = false)
      if remote_model_methods.include? symbol
        return true
      end

      super
    end

    def method_missing(method, *args, &block)
      # Check for custom URLs
      if self.class.custom_urls.has_key? method
        return self.class.custom_urls[method].format(args && args[0], self)
      end

      # has_one relationships
      if self.class.has_one.has_key?(method) || self.class.belongs_to.has_key?(method)
        return instance_variable_get("@" + method.to_s)
      elsif (setter_vals = setter_klass(self.class.has_one, method) || setter_vals = setter_klass(self.class.belongs_to, method))
        klass, hash_symbol = setter_vals
        obj = args[0]
        if obj.class != klass
          obj = klass.new(obj)
        end
        return instance_variable_set("@" + hash_symbol.to_s, obj)
      end

      # has_many relationships
      if self.class.has_many.has_key?(method)
        ivar = "@" + method.to_s
        if !instance_variable_defined? ivar
          instance_variable_set(ivar, [])
        end
        return instance_variable_get ivar
      elsif (setter_vals = setter_klass(self.class.has_many, method))
        klass, hash_symbol = setter_vals
        ivar = "@" + hash_symbol.to_s

        tmp = []
        args[0].each do |arg|
          rep = nil
          if arg.class == Hash
            rep = klass.new(arg)
          elsif arg.class == klass
            rep = arg
          end

          if rep.class.belongs_to.values.member? self.class
            rep.send((rep.class.belongs_to.invert[self.class].to_s + "=").to_sym, self)
          end

          tmp << rep
        end

        instance_variable_set(ivar, tmp)
        return instance_variable_get(ivar)
      end

      # HTTP methods
      if RemoteModule::RemoteModel::HTTP_METHODS.member? method
        return self.class.send(method, *args, &block)
      end

      super
    end

    private
    # PARAMS For a given method symbol, look through the hash
    # (which is a map of :symbol => Class)
    # and see if that symbol applies to any keys.
    # RETURNS an array [Klass, symbol] for which the original
    # method symbol applies.
    # EX
    # setter_klass({:answers => Answer}, :answers=)
    # => [Answer, :answers]
    # setter_klass({:answers => Answer}, :setAnswers)
    # => [Answer, :answers]
    def setter_klass(hash, symbol)

      # go ahead and guess it's of the form :symbol=:
      hash_symbol = symbol.to_s[0..-2].to_sym

      # if it's the ObjC style setSymbol, change it to that.
      if symbol[0..2] == "set"
        # handles camel case arguments. ex setSomeVariableLikeThis => some_variable_like_this
        hash_symbol = symbol.to_s[3..-1].split(/([[:upper:]][[:lower:]]*)/).delete_if(&:empty?).map(&:downcase).join("_").to_sym
      end

      klass = hash[hash_symbol]
      if klass.nil?
        return nil
      end
      [klass, hash_symbol]
    end
  end
end
Something went wrong with that request. Please try again.