granite icon indicating copy to clipboard operation
granite copied to clipboard

Psuedo Arrays

Open elaine-jackson opened this issue 4 years ago • 3 comments

Hi, at my organization (@ulayer) we are interested in the ability for Granite to have a sort of Psuedo Arrays to avoid needing to write complex custom setter and getter methods.

Context: Our users want to be able to share their Virtual Machines with trusted contacts. To accomplish that we currently have a String field that looks like @allowed_clients = "1,2,5,6,8," and so forth. Our model for Virtual Machines has two methods like:

##
  # Get Allowed Clients
  ##
  def get_allowed_clients
    numbers = [] of Int32
    clients = [] of BlestaClient

    if a = @allowed_clients
      nums_arr = a.split(',')
    else
      return nil
    end

    if (nums = nums_arr)
      nums.pop
      nums.each do |n|
        if (num = n.to_i32)
          numbers.push(num)
        end
      end
    else
      return nil
    end

    numbers.each do |num|
      if (client = BlestaClient.find_by(id_value: num))
        clients.push(client)
      end
    end

    return clients
  end

  ##
  # Set Allowed Clients
  ##
  def set_allowed_clients(clients_string)
    valid_clients = [] of Int32
    valid_clients_string = ""
    if ((clients = clients_string) && (clients_array = clients.split(",")))
      clients_array.each do |num|
        if ((client = BlestaClient.find_by(id_value: num)) && (client_id = client.id_value))
          valid_clients.push(client_id)
        end
      end
    end
    valid_clients.each do |client|
      valid_clients_string = "#{valid_clients_string}#{client},"
    end
    @allowed_clients = valid_clients_string
  end

  ##
  #  Check if Client is allowed
  ##
  def check_if_client_is_allowed(client_id)
    numbers = [] of Int32

    if a = @allowed_clients
      nums_arr = a.split(',')
    else
      return nil
    end

    if (nums = nums_arr)
      nums.pop
      nums.each do |n|
        if (num = n.to_i32)
          numbers.push(num)
        end
      end
    else
      return nil
    end

    numbers.each do |num|
      if (client_id == num)
        return true
      end
    end

    return nil
  end

We would like to be able to use the storage of text/strings in the database to have "val,val,val,val" but still take advantage of Crystal's type casting. Instead of having column allowed_clients : String? we would have column allowed_clients : Array(Int32) and Granite perform the underlying storage and conversions for us. Array method like .includes?(value) would by extension work.

What are your thoughts on this?

elaine-jackson avatar Sep 21 '19 21:09 elaine-jackson

We talked about this on Gitter. Granite currently supports array if the underlying database driver does as well, i.e. Postgres, in the form of column values : Array(Int32).

This however could be documented if someone wants to make a PR.

Blacksmoke16 avatar Sep 21 '19 22:09 Blacksmoke16

What about including this as a fallback for MySQL?

elaine-jackson avatar Sep 22 '19 21:09 elaine-jackson

@nsuchy You could just use a TEXT column with a JSON converter. I.e.

column details : Array(Int32), column_type: "TEXT", converter: Granite::Converters::Json(Array(Int32), String)

Which would save the array as JSON like "[1,2,3]", then read it from the JSON string as a Array(Int32).

Blacksmoke16 avatar Sep 22 '19 21:09 Blacksmoke16