GithubHelp home page GithubHelp logo

Comments (9)

palkan avatar palkan commented on July 23, 2024 2

CultOfMartians task http://cultofmartians.com/tasks/logidze-associations.html

from logidze.

palkan avatar palkan commented on July 23, 2024 1

I read your implementation tips on the Cult of Martians site. Do you want to somehow override ActiveRecord associations getters? Or you meant some other approach?

I think, we can patch load_target method to modify loaded items.

Also, to reduce global monkey-patching, we can patch association instances in-place, prepending some module into an instance of association in association method.

Smth like:

# this method is called on our model object, which `has_logidze`
def association(*args)
  res = super
  # only patch association if we request some past state
  res.singleton_class.prepend Logidze::VersionedAssociation if in_the_past?
  res
end

from logidze.

palkan avatar palkan commented on July 23, 2024

@ezubairov Thanks for the request!

I was thinking about this feature too, but was not sure whether it's useful or not. Now I see – it is.

So, I'll share the task on http://cultofmartians.com with some thoughts on implementation; I think, we'll find someone to handle this)

from logidze.

charlie-wasp avatar charlie-wasp commented on July 23, 2024

@palkan at last I found time to dig through this task

General question:

I read your implementation tips on the Cult of Martians site. Do you want to somehow override ActiveRecord associations getters? Or you meant some other approach?

from logidze.

charlie-wasp avatar charlie-wasp commented on July 23, 2024

@palkan hello, I think, I got it at last. Got it somewhere at least :)

It took me a while to get familiar with Rails inner association logic, I spent some time tinkering with it to just understand the clues you gave :)

Unfortunately, there is a trouble with the collection associations, particularly with the size method. It doesn't call load_target, if target wasn't loaded in memory already, it executes SQL count statement instead (as far as I understand it). So we can't hook into that with load_target.

May I open the pull request, so we could discuss the actual code?

from logidze.

palkan avatar palkan commented on July 23, 2024

May I open the pull request, so we could discuss the actual code?

Yes, of course.

... particularly with the size method

We can ignore such methods at first (and add a note about it to the README).
But that's an important point, thanks for figuring it out! 👍

from logidze.

NullVoxPopuli avatar NullVoxPopuli commented on July 23, 2024

huh, this could be handy, depending on how well it behaves. Here is what I've come up with in the mean time.

Having issues with weird log_data in tests though... like overly nested

 {"c"=>
         {"h"=>
           [{"c"=>
# frozen_string_literal: true
#
# This is for manually creating versions using logdize's technique
#
# The automatic version on update has been disabled (but could be enabled by
# running a migration that updated the trigger).
# This is to ensure that any version, other than the first version, is
# always created by our code.
# It also means that we can use the same version for every association -- which
# simplifies relations
#
# Note that the latest version on the log_data will always be the most recent
# snapshot, and may not reflect the current state
# So, record.log_version will always be one less than the number of versions.
# E.g.: record.log_size # => 3 -- but the total versions are 3 + [current]
#
# NOTE: gem documentation: https://github.com/palkan/logidze
module Versionable
  extend ActiveSupport::Concern

  included do
    has_logidze

    # set the initial version
    # after_create :save_version!
    # - this happens via SQL TRIGGER
    #
    # increment version
    # before_save :save_version!
    # - this is not automatic, because we need to
    #   have a consistent version across relationships

    # Class Methods
    class << self
      # for retreiving a relationship of this (whatever includes this module)
      # requiring each member to match the provided version number or
      # datetime of the version
      #
      # @param [Number] version
      # @param [Time] time
      def at(version: nil, time: nil)
        # TODO: find a more efficient way to do this
        all.map do |current|
          time ? current.at!(time) : current.at_version!(version)
        end
      end

      private

      # Iterate over the associations, and add a method that will retreive
      # the relationship at the same version as the calling object.
      # That way we don't need to manually carry the version through our
      # relation call chain.
      #
      # More information on reflections
      # http://api.rubyonrails.org/classes/ActiveRecord/Reflection/ClassMethods.html#method-i-reflections
      def build_versioned_reflections!
        reflections.each do |association_name, reflection|
          # is this a versioned reflection?
          next unless reflection.klass.has_attribute?(:log_data)

          define_method("#{association_name}_at_same_version") do
            send(association_name).at(time: time_of_version)
          end
        end
      end
    end

    # ...Metaprogramming... Wooosh
    build_versioned_reflections!
  end

  # @return [NilClass|Time]
  def time_of_version
    ts = log_data&.find_by_version(version)&.data&.fetch('ts')
    Time.at(ts) if ts
  end

  def save_version!
    save_version
    reload
  end

  # For saving logdize historical versions manually.
  # Be sure that logdize has logging turned off globally
  #
  # TODO: have configurable parameter exclusions, such as draft_id
  def save_version
    return false unless has_attribute?(:log_data)
    # return false unless log_data.present?

    klass = self.class
    klass.connection.execute(%(
      SELECT make_snapshot(#{id}, '#{klass.table_name}');
    ))
  end

  # Slightly shorter than log_version, and won't error if there isn't (yet) log_data.
  # log_data is only populated on save, before executing the insert or update statement
  # on the db side
  def version
    log_data ? log_version : 0
  end
end

from logidze.

charlie-wasp avatar charlie-wasp commented on July 23, 2024

@palkan what we are going to do with this issue?

from logidze.

charlie-wasp avatar charlie-wasp commented on July 23, 2024

@ezubairov hello!

Today PR, addressing this issue, was merged, you can read about it in the wiki page

from logidze.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.