active_nomad 0.0.4 → 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.
- data/CHANGELOG +5 -0
- data/README.markdown +28 -18
- data/lib/active_nomad.rb +63 -22
- data/lib/active_nomad/version.rb +1 -1
- data/spec/unit/active_nomad_spec.rb +206 -60
- metadata +4 -4
    
        data/CHANGELOG
    CHANGED
    
    
    
        data/README.markdown
    CHANGED
    
    | @@ -6,16 +6,15 @@ ActiveRecord objects with a customizable persistence strategy. | |
| 6 6 |  | 
| 7 7 | 
             
            Sometimes you want an Active Record object that does not live in the database.
         | 
| 8 8 | 
             
            Perhaps it never needs to be persisted, or you'd like to store it in a cookie,
         | 
| 9 | 
            -
            or  | 
| 9 | 
            +
            or a key-value store, but it would still be handy to have ActiveRecord's ability
         | 
| 10 10 | 
             
            to cast values, run validations, or fire callbacks.
         | 
| 11 11 |  | 
| 12 | 
            -
             | 
| 12 | 
            +
            If only the persistence strategy was pluggable...
         | 
| 13 13 |  | 
| 14 14 | 
             
            ## How
         | 
| 15 15 |  | 
| 16 | 
            -
            Subclass from ActiveNomad::Base and declare your attributes with the
         | 
| 17 | 
            -
             | 
| 18 | 
            -
            migration:
         | 
| 16 | 
            +
            Subclass from ActiveNomad::Base and declare your attributes with the `attribute`
         | 
| 17 | 
            +
            class method. The arguments look just like creating a column in a migration:
         | 
| 19 18 |  | 
| 20 19 | 
             
                class Thing < ActiveNomad::Base
         | 
| 21 20 | 
             
                  attribute :name, :string, :limit => 20
         | 
| @@ -25,27 +24,38 @@ To persist the record, Active Nomad calls `persist`, which calls a | |
| 25 24 | 
             
            Proc registered by `to_save`. For example, here's how you could
         | 
| 26 25 | 
             
            persist to a cookie:
         | 
| 27 26 |  | 
| 28 | 
            -
                thing = Thing. | 
| 27 | 
            +
                thing = Thing.from_json(cookies[:thing])
         | 
| 29 28 | 
             
                thing.to_save do |thing|
         | 
| 30 | 
            -
                  cookies[:thing] = thing. | 
| 29 | 
            +
                  cookies[:thing] = thing.to_json
         | 
| 31 30 | 
             
                  true
         | 
| 32 31 | 
             
                end
         | 
| 33 32 |  | 
| 34 33 | 
             
            Things to note:
         | 
| 35 34 |  | 
| 36 | 
            -
             * Active Nomad defines `serialize` and `deserialize` which will
         | 
| 37 | 
            -
               serialize to and from a valid query string with predictable
         | 
| 38 | 
            -
               attribute order (i.e., appropriate for a cookie).
         | 
| 39 35 | 
             
             * The proc should return true if persistence was successful, false
         | 
| 40 36 | 
             
               otherwise. This will be the return value of `save`, etc.
         | 
| 41 | 
            -
             *  | 
| 42 | 
            -
                | 
| 37 | 
            +
             * Active Nomad defines `to_json` and `from_json` which will serialize to and
         | 
| 38 | 
            +
               from a JSON string with predictable attribute order (i.e., appropriate for a
         | 
| 39 | 
            +
               cookie).
         | 
| 40 | 
            +
             * You may alternatively override `persist` in a subclass if you don't want to
         | 
| 41 | 
            +
               register a proc for every instance.
         | 
| 43 42 |  | 
| 44 | 
            -
             | 
| 43 | 
            +
            ### Serialization formats
         | 
| 45 44 |  | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 45 | 
            +
            Active Nomad provides serialization to and from:
         | 
| 46 | 
            +
             | 
| 47 | 
            +
             * JSON (`to_json` and `from_json`)
         | 
| 48 | 
            +
             * Query string (`to_query_string` and `from_query_string`)
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            You can define your own formats easily using `to_serialized_attributes` and
         | 
| 51 | 
            +
            `from_serialized_attributes`. The former returns an `ActiveSupport::OrderedHash`
         | 
| 52 | 
            +
            of attribute names to serialized values (`String`s or `nil`s).
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            ### Transactions
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            In addition to customizing persistence, you can also customize transaction
         | 
| 57 | 
            +
            semantics by overriding the `transaction` class method in a base class. Here's
         | 
| 58 | 
            +
            an example that implements transactions with Redis:
         | 
