Skip to content
This repository
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 433 lines (367 sloc) 13.118 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 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
module SimpleRecord
  module Attributes
# For all things related to defining attributes.


    def self.included(base)
      #puts 'Callbacks included in ' + base.inspect
=begin
instance_eval <<-endofeval

def self.defined_attributes
#puts 'class defined_attributes'
@attributes ||= {}
@attributes
endendofeval
endofeval
=end

    end


    module ClassMethods


      # Add configuration to this particular class.
      # :single_clob=> true/false. If true will store all clobs as a single object in s3. Default is false.
      def sr_config(options={})
        get_sr_config
        @sr_config.merge!(options)
      end

      def get_sr_config
        @sr_config ||= {}
      end

      def defined_attributes
        @attributes ||= {}
        @attributes
      end

      def has_attributes(*args)
        has_attributes2(args)
      end

      def has_attributes2(args, options_for_all={})
# puts 'args=' + args.inspect
# puts 'options_for_all = ' + options_for_all.inspect
        args.each do |arg|
          arg_options = {}
          if arg.is_a?(Hash)
            # then attribute may have extra options
            arg_options = arg
            arg = arg_options[:name].to_sym
          else
            arg = arg.to_sym
          end
          type = options_for_all[:type] || :string
          attr = Attribute.new(type, arg_options)
          defined_attributes[arg] = attr if defined_attributes[arg].nil?

          # define reader method
          arg_s = arg.to_s # to get rid of all the to_s calls
          send(:define_method, arg) do
            ret = get_attribute(arg)
            return ret
          end

          # define writer method
          send(:define_method, arg_s+"=") do |value|
            set(arg, value)
          end

          define_dirty_methods(arg_s)
        end
      end

      def define_dirty_methods(arg_s)
        # Now for dirty methods: http://api.rubyonrails.org/classes/ActiveRecord/Dirty.html
        # define changed? method
        send(:define_method, arg_s + "_changed?") do
          @dirty.has_key?(sdb_att_name(arg_s))
        end

        # define change method
        send(:define_method, arg_s + "_change") do
          old_val = @dirty[sdb_att_name(arg_s)]
          [old_val, get_attribute(arg_s)]
        end

        # define was method
        send(:define_method, arg_s + "_was") do
          old_val = @dirty[sdb_att_name(arg_s)]
          old_val
        end
      end

      def has_strings(*args)
        has_attributes(*args)
      end

      def has_ints(*args)
        has_attributes(*args)
        are_ints(*args)
      end

      def has_floats(*args)
        has_attributes(*args)
        are_floats(*args)
      end

      def has_dates(*args)
        has_attributes(*args)
        are_dates(*args)
      end

      def has_booleans(*args)
        has_attributes(*args)
        are_booleans(*args)
      end

      def are_ints(*args)
        # puts 'calling are_ints: ' + args.inspect
        args.each do |arg|
          defined_attributes[arg.to_sym].type = :int
        end
      end

      def are_floats(*args)
        # puts 'calling are_ints: ' + args.inspect
        args.each do |arg|
          defined_attributes[arg.to_sym].type = :float
        end
      end

      def are_dates(*args)
        args.each do |arg|
          defined_attributes[arg.to_sym].type = :date
        end
      end

      def are_booleans(*args)
        args.each do |arg|
          defined_attributes[arg.to_sym].type = :boolean
        end
      end

      def has_clobs(*args)
        has_attributes2(args, :type=>:clob)

      end

      def has_virtuals(*args)
        @@virtuals = args
        args.each do |arg|
          #we just create the accessor functions here, the actual instance variable is created during initialize
          attr_accessor(arg)
        end
      end

      # One belongs_to association per call. Call multiple times if there are more than one.
      #
      # This method will also create an {association)_id method that will return the ID of the foreign object
      # without actually materializing it.
      #
      # options:
      # :class_name=>"User" - to change the default class to use
      def belongs_to(association_id, options = {})
        arg = association_id
        arg_s = arg.to_s
        arg_id = arg.to_s + '_id'
        attribute = Attribute.new(:belongs_to, options)
        defined_attributes[arg] = attribute

        # todo: should also handle foreign_key http://74.125.95.132/search?q=cache:KqLkxuXiBBQJ:wiki.rubyonrails.org/rails/show/belongs_to+rails+belongs_to&hl=en&ct=clnk&cd=1&gl=us
        # puts "arg_id=#{arg}_id"
        # puts "is defined? " + eval("(defined? #{arg}_id)").to_s
        # puts 'atts=' + @attributes.inspect

        # Define reader method
        send(:define_method, arg) do
          return get_attribute(arg)
        end


        # Define writer method
        send(:define_method, arg.to_s + "=") do |value|
          set(arg, value)
        end


        # Define ID reader method for reading the associated objects id without getting the entire object
        send(:define_method, arg_id) do
          get_attribute_sdb(arg_s)
        end

        # Define writer method for setting the _id directly without the associated object
        send(:define_method, arg_id + "=") do |value|
