Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/active_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ module ActiveResource
autoload :Coder
autoload :Connection
autoload :CustomMethods
autoload :Dirty
autoload :Formats
autoload :HttpMock
autoload :Rescuable
Expand Down
35 changes: 35 additions & 0 deletions lib/active_resource/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,34 @@ module ActiveResource
# Person.format.decode(person.encode)
# # => {"first_name"=>"First", "last_name"=>"Last"}
#
# === Attribute change tracking
#
# Active Resources track local attribute changes by integrating with
# ActiveModel::Dirty.
#
# Changes are discard when Active Resource successfuly saves the resource
# to the remote server or reloads data from the remote server:
#
# person = Person.find(1)
# person.name # => "Matz"
# person.name = "Changed"
#
# person.name_changed? # => true
# person.name_changes # => ["Matz", "Changed"]
# person.save
#
# person.name_changed? # => false
# person.name_changes # => []
#
# person.name = "Matz"
# person.name_changed? # => true
# person.name_changes # => ["Changed", "Matz"]
#
# person.reload
# person.name # => "Changed"
# person.name_changed? # => false
# person.name_changes # => []
#
# === Custom REST methods
#
# Since simple CRUD/life cycle methods can't accomplish every task, Active Resource also supports
Expand Down Expand Up @@ -463,6 +491,7 @@ def schema(&block)
@schema[k] = v
@known_attributes << k
end
define_attribute_methods @known_attributes

@schema
else
Expand Down Expand Up @@ -492,6 +521,7 @@ def schema=(the_schema)
# purposefully nulling out the schema
@schema = nil
@known_attributes = []
undefine_attribute_methods
return
end

Expand Down Expand Up @@ -1735,13 +1765,17 @@ def read_attribute(attr_name)
name = self.class.primary_key if name == "id" && self.class.primary_key
@attributes[name]
end
alias_method :attribute, :read_attribute

def write_attribute(attr_name, value)
name = attr_name.to_s

name = self.class.primary_key if name == "id" && self.class.primary_key
singleton_class.define_attribute_methods(name) unless known_attributes.include?(name)
attribute_will_change!(name) if @attributes[name] != value
@attributes[name] = value
end
alias_method :attribute=, :write_attribute

protected
def connection(refresh = false)
Expand Down Expand Up @@ -1904,6 +1938,7 @@ class Base
extend ActiveResource::Associations

include Callbacks, CustomMethods, Validations, Serialization
include Dirty
include ActiveModel::Conversion
include ActiveModel::ForbiddenAttributesProtection
include ActiveModel::Serializers::JSON
Expand Down
24 changes: 24 additions & 0 deletions lib/active_resource/dirty.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module ActiveResource
module Dirty # :nodoc:
extend ActiveSupport::Concern

included do
include ActiveModel::Dirty

after_save :changes_applied
after_reload :clear_changes_information

private

def mutations_from_database
@mutations_from_database ||= ActiveModel::ForcedMutationTracker.new(self)
end

def forget_attribute_assignments
# no-op
end
end
end
end
87 changes: 87 additions & 0 deletions test/cases/dirty_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# frozen_string_literal: true

require "abstract_unit"
require "fixtures/person"

class DirtyTest < ActiveSupport::TestCase
setup do
setup_response
@previous_schema = Person.schema
end

teardown do
Person.schema = @previous_schema
end

test "is clean when built" do
resource = Person.new

assert_empty resource.changes
end

test "is clean when reloaded" do
Person.schema do
attribute :name, :string
end

resource = Person.find(1)
resource.name = "changed"

assert_changes -> { resource.name_changed? }, from: true, to: false do
resource.reload
end
end

test "is clean after create" do
Person.schema do
attribute :name, :string
end

resource = Person.new name: "changed"
ActiveResource::HttpMock.respond_to.post "/people.json", {}, { id: 1, name: "changed" }.to_json

assert_changes -> { resource.name_changed? }, from: true, to: false do
resource.save
end
assert_empty resource.changes
end

test "is clean after update" do
Person.schema do
attribute :name, :string
end

resource = Person.find(1)
ActiveResource::HttpMock.respond_to.put "/people/1.json", {}, { id: 1, name: "changed" }.to_json

assert_changes -> { resource.name_changed? }, from: true, to: false do
resource.update(name: "changed")
end
assert_empty resource.changes
end

test "is dirty when known attribute changes are unsaved" do
Person.schema do
attribute :name, :string
end
expected_changes = {
"name" => [ nil, "known" ]
}

resource = Person.new name: "known"

assert_predicate resource, :name_changed?
assert_equal expected_changes, resource.changes
end

test "is dirty when unknown attribute changes are unsaved" do
expected_changes = {
"name" => [ nil, "unknown" ]
}

resource = Person.new name: "unknown"

assert_predicate resource, :name_changed?
assert_equal expected_changes, resource.changes
end
end