| 49 59 |  | 
| 50 60 | 
             
                class RedisNomad < ActiveNomad::Base
         | 
| 51 61 | 
             
                  def self.transaction
         | 
| @@ -64,8 +74,8 @@ a base class. Example: | |
| 64 74 | 
             
                  end
         | 
| 65 75 | 
             
                end
         | 
| 66 76 |  | 
| 67 | 
            -
            `ActiveNomad::Base.transaction` simply calls the given block with no
         | 
| 68 | 
            -
             | 
| 77 | 
            +
            `ActiveNomad::Base.transaction` simply calls the given block with no real
         | 
| 78 | 
            +
            transaction semantics.
         | 
| 69 79 |  | 
| 70 80 | 
             
            ## Notes
         | 
| 71 81 |  | 
    
        data/lib/active_nomad.rb
    CHANGED
    
    | @@ -13,34 +13,74 @@ module ActiveNomad | |
| 13 13 | 
             
                end
         | 
| 14 14 |  | 
| 15 15 | 
             
                #
         | 
| 16 | 
            -
                # Return  | 
| 17 | 
            -
                # string.
         | 
| 16 | 
            +
                # Return an ActiveSupport::OrderedHash of serialized attributes.
         | 
| 18 17 | 
             
                #
         | 
| 19 | 
            -
                # Attributes are sorted by name.
         | 
| 18 | 
            +
                # Attributes are sorted by name. Each value is either a string or
         | 
| 19 | 
            +
                # nil.
         | 
| 20 20 | 
             
                #
         | 
| 21 | 
            -
                def  | 
| 22 | 
            -
                   | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 21 | 
            +
                def to_serialized_attributes
         | 
| 22 | 
            +
                  attributes = ActiveSupport::OrderedHash.new
         | 
| 23 | 
            +
                  columns = self.class.columns_hash
         | 
| 24 | 
            +
                  self.class.column_names.sort.each do |name|
         | 
| 25 | 
            +
                    column = columns[name] or
         | 
| 25 26 | 
             
                      next
         | 
| 26 | 
            -
                     | 
| 27 | 
            -
                  end | 
| 27 | 
            +
                    attributes[name] = serialize_value(send(name), column.type)
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                  attributes
         | 
| 28 30 | 
             
                end
         | 
| 29 31 |  | 
| 30 32 | 
             
                #
         | 
| 31 33 | 
             
                # Recreate an object from a serialized string.
         | 
| 32 34 | 
             
                #
         | 
| 33 | 
            -
                def self. | 
| 34 | 
            -
                  params = string ? CGI.parse(string.strip) : {}
         | 
| 35 | 
            +
                def self.from_serialized_attributes(deserialized_attributes)
         | 
| 35 36 | 
             
                  instance = new
         | 
| 36 | 
            -
                   | 
| 37 | 
            -
                     | 
| 38 | 
            -
             | 