# rb_att_name = arg_s # n2 = name.to_s[0, name.length-3]
          set(arg_id, value)
# if value.nil?
# self[arg_id] = nil unless self[arg_id].nil? # if it went from something to nil, then we have to remember and remove attribute on save
# else
# self[arg_id] = value
# end
        end

        send(:define_method, "create_"+arg.to_s) do |*params|
          newsubrecord=eval(arg.to_s.classify).new(*params)
          newsubrecord.save
          arg_id = arg.to_s + '_id'
          self[arg_id]=newsubrecord.id
        end

        define_dirty_methods(arg_s)
      end

      def has_many(*args)
        args.each do |arg|
          #okay, this creates an instance method with the pluralized name of the symbol passed to belongs_to
          send(:define_method, arg) do
            #when called, the method creates a new, very temporary instance of the Activerecordtosdb_subrecord class
            #It is passed the three initializers it needs:
            #note the first parameter is just a string by time new gets it, like "user"
            #the second and third parameters are still a variable when new gets it, like user_id
            return eval(%{Activerecordtosdb_subrecord_array.new('#{arg}', self.class.name ,id)})
          end
        end
        #Disclaimer: this whole funciton just seems crazy to me, and a bit inefficient. But it was the clearest way I could think to do it code wise.
        #It's bad programming form (imo) to have a class method require something that isn't passed to it through it's variables.
        #I couldn't pass the id when calling find, since the original find doesn't work that way, so I was left with this.
      end

      def has_one(*args)

      end


    end

    @@virtuals=[]

    def self.handle_virtuals(attrs)
      @@virtuals.each do |virtual|
        #we first copy the information for the virtual to an instance variable of the same name
        eval("@#{virtual}=attrs['#{virtual}']")
        #and then remove the parameter before it is passed to initialize, so that it is NOT sent to SimpleDB
        eval("attrs.delete('#{virtual}')")
      end
    end


    def set(name, value, dirtify=true)
# puts "SET #{name}=#{value.inspect}" if SimpleRecord.logging?
# puts "self=" + self.inspect
      attname = name.to_s # default attname
      name = name.to_sym
      att_meta = get_att_meta(name)
      store_rb_val = false
      if att_meta.nil?
        # check if it ends with id and see if att_meta is there
        ends_with = name.to_s[-3, 3]
        if ends_with == "_id"
# puts 'ends with id'
          n2 = name.to_s[0, name.length-3]
# puts 'n2=' + n2
          att_meta = defined_attributes_local[n2.to_sym]
# puts 'defined_attributes_local=' + defined_attributes_local.inspect
          attname = name.to_s
          attvalue = value
          name = n2.to_sym
        end
        return if att_meta.nil?
      else
        if att_meta.type == :belongs_to
          ends_with = name.to_s[-3, 3]
          if ends_with == "_id"
            att_name = name.to_s
            attvalue = value
          else
            attname = name.to_s + '_id'
            attvalue = value.nil? ? nil : value.id
            store_rb_val = true
          end
        elsif att_meta.type == :clob
          make_dirty(name, value) if dirtify
          @lobs[name] = value
          return
        else
          attname = name.to_s
          attvalue = att_meta.init_value(value)
