Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Combine Parent.save and Child.save to the same transaction. #61

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
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
4 changes: 2 additions & 2 deletions citier.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ Gem::Specification.new do |s|
s.rubyforge_project = %q{citier}
s.rubygems_version = %q{1.3.7}
s.summary = s.description
s.add_dependency('rails_sql_views') #needs the 'rails_sql_views', :git => 'git://github.com/morgz/rails_sql_views.git' fork. Set this in your apps bundle

s.add_dependency('rails_sql_views') #needs the 'rails_sql_views', :git => 'https://github.com/ryanlitalien/rails_sql_views.git' fork. Set this in your apps bundle

if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
Expand Down
5 changes: 1 addition & 4 deletions lib/citier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@ def citier_debug(s)
# Methods that will be used for the instances of the Non Root Classes
require 'citier/child_instance_methods'

# Require SQL Adapters
require 'citier/sql_adapters'

#Require acts_as_citier hook
require 'citier/acts_as_citier'

# Methods that override ActiveRecord::Relation
require 'citier/relation_methods'
require 'citier/relation_methods'
116 changes: 61 additions & 55 deletions lib/citier/child_instance_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,23 @@ module ChildInstanceMethods

def save(options={})
return false if (options[:validate] != false && !self.valid?)

#citier_debug("Callback (#{self.inspect})")
citier_debug("SAVING #{self.class.to_s}")

#AIT NOTE: Will change any protected values back to original values so any models onwards won't see changes.
# Run save and create/update callbacks, just like ActiveRecord does
self.run_callbacks(:save) do
self.run_callbacks(self.new_record? ? :create : :update) do
#get the attributes of the class which are inherited from it's parent.
attributes_for_parent = self.attributes.reject { |key,value| !self.class.superclass.column_names.include?(key) }
changed_attributes_for_parent = self.changed_attributes.reject { |key,value| !self.class.superclass.column_names.include?(key) }
attributes = self.instance_variable_get(:@attributes)
changed_attributes = self.instance_variable_get(:@changed_attributes)
attributes_for_parent = attributes.reject { |key,value| !self.class.superclass.column_names.include?(key) }
changed_attributes_for_parent = changed_attributes.reject { |key,value| !self.class.superclass.column_names.include?(key) }

# Get the attributes of the class which are unique to this class and not inherited.
attributes_for_current = self.attributes.reject { |key,value| self.class.superclass.column_names.include?(key) }
changed_attributes_for_current = self.changed_attributes.reject { |key,value| self.class.superclass.column_names.include?(key) }
attributes_for_current = attributes.reject { |key,value| self.class.superclass.column_names.include?(key) }
changed_attributes_for_current = changed_attributes.reject { |key,value| self.class.superclass.column_names.include?(key) }

citier_debug("Attributes for #{self.class.superclass.to_s}: #{attributes_for_parent.inspect}")
citier_debug("Changed attributes for #{self.class.superclass.to_s}: #{changed_attributes_for_parent.keys.inspect}")
Expand All @@ -27,76 +29,80 @@ def save(options={})
########
#
# Parent saving

#create a new instance of the superclass, passing the inherited attributes.
parent = self.class.superclass.new

parent.force_attributes(attributes_for_parent, :merge => true)
changed_attributes_for_parent["id"] = 0 # We need to change at least something to force a timestamps update.
parent.force_changed_attributes(changed_attributes_for_parent)

parent.id = self.id if id
parent.type = self.type

parent.is_new_record(new_record?)
# If we're root (AR subclass) this will just be saved as normal through AR. If we're a child it will call this method again.

# If we're root (AR subclass) this will just be saved as normal through AR. If we're a child it will call this method again.
# It will try and save it's parent and then save itself through the Writable constant.
parent_saved = parent.save
self.id = parent.id

if !parent_saved
# Couldn't save parent class
citier_debug("Class (#{self.class.superclass.to_s}) could not be saved")
citier_debug("Errors = #{parent.errors.to_s}")
return false # Return false and exit run_callbacks :save and :create/:update, so the after_ callback won't run.
end