| 39 | 
            -
                    instance.send "#{column.name}=", deserialize_value( | 
| 37 | 
            +
                  deserialized_attributes.each do |name, serialized_value|
         | 
| 38 | 
            +
                    column = columns_hash[name.to_s] or
         | 
| 39 | 
            +
                      next
         | 
| 40 | 
            +
                    instance.send "#{column.name}=", deserialize_value(serialized_value, column.type)
         | 
| 40 41 | 
             
                  end
         | 
| 41 42 | 
             
                  instance
         | 
| 42 43 | 
             
                end
         | 
| 43 44 |  | 
| 45 | 
            +
                #
         | 
| 46 | 
            +
                # Serialize this record as a query string.
         | 
| 47 | 
            +
                #
         | 
| 48 | 
            +
                def to_query_string
         | 
| 49 | 
            +
                  to_serialized_attributes.map do |name, value|
         | 
| 50 | 
            +
                    next nil if value.nil?
         | 
| 51 | 
            +
                    "#{CGI.escape(name)}=#{CGI.escape(value)}"
         | 
| 52 | 
            +
                  end.compact.sort.join('&')
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                #
         | 
| 56 | 
            +
                # Deserialize this record from a query string returned by
         | 
| 57 | 
            +
                # #to_query_string.
         | 
| 58 | 
            +
                #
         | 
| 59 | 
            +
                def self.from_query_string(string)
         | 
| 60 | 
            +
                  return new if string.blank?
         | 
| 61 | 
            +
                  serialized_attributes = string.strip.split(/&/).map do |pair|
         | 
| 62 | 
            +
                    name, value = pair.split(/=/, 2)
         | 
| 63 | 
            +
                    [CGI.unescape(name), CGI.unescape(value)]
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
                  from_serialized_attributes(serialized_attributes)
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                #
         | 
| 69 | 
            +
                # Serialize this record as a JSON string.
         | 
| 70 | 
            +
                #
         | 
| 71 | 
            +
                def to_json
         | 
| 72 | 
            +
                  to_serialized_attributes.to_json
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                #
         | 
| 76 | 
            +
                # Deserialize this record from a JSON string returned by #to_json.
         | 
| 77 | 
            +
                #
         | 
| 78 | 
            +
                def self.from_json(string)
         | 
| 79 | 
            +
                  return new if string.blank?
         | 
| 80 | 
            +
                  serialized_attributes = JSON.parse(string)
         | 
| 81 | 
            +
                  from_serialized_attributes(serialized_attributes)
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 44 84 | 
             
                protected
         | 
| 45 85 |  | 
| 46 86 | 
             
                #
         | 
| @@ -115,11 +155,12 @@ module ActiveNomad | |
| 115 155 | 
             
                  return nil if value.nil?
         | 
| 116 156 | 
             
                  case type
         | 
| 117 157 | 
             
                  when :datetime, :timestamp, :time
         | 
| 118 | 
            -
                     | 
| 158 | 
            +
                    # The day in RFC 2822 is optional - chop it.
         | 
| 159 | 
            +
                    value.rfc2822.sub(/\A[A-Za-z]{3}, /, '')
         | 
| 119 160 | 
             
                  when :date
         | 
| 120 | 
            -
                     | 
| 161 | 
            +
                    value.to_date.strftime(DATE_FORMAT)
         | 
| 121 162 | 
             
                  else
         | 
| 122 | 
            -
                     | 
| 163 | 
            +
                    value.to_s
         | 
| 123 164 | 
             
                  end
         | 
| 124 165 | 
             
                end
         | 
| 125 166 |  | 
| @@ -127,14 +168,14 @@ module ActiveNomad | |
| 127 168 | 
             
                  return nil if string.nil?
         | 
| 128 169 | 
             
                  case type
         | 
| 129 170 | 
             
                  when :datetime, :timestamp, :time
         | 
| 130 | 
            -
                    Time. | 
| 171 | 
            +
                    Time.parse(string)
         | 
| 131 172 | 
             
                  when :date
         | 
| 132 | 
            -
                     | 
| 173 | 
            +
                    Date.parse(string)
         | 
| 133 174 | 
             
                  else
         | 
| 134 | 
            -
                     | 
| 175 | 
            +
                    string
         | 
| 135 176 | 
             
                  end
         | 
| 136 177 | 
             
                end
         | 
| 137 178 |  | 
| 138 | 
            -
                 | 
| 179 | 
            +
                DATE_FORMAT = '%d %b %Y'.freeze
         | 
| 139 180 | 
             
              end
         | 
| 140 181 | 
             
            end
         | 
    
        data/lib/active_nomad/version.rb
    CHANGED
    
    
| @@ -123,23 +123,138 @@ describe ActiveNomad::Base do | |
| 123 123 | 
             
                end
         | 
| 124 124 | 
             
              end
         | 
| 125 125 |  | 
| 126 | 
            -
              describe "# | 
| 126 | 
            +
              describe "#to_serialized_attributes" do
         | 
| 127 | 
            +
                it "should return a list of attribute names with serialized attributes, sorted by name" do
         | 
| 128 | 
            +
                  klass = Class.new(ActiveNomad::Base) do
         | 
| 129 | 
            +
                    attribute :integer_attribute, :integer
         | 
| 130 | 
            +
                    attribute :string_attribute, :string
         | 
| 131 | 
            +
                    attribute :text_attribute, :text
         | 
| 132 | 
            +
                    attribute :float_attribute, :float
         | 
| 133 | 
            +
                    attribute :decimal_attribute, :decimal
         | 
| 134 | 
            +
                    attribute :datetime_attribute, :datetime
         | 
| 135 | 
            +
                    attribute :timestamp_attribute, :timestamp
         | 
| 136 | 
            +
                    attribute :time_attribute, :time
         | 
| 137 | 
            +
                    attribute :date_attribute, :date
         | 
| 138 | 
            +
                    attribute :binary_attribute, :binary
         | 
| 139 | 
            +
                    attribute :boolean_attribute, :boolean
         | 
| 140 | 
            +
                    attribute :nil_attribute, :string
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
                  instance = klass.new(
         | 
| 143 | 
            +
                    :integer_attribute => 5,
         | 
| 144 | 
            +
                    :string_attribute => 'string',
         | 
| 145 | 
            +
                    :text_attribute => 'text',
         | 
| 146 | 
            +
                    :float_attribute => 1.23,
         | 
| 147 | 
            +
                    :decimal_attribute => BigDecimal.new('123.45'),
         | 
| 148 | 
            +
                    :datetime_attribute => Time.parse('03 Feb 2001 12:34:56 -0000'),
         | 
| 149 | 
            +
                    :timestamp_attribute => Time.parse('03 Feb 2001 12:34:56 -0000'),
         | 
| 150 | 
            +
                    :time_attribute => Time.parse('03 Feb 2001 12:34:56 -0000'),
         | 
| 151 | 
            +
                    :date_attribute => Date.parse('03 Feb 2001'),
         | 
| 152 | 
            +
                    :binary_attribute => "\0\1",
         | 
| 153 | 
            +
                    :boolean_attribute => true,
         | 
| 154 | 
            +
                    :nil_attribute => nil
         | 
| 155 | 
            +
                  )
         | 
| 156 | 
            +
                  instance.to_serialized_attributes.to_a.should == [
         | 
| 157 | 
            +
                    ['binary_attribute', "\0\1"],
         | 
| 158 | 
            +
                    ['boolean_attribute', 'true'],
         | 
| 159 | 
            +
                    ['date_attribute', '03 Feb 2001'],
         | 
| 160 | 
            +
                    ['datetime_attribute', '03 Feb 2001 12:34:56 -0000'],
         | 
| 161 | 
            +
                    ['decimal_attribute', '123.45'],
         | 
| 162 | 
            +
                    ['float_attribute', '1.23'],
         | 
| 163 | 
            +
                    ['integer_attribute', '5'],
         | 
| 164 | 
            +
                    ['nil_attribute', nil],
         | 
| 165 | 
            +
                    ['string_attribute', 'string'],
         | 
| 166 | 
            +
                    ['text_attribute', 'text'],
         | 
| 167 | 
            +
                    ['time_attribute', '03 Feb 2001 12:34:56 -0000'],
         | 
| 168 | 
            +
                    ['timestamp_attribute', '03 Feb 2001 12:34:56 -0000'],
         | 
| 169 | 
            +
                  ]
         | 
| 170 | 
            +
                end
         | 
| 171 | 
            +
              end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
              describe ".from_serialized_attributes" do
         | 
| 174 | 
            +
                it "should create a new record with attributes deserialized from the given parameter list" do
         | 
| 175 | 
            +
                  klass = Class.new(ActiveNomad::Base) do
         | 
| 176 | 
            +
                    attribute :integer_attribute, :integer
         | 
| 177 | 
            +
                    attribute :string_attribute, :string
         | 
| 178 | 
            +
                    attribute :text_attribute, :text
         | 
| 179 | 
            +
                    attribute :float_attribute, :float
         | 
| 180 | 
            +
                    attribute :decimal_attribute, :decimal
         | 
| 181 | 
            +
                    attribute :datetime_attribute, :datetime
         | 
| 182 | 
            +
                    attribute :timestamp_attribute, :timestamp
         | 
| 183 | 
            +
                    attribute :time_attribute, :time
         | 
| 184 | 
            +
                    attribute :date_attribute, :date
         | 
| 185 | 
            +
                    attribute :binary_attribute, :binary
         | 
| 186 | 
            +
                    attribute :boolean_attribute, :boolean
         | 
| 187 | 
            +
                    attribute :nil_attribute, :boolean
         | 
| 188 | 
            +
                  end
         | 
| 189 | 
            +
                  instance = klass.from_serialized_attributes([
         | 
| 190 | 
            +
                    [:integer_attribute, '5'],
         | 
| 191 | 
            +
                    [:string_attribute, 'string'],
         | 
| 192 | 
            +
                    [:text_attribute, 'text'],
         | 
| 193 | 
            +
                    [:float_attribute, '1.23'],
         | 
| 194 | 
            +
                    [:decimal_attribute, '123.45'],
         | 
| 195 | 
            +
                    [:datetime_attribute, '03 Feb 2001 12:34:56 -0000'],
         | 
| 196 | 
            +
                    [:timestamp_attribute, '03 Feb 2001 12:34:56 -0000'],
         | 
| 197 | 
            +
                    [:time_attribute, '03 Feb 2001 12:34:56 -0000'],
         | 
| 198 | 
            +
                    [:date_attribute, '03 Feb 2001'],
         | 
| 199 | 
            +
                    [:binary_attribute, "\0\1"],
         | 
| 200 | 
            +
                    [:boolean_attribute, 'true'],
         | 
| 201 | 
            +
                    [:nil_attribute, nil]
         | 
| 202 | 
            +
                  ])
         | 
| 203 | 
            +
                  instance.integer_attribute.should == 5
         | 
| 204 | 
            +
                  instance.string_attribute.should == 'string'
         | 
| 205 | 
            +
                  instance.text_attribute.should == 'text'
         | 
| 206 | 
            +
                  instance.float_attribute.should == 1.23
         | 
| 207 | 
            +
                  instance.decimal_attribute.should == BigDecimal.new('123.45')
         | 
| 208 | 
            +
                  instance.datetime_attribute.should == Time.parse('03 Feb 2001 12:34:56 -0000')
         | 
| 209 | 
            +
                  instance.timestamp_attribute.should == Time.parse('03 Feb 2001 12:34:56 -0000')
         | 
| 210 | 
            +
                  instance.time_attribute.should == Time.parse('03 Feb 2001 12:34:56 -0000')
         | 
| 211 | 
            +
                  instance.date_attribute.should == Date.parse('03 Feb 2001')
         | 
| 212 | 
            +
                  instance.binary_attribute.should == "\0\1"
         | 
| 213 | 
            +
                  instance.boolean_attribute.should be_true
         | 
| 214 | 
            +
                  instance.nil_attribute.should be_nil
         | 
| 215 | 
            +
                end
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                it "should work with any enumerable" do
         | 
| 218 | 
            +
                  klass = Class.new(ActiveNomad::Base) do
         | 
| 219 | 
            +
                    attribute :name, :string
         | 
| 220 | 
            +
                  end
         | 
| 221 | 
            +
                  params = Object.new
         | 
| 222 | 
            +
                  class << params
         | 
| 223 | 
            +
                    def each
         | 
| 224 | 
            +
                      yield :name, 'joe'
         | 
| 225 | 
            +
                    end
         | 
| 226 | 
            +
                  end
         | 
| 227 | 
            +
                  params.extend Enumerable
         | 
| 228 | 
            +
                  instance = klass.from_serialized_attributes(params)
         | 
| 229 | 
            +
                  instance.name.should == 'joe'
         | 
| 230 | 
            +
                end
         | 
| 231 | 
            +
             | 
| 232 | 
            +
                it "should leave defaults alone for attributes which are not set" do
         | 
| 233 | 
            +
                  klass = Class.new(ActiveNomad::Base) do
         | 
| 234 | 
            +
                    attribute :name, :string, :default => 'Joe'
         | 
| 235 | 
            +
                  end
         | 
| 236 | 
            +
                  instance = klass.from_serialized_attributes({})
         | 
| 237 | 
            +
                  instance.name.should == 'Joe'
         | 
| 238 | 
            +
                end
         | 
| 239 | 
            +
              end
         | 
| 240 | 
            +
             | 
| 241 | 
            +
              describe "#to_query_string" do
         | 
| 127 242 | 
             
                it "should serialize the attributes as a query string" do
         | 
| 128 243 | 
             
                  klass = Class.new(ActiveNomad::Base) do
         | 
| 129 244 | 
             
                    attribute :first_name, :string
         | 
| 130 245 | 
             
                    attribute :last_name, :string
         | 
| 131 246 | 
             
                  end
         | 
| 132 247 | 
             
                  instance = klass.new(:first_name => 'Joe', :last_name => 'Blow')
         | 
| 133 | 
            -
                  instance. | 
| 248 | 
            +
                  instance.to_query_string.should == 'first_name=Joe&last_name=Blow'
         | 
| 134 249 | 
             
                end
         | 
| 135 250 | 
             
              end
         | 
| 136 251 |  | 
| 137 | 
            -
              describe ". | 
| 252 | 
            +
              describe ".from_query_string" do
         | 
| 138 253 | 
             
                it "should create a new record with no attributes set if nil is given" do
         | 
| 139 254 | 
             
                  klass = Class.new(ActiveNomad::Base) do
         | 
| 140 255 | 
             
                    attribute :name, :string
         | 
| 141 256 | 
             
                  end
         | 
| 142 | 
            -
                  instance = klass. | 
| 257 | 
            +
                  instance = klass.from_query_string(nil)
         | 
| 143 258 | 
             
                  instance.name.should be_nil
         | 
| 144 259 | 
             
                end
         | 
| 145 260 |  | 
| @@ -147,7 +262,7 @@ describe ActiveNomad::Base do | |
| 147 262 | 
             
                  klass = Class.new(ActiveNomad::Base) do
         | 
| 148 263 | 
             
                    attribute :name, :string
         | 
| 149 264 | 
             
                  end
         | 
| 150 | 
            -
                  instance = klass. | 
| 265 | 
            +
                  instance = klass.from_query_string('')
         | 
| 151 266 | 
             
                  instance.name.should be_nil
         | 
| 152 267 | 
             
                end
         | 
| 153 268 |  | 
| @@ -155,7 +270,7 @@ describe ActiveNomad::Base do | |
| 155 270 | 
             
                  klass = Class.new(ActiveNomad::Base) do
         | 
| 156 271 | 
             
                    attribute :name, :string
         | 
| 157 272 | 
             
                  end
         | 
| 158 | 
            -
                  instance = klass. | 
| 273 | 
            +
                  instance = klass.from_query_string(" \t")
         | 
| 159 274 | 
             
                  instance.name.should be_nil
         | 
| 160 275 | 
             
                end
         | 
| 161 276 |  | 
| @@ -163,19 +278,84 @@ describe ActiveNomad::Base do | |
| 163 278 | 
             
                  klass = Class.new(ActiveNomad::Base) do
         | 
| 164 279 | 
             
                    attribute :name, :string, :default => 'Joe'
         | 
| 165 280 | 
             
                  end
         | 
| 166 | 
            -
                  instance = klass. | 
| 281 | 
            +
                  instance = klass.from_query_string(" \t")
         | 
| 167 282 | 
             
                  instance.name.should == 'Joe'
         | 
| 168 283 | 
             
                end
         | 
| 169 284 | 
             
              end
         | 
| 170 285 |  | 
| 171 | 
            -
               | 
| 286 | 
            +
              def self.it_should_roundtrip_through(serializer, deserializer, &block)
         | 
| 287 | 
            +
                describe "roundtripping through ##{serializer} and .#{deserializer}" do
         | 
| 288 | 
            +
                  class_eval(&block) if block
         | 
| 289 | 
            +
             | 
| 290 | 
            +
                  (class << self; self; end).class_eval do
         | 
| 291 | 
            +
                    define_method :it_should_roundtrip do |type, value|
         | 
| 292 | 
            +
                      value = Time.at(value.to_i) if value.is_a?(Time) # chop off subseconds
         | 
| 293 | 
            +
                      it "should roundtrip #{value.inspect} correctly as a #{type}" do
         | 
| 294 | 
            +
                        klass = Class.new(ActiveNomad::Base) do
         | 
| 295 | 
            +
                          attribute :value, type
         | 
| 296 | 
            +
                        end
         | 
| 297 | 
            +
                        instance = klass.new(:value => value)
         | 
| 298 | 
            +
                        roundtripped = klass.send(deserializer, instance.send(serializer))
         | 
| 299 | 
            +
                        roundtripped.value.should == value
         | 
| 300 | 
            +
                      end
         | 
| 301 | 
            +
                    end
         | 
| 302 | 
            +
                  end
         | 
| 303 | 
            +
             | 
| 304 | 
            +
                  it_should_roundtrip :integer, nil
         | 
| 305 | 
            +
                  it_should_roundtrip :integer, 0
         | 
| 306 | 
            +
                  it_should_roundtrip :integer, 123
         | 
| 307 | 
            +
             | 
| 308 | 
            +
                  it_should_roundtrip :string, nil
         | 
| 309 | 
            +
                  it_should_roundtrip :string, ''
         | 
| 310 | 
            +
                  it_should_roundtrip :string, 'hi'
         | 
| 311 | 
            +
             | 
| 312 | 
            +
                  it_should_roundtrip :text, nil
         | 
| 313 | 
            +
                  it_should_roundtrip :text, ''
         | 
| 314 | 
            +
                  it_should_roundtrip :text, 'hi'
         | 
| 315 | 
            +
             | 
| 316 | 
            +
                  it_should_roundtrip :float, nil
         | 
| 317 | 
            +
                  it_should_roundtrip :float, 0
         | 
| 318 | 
            +
                  it_should_roundtrip :float, 0.123
         | 
| 319 | 
            +
             | 
| 320 | 
            +
                  it_should_roundtrip :decimal, nil
         | 
| 321 | 
            +
                  it_should_roundtrip :decimal, BigDecimal.new('0')
         | 
| 322 | 
            +
                  it_should_roundtrip :decimal, BigDecimal.new('123.45')
         | 
| 323 | 
            +
             | 
| 324 | 
            +
                  it_should_roundtrip :datetime, nil
         | 
| 325 | 
            +
                  it_should_roundtrip :datetime, Time.now.in_time_zone
         | 
| 326 | 
            +
                  # TODO: Support DateTime here, which is used when the value is
         | 
| 327 | 
            +
                  # outside the range of a Time.
         | 
| 328 | 
            +
             | 
| 329 | 
            +
                  it_should_roundtrip :timestamp, nil
         | 
| 330 | 
            +
                  it_should_roundtrip :timestamp, Time.now.in_time_zone
         | 
| 331 | 
            +
             | 
| 332 | 
            +
                  it_should_roundtrip :time, nil
         | 
| 333 | 
            +
                  it_should_roundtrip :time, Time.parse('2000-01-01 01:23:34').in_time_zone
         | 
| 334 | 
            +
             | 
| 335 | 
            +
                  it_should_roundtrip :date, nil
         | 
| 336 | 
            +
                  it_should_roundtrip :date, Date.today
         | 
| 337 | 
            +
             | 
| 338 | 
            +
                  it_should_roundtrip :binary, nil
         | 
| 339 | 
            +
                  it_should_roundtrip :binary, ''
         | 
| 340 | 
            +
                  it_should_roundtrip :binary, "\t\n"
         | 
| 341 | 
            +
                  #it_should_roundtrip :binary, "\0\1" # TODO: the JSON gem does not handle this
         | 
| 342 | 
            +
             | 
| 343 | 
            +
                  it_should_roundtrip :boolean, nil
         | 
| 344 | 
            +
                  it_should_roundtrip :boolean, true
         | 
| 345 | 
            +
                  it_should_roundtrip :boolean, false
         | 
| 346 | 
            +
                end
         | 
| 347 | 
            +
              end
         | 
| 348 | 
            +
             | 
| 349 | 
            +
              it_should_roundtrip_through(:to_serialized_attributes, :from_serialized_attributes)
         | 
| 350 | 
            +
             | 
| 351 | 
            +
              it_should_roundtrip_through(:to_query_string, :from_query_string) do
         | 
| 172 352 | 
             
                it "should not be tripped up by delimiters in the keys" do
         | 
| 173 353 | 
             
                  klass = Class.new(ActiveNomad::Base) do
         | 
| 174 354 | 
             
                    attribute :'a=x', :string
         | 
| 175 355 | 
             
                    attribute :'b&x', :string
         | 
| 176 356 | 
             
                  end
         | 
| 177 357 | 
             
                  original = klass.new("a=x" => "1", "b&x" => "2")
         | 
| 178 | 
            -
                  roundtripped = klass. | 
| 358 | 
            +
                  roundtripped = klass.from_query_string(original.to_query_string)
         | 
| 179 359 | 
             
                  roundtripped.send("a=x").should == "1"
         | 
| 180 360 | 
             
                  roundtripped.send("b&x").should == "2"
         | 
| 181 361 | 
             
                end
         | 
| @@ -186,64 +366,30 @@ describe ActiveNomad::Base do | |
| 186 366 | 
             
                    attribute :b, :string
         | 
| 187 367 | 
             
                  end
         | 
| 188 368 | 
             
                  original = klass.new(:a => "1=2", :b => "3&4")
         | 
| 189 | 
            -
                  roundtripped = klass. | 
| 369 | 
            +
                  roundtripped = klass.from_query_string(original.to_query_string)
         | 
| 190 370 | 
             
                  roundtripped.a.should == "1=2"
         | 
| 191 371 | 
             
                  roundtripped.b.should == "3&4"
         | 
| 192 372 | 
             
                end
         | 
| 373 | 
            +
              end
         | 
| 193 374 |  | 
| 194 | 
            -
             | 
| 195 | 
            -
             | 
| 196 | 
            -
                   | 
| 197 | 
            -
                     | 
| 198 | 
            -
                      attribute :value, type
         | 
| 199 | 
            -
                    end
         | 
| 200 | 
            -
                    instance = klass.new(:value => value)
         | 
| 201 | 
            -
                    roundtripped = klass.deserialize(instance.serialize)
         | 
| 202 | 
            -
                    roundtripped.value.should == value
         | 
| 375 | 
            +
              it_should_roundtrip_through(:to_json, :from_json) do
         | 
| 376 | 
            +
                it "should not be tripped up by delimiters in the keys" do
         | 
| 377 | 
            +
                  klass = Class.new(ActiveNomad::Base) do
         | 
| 378 | 
            +
                    attribute :"'a':b,c", :string
         | 
| 203 379 | 
             
                  end
         | 
| 380 | 
            +
                  original = klass.new("'a':b,c" => "1")
         | 
| 381 | 
            +
                  roundtripped = klass.from_json(original.to_json)
         | 
| 382 | 
            +
                  roundtripped.send("'a':b,c").should == "1"
         | 
| 204 383 | 
             
                end
         | 
| 205 384 |  | 
| 206 | 
            -
                 | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
             | 
| 210 | 
            -
             | 
| 211 | 
            -
             | 
| 212 | 
            -
             | 
| 213 | 
            -
             | 
| 214 | 
            -
                it_should_roundtrip :text, nil
         | 
| 215 | 
            -
                it_should_roundtrip :text, ''
         | 
| 216 | 
            -
                it_should_roundtrip :text, 'hi'
         | 
| 217 | 
            -
             | 
| 218 | 
            -
                it_should_roundtrip :float, nil
         | 
| 219 | 
            -
                it_should_roundtrip :float, 0
         | 
| 220 | 
            -
                it_should_roundtrip :float, 0.123
         | 
| 221 | 
            -
             | 
| 222 | 
            -
                it_should_roundtrip :decimal, nil
         | 
| 223 | 
            -
                it_should_roundtrip :decimal, BigDecimal.new('0')
         | 
| 224 | 
            -
                it_should_roundtrip :decimal, BigDecimal.new('123.45')
         | 
| 225 | 
            -
             | 
| 226 | 
            -
                it_should_roundtrip :datetime, nil
         | 
| 227 | 
            -
                it_should_roundtrip :datetime, Time.now.in_time_zone
         | 
| 228 | 
            -
                # TODO: Support DateTime here, which is used when the value is
         | 
| 229 | 
            -
                # outside the range of a Time.
         | 
| 230 | 
            -
             | 
| 231 | 
            -
                it_should_roundtrip :timestamp, nil
         | 
| 232 | 
            -
                it_should_roundtrip :timestamp, Time.now.in_time_zone
         | 
| 233 | 
            -
             | 
| 234 | 
            -
                it_should_roundtrip :time, nil
         | 
| 235 | 
            -
                it_should_roundtrip :time, Time.parse('2000-01-01 01:23:34').in_time_zone
         | 
| 236 | 
            -
             | 
| 237 | 
            -
                it_should_roundtrip :date, nil
         | 
| 238 | 
            -
                it_should_roundtrip :date, Date.today
         | 
| 239 | 
            -
             | 
| 240 | 
            -
                it_should_roundtrip :binary, nil
         | 
| 241 | 
            -
                it_should_roundtrip :binary, ''
         | 
| 242 | 
            -
                it_should_roundtrip :binary, "\0\1"
         | 
| 243 | 
            -
             | 
| 244 | 
            -
                it_should_roundtrip :boolean, nil
         | 
| 245 | 
            -
                it_should_roundtrip :boolean, true
         | 
| 246 | 
            -
                it_should_roundtrip :boolean, false
         | 
| 385 | 
            +
                it "should not be tripped up by delimiters in the values" do
         | 
| 386 | 
            +
                  klass = Class.new(ActiveNomad::Base) do
         | 
| 387 | 
            +
                    attribute :a, :string
         | 
| 388 | 
            +
                  end
         | 
| 389 | 
            +
                  original = klass.new(:a => "'a':b,c")
         | 
| 390 | 
            +
                  roundtripped = klass.from_json(original.to_json)
         | 
| 391 | 
            +
                  roundtripped.a.should == "'a':b,c"
         | 
| 392 | 
            +
                end
         | 
| 247 393 | 
             
              end
         | 
| 248 394 |  | 
| 249 395 | 
             
              describe ".transaction" do
         | 
    
        metadata
    CHANGED
    
    | @@ -1,13 +1,13 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification 
         | 
| 2 2 | 
             
            name: active_nomad
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            -
              hash:  | 
| 4 | 
            +
              hash: 27
         | 
| 5 5 | 
             
              prerelease: false
         | 
| 6 6 | 
             
              segments: 
         | 
| 7 7 | 
             
              - 0
         | 
| 8 | 
            +
              - 1
         | 
| 8 9 | 
             
              - 0
         | 
| 9 | 
            -
               | 
| 10 | 
            -
              version: 0.0.4
         | 
| 10 | 
            +
              version: 0.1.0
         | 
| 11 11 | 
             
            platform: ruby
         | 
| 12 12 | 
             
            authors: 
         | 
| 13 13 | 
             
            - George Ogata
         | 
| @@ -15,7 +15,7 @@ autorequire: | |
| 15 15 | 
             
            bindir: bin
         | 
| 16 16 | 
             
            cert_chain: []
         | 
| 17 17 |  | 
| 18 | 
            -
            date: 2010-10- | 
| 18 | 
            +
            date: 2010-10-05 00:00:00 -04:00
         | 
| 19 19 | 
             
            default_executable: 
         | 
| 20 20 | 
             
            dependencies: 
         | 
| 21 21 | 
             
            - !ruby/object:Gem::Dependency 
         |