# attvalue = value
          #puts 'converted ' + value.inspect + ' to ' + attvalue.inspect
        end
      end
      attvalue = strip_array(attvalue)
      make_dirty(name, attvalue) if dirtify
# puts "ARG=#{attname.to_s} setting to #{attvalue}"
      sdb_val = ruby_to_sdb(name, attvalue)
# puts "sdb_val=" + sdb_val.to_s
      @attributes[attname] = sdb_val
# attvalue = wrap_if_required(name, attvalue, sdb_val)
# puts 'attvalue2=' + attvalue.to_s

      if store_rb_val
        @attributes_rb[name.to_s] = value
      else
        @attributes_rb.delete(name.to_s)
      end

    end


    def set_attribute_sdb(name, val)
      @attributes[sdb_att_name(name)] = val
    end


    def get_attribute_sdb(name)
      name = name.to_sym
      ret = strip_array(@attributes[sdb_att_name(name)])
      return ret
    end

    # Since SimpleDB supports multiple attributes per value, the values are an array.
    # This method will return the value unwrapped if it's the only, otherwise it will return the array.
    def get_attribute(name)
# puts "get_attribute #{name}"
      # Check if this arg is already converted
      name_s = name.to_s
      name = name.to_sym
      att_meta = get_att_meta(name)
# puts "att_meta for #{name}: " + att_meta.inspect
      if att_meta && att_meta.type == :clob
        ret = @lobs[name]
# puts 'get_attribute clob ' + ret.inspect
        if ret
          if ret.is_a? RemoteNil
            return nil
          else
            return ret
          end
        end
        # get it from s3
        unless new_record?
          if self.class.get_sr_config[:single_clob]
            begin
              single_clob = s3_bucket(false, :s3_bucket=>:new).get(single_clob_id)
              single_clob = JSON.parse(single_clob)
# puts "single_clob=" + single_clob.inspect
              single_clob.each_pair do |name2, val|
                @lobs[name2.to_sym] = val
              end
              ret = @lobs[name]
              SimpleRecord.stats.s3_gets += 1
            rescue Aws::AwsError => ex
              if ex.include?(/NoSuchKey/) || ex.include?(/NoSuchBucket/)
                ret = nil
              else
                raise ex
              end
            end
          else
            begin
              ret = s3_bucket.get(s3_lob_id(name))
                            # puts 'got from s3 ' + ret.inspect
              SimpleRecord.stats.s3_gets += 1
            rescue Aws::AwsError => ex
              if ex.include?(/NoSuchKey/) || ex.include?(/NoSuchBucket/)
                ret = nil
              else
                raise ex
              end
            end
          end

          if ret.nil?
            ret = RemoteNil.new
          end
        end
        @lobs[name] = ret
        return nil if ret.is_a? RemoteNil
        return ret
      else
        @attributes_rb = {} unless @attributes_rb # was getting errors after upgrade.
        ret = @attributes_rb[name_s] # instance_variable_get(instance_var)
        return ret unless ret.nil?
        return nil if ret.is_a? RemoteNil
        ret = get_attribute_sdb(name)
# p ret
        ret = sdb_to_ruby(name, ret)
# p ret
        @attributes_rb[name_s] = ret
        return ret
      end

    end


    private
    def set_attributes(atts)
      atts.each_pair do |k, v|
        set(k, v)
      end
    end


    # Holds information about an attribute
    class Attribute
      attr_accessor :type, :options

      def initialize(type, options=nil)
        @type = type
        @options = options
      end

      def init_value(value)
        return value if value.nil?
        ret = value
        case self.type
          when :int
            if value.is_a? Array
              ret = value.collect { |x| x.to_i }
            else
              ret = value.to_i
            end
        end
        ret
      end

    end

  end
end
Something went wrong with that request. Please try again.