#End of parent saving

######
##
## Self Saving
##

# If there are attributes for the current class (unique & not inherited), save current model
if !attributes_for_current.empty?
current = self.class::Writable.new

current.force_attributes(attributes_for_current, :merge => true)
current.force_changed_attributes(changed_attributes_for_current)

current.id = self.id
current.is_new_record(new_record?)

current_saved = current.save

current.after_save_change_request if current.respond_to?('after_save_change_request') #Specific to an app I'm building

if !current_saved
parent.transaction do
parent_saved = parent.save
self.id = parent.id
self.created_at ||= parent.created_at # should set the generated timestamps to view object
self.updated_at ||= parent.updated_at

if !parent_saved
# Couldn't save parent class
citier_debug("Class (#{self.class.superclass.to_s}) could not be saved")
citier_debug("Errors = #{current.errors.to_s}")
return false # Return false and exit run_callbacks :save and :create/:update, so the after callback won't run.
citier_debug("Errors = #{parent.errors.to_s}")
return false # Return false and exit run_callbacks :save and :create/:update, so the after_ callback won't run.
end

#End of parent saving

######
##
## Self Saving
##

# If there are attributes for the current class (unique & not inherited), save current model
if !attributes_for_current.empty?
current = self.class::Writable.new

current.force_attributes(attributes_for_current, :merge => true)
current.force_changed_attributes(changed_attributes_for_current)

current.id = self.id
current.is_new_record(new_record?)

current_saved = current.save

current.after_save_change_request if current.respond_to?('after_save_change_request') #Specific to an app I'm building

if !current_saved
citier_debug("Class (#{self.class.superclass.to_s}) could not be saved")
citier_debug("Errors = #{current.errors.to_s}")
return false # Return false and exit run_callbacks :save and :create/:update, so the after callback won't run.
end
end
end

# at this point, parent_saved && current_saved

is_new_record(false) # This is no longer a new record
# at this point, parent_saved && current_saved

is_new_record(false) # This is no longer a new record

self.force_changed_attributes({}) # Reset changed_attributes so future changes will be tracked correctly

self.force_changed_attributes({}) # Reset changed_attributes so future changes will be tracked correctly

# No return, because we want the after callback to run.
# No return, because we want the after callback to run.
end
end
end
return true
end

def save!(options={})
raise ActiveRecord::RecordInvalid.new(self) if (options[:validate] != false && !self.valid?)
self.save || raise(ActiveRecord::RecordNotSaved)
end

include InstanceMethods
end
end
end
9 changes: 8 additions & 1 deletion lib/citier/core_ext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ def self.create_class_writable(class_reference) #creation of a new class which
# set the name of the table associated to this class
# this class will be associated to the writable table of the class_reference class
self.table_name = t_name

class_attribute :view_class, :writable_serialized_attributes
self.view_class = class_reference

def self.serialized_attributes
self.writable_serialized_attributes ||= view_class.serialized_attributes.reject { |key, value| view_class.superclass.column_names.include?(key) }
end
end
end
end
Expand All @@ -51,7 +58,7 @@ def create_citier_view(theclass) #function for creating views for migrations
self_read_table = theclass.table_name
self_write_table = theclass::Writable.table_name
parent_read_table = theclass.superclass.table_name
select_sql = "SELECT #{parent_read_table}.id, #{columns.map { |c| "\"#{c}\"" }.join(',')} FROM #{parent_read_table}, #{self_write_table} WHERE #{parent_read_table}.id = #{self_write_table}.id"
select_sql = "SELECT #{parent_read_table}.id, #{columns.map { |c| theclass.connection.quote_column_name(c) }.join(',')} FROM #{parent_read_table}, #{self_write_table} WHERE #{parent_read_table}.id = #{self_write_table}.id"
sql = "CREATE VIEW #{self_read_table} AS #{select_sql}"

#Use our rails_sql_views gem to create the view so we get it outputted to schema
Expand Down
Loading