motion-prime/models/sync.rb in motion-prime-0.2.1 vs motion-prime/models/sync.rb in motion-prime-0.3.0

- old
+ new

@@ -4,13 +4,13 @@ base.class_attribute :_sync_url base.class_attribute :_updatable_attributes base.class_attribute :_associations end - def sync_url(method = :get) + def sync_url(method = :get, options = {}) url = self.class.sync_url - url = url.call(method, self) if url.is_a?(Proc) + url = url.call(method, self, options) if url.is_a?(Proc) normalize_sync_url(url) end # destroy on server and delete on local def destroy(&block) @@ -36,106 +36,128 @@ sync_options[:fetch_associations] else # do not need to fetch unless this is a GET request should_fetch end - method = if should_update + method = sync_options[:method] + method ||= if should_update persisted? ? :put : :post else :get end - url = sync_url(method) + url = sync_url(method, sync_options) if url.blank? should_fetch = false should_update = false end should_fetch = !new_record? if should_fetch.nil? should_update ||= new_record? unless should_fetch - fetch_with_url url do + fetch_with_url url do |data, status_code| save if sync_options[:save] - block.call if use_callback + block.call(data, status_code) if use_callback end if should_fetch update_with_url url, sync_options do |data, status_code| save if sync_options[:save] && status_code.to_s =~ /20\d/ # run callback only if it wasn't run on fetch block.call(data, status_code) if use_callback && !should_fetch end if should_update - fetch_associations(sync_options) do + fetch_associations(sync_options) do |data, status_code| # run callback only if it wasn't run on fetch or update - block.call if use_callback && !should_fetch && !should_update + block.call(data, status_code) if use_callback && !should_fetch && !should_update end if should_fetch_associations end # fetch from server using url def fetch_with_url(url, &block) - api_client.get(url) do |data| - if data.present? - fetch_with_attributes(data, &block) - end + use_callback = block_given? + api_client.get(url) do |data, status_code| + fetch_with_attributes(data, &block) if data.present? + block.call(data, status_code) if use_callback end end # update on server using url def update_with_url(url, sync_options = nil, &block) use_callback = block_given? - post_data = { model_name => filtered_updatable_attributes(sync_options)} - api_client.send(id ? :put : :post, url, post_data) do |data, status_code| - if status_code.to_s =~ /20\d/ && data.is_a?(Hash) - self.id ||= data['id'] - accessible_attributes = self.class.attributes.map(&:to_sym) - [:id] - attrs = data.symbolize_keys.slice(*accessible_attributes) - fetch_with_attributes(attrs) + filtered_attributes = filtered_updatable_attributes(sync_options) + + post_data = sync_options[:params_root] || {} + post_data[:files] = {} + filtered_attributes.delete(:files).each do |file_name, file| + post_data[:files][[model_name, file_name].join] = file + end + post_data[model_name] = filtered_attributes + + method = sync_options[:method] || (id ? :put : :post) + api_client.send(method, url, post_data) do |data, status_code| + update_from_response = sync_options.has_key?(:update_from_response) ? sync_options[:update_from_response] : true + if update_from_response && status_code.to_s =~ /20\d/ && data.is_a?(Hash) + set_attributes_from_response(data) end block.call(data, status_code) if use_callback end end + def set_attributes_from_response(data) + self.id ||= data.delete('id') + fetch_with_attributes(data) + end + # set attributes, using fetch - def fetch_with_attributes(attrs, &block) + def fetch_with_attributes(attrs) attrs.each do |key, value| if respond_to?(:"fetch_#{key}") self.send(:"fetch_#{key}", value) elsif respond_to?(:"#{key}=") self.send(:"#{key}=", value) end end - block.call(self) if block_given? end def fetch_associations(sync_options = {}, &block) use_callback = block_given? associations = self.class._associations || {} + association_keys = associations.keys.select { |key| fetch_association?(key) } - associations.keys.each_with_index do |key, index| + association_keys.each_with_index do |key, index| if use_callback && associations.count - 1 == index fetch_association(key, sync_options, &block) else fetch_association(key, sync_options) end end end + def fetch_association?(key) + options = self.class._associations[key] + return if options[:if] && !options[:if].to_proc.call(self) + options[:sync_url].present? + end + def fetch_association(key, sync_options = {}, &block) + return unless fetch_association?(key) options = self.class._associations[key] - return unless options[:sync_url] - options[:type] == :many ? - fetch_has_many(key, options, sync_options, &block) : - fetch_has_one(key, options, sync_options, &block) + if options[:type] == :many + fetch_has_many(key, options, sync_options, &block) + else + fetch_has_one(key, options, sync_options, &block) + end end def fetch_has_many(key, options = {}, sync_options = {}, &block) old_collection = self.send(key) + use_callback = block_given? - puts "SYNC: started sync for #{key} in #{self.class.name}" - api_client.get normalize_sync_url(options[:sync_url]) do |data| + puts "SYNC: started sync for #{key} in #{self.class_name_without_kvo}" + api_client.get normalize_sync_url(options[:sync_url]) do |data, status_code| data = data[options[:sync_key]] if options[:sync_key] - if data.present? + if data # Update/Create existing records data.each do |attributes| model = old_collection.detect{ |model| model.id == attributes[:id]} unless model model = key.singularize.to_s.classify.constantize.new @@ -149,57 +171,67 @@ unless model old_model.delete end end save if sync_options[:save] - puts "SYNC: finished sync for #{key} in #{self.class.name}" - block.call if use_callback + puts "SYNC: finished sync for #{key} in #{self.class_name_without_kvo}" + block.call(data, status_code) if use_callback else - puts "SYNC ERROR: failed sync for #{key} in #{self.class.name}" - block.call if use_callback + puts "SYNC ERROR: failed sync for #{key} in #{self.class_name_without_kvo}" + block.call(data, status_code) if use_callback end end end def fetch_has_one(key, options = {}, &block) use_callback = block_given? - puts "SYNC: started sync for #{key} in #{self.class.name}" - api_client.get normalize_sync_url(options[:sync_url]) do |data| + puts "SYNC: started sync for #{key} in #{self.class_name_without_kvo}" + api_client.get normalize_sync_url(options[:sync_url]) do |data, status_code| data = data[options[:sync_key]] if options[:sync_key] if data.present? model = self.send(key) unless model model = key.singularize.to_s.classify.constantize.new self.send(:"#{key}_bag") << model end model.fetch_with_attributes(data) model.save if sync_options[:save] - block.call if use_callback + block.call(data, status_code) if use_callback else - puts "SYNC ERROR: failed sync for #{key} in #{self.class.name}" - block.call if use_callback + puts "SYNC ERROR: failed sync for #{key} in #{self.class_name_without_kvo}" + block.call(data, status_code) if use_callback end end end def filtered_updatable_attributes(options = {}) slice_attributes = options[:updatable_attributes].map(&:to_sym) if options.has_key?(:updatable_attributes) updatable_attributes = self.class.updatable_attributes if updatable_attributes.blank? - return slice_attributes ? attributes_hash.slice(*slice_attributes) : attributes_hash + attrs = slice_attributes ? attributes_hash.slice(*slice_attributes) : attributes_hash + return attrs.merge(files: {}) end updatable_attributes = updatable_attributes.slice(*slice_attributes) if slice_attributes - updatable_attributes.to_a.inject({}) do |hash, attribute| + updatable_attributes.to_a.inject({files: {}}) do |hash, attribute| key, options = *attribute - return hash if options[:if] && !send(options[:if]) + next hash if options[:if] && !send(options[:if]) value = if block = options[:block] - block.call(self) + block.call(self, hash) else info[key] end - hash.merge(key => value) + + if key.to_s.starts_with?('file_') + value.to_a.each do |file_data| + file_name, file = file_data.to_a + hash[:files]["[#{key.partition('_').last}]#{file_name}"] = file + end + else + hash.merge!(key => value) + end + hash end end def normalize_sync_url(url) url.to_s.gsub(':id', id.to_s) \ No newline at end of file