How can I transform linked records into a hash?

By : Technoh
Source: Stackoverflow.com
Question!

I am building an application with a database table for games. All games have different attributes, although some attributes are the same for each game, mostly name and type. I store all the attributes in a separate table so that they can be searchable. Every game has a has_many relationship to attributes.

Table games
id       int     primary key
name     string  unique
type     string

Table attributes
id       int     primary_key
game_id  int
name     string
value    text

For example the game "Nyan Cat Adventures in Space" (made up name) could have the following attributes:

id   game_id    name      value
4    344        dlc       99
5    344        packages  2209
6    344        language  "Space Cat"
7    344        dlc       551

I need the attributes to be fully searchable (and indexed). I would like to be able to transform the attributes into a hash so that I could access certain attributes like this: game.attributes['dlc']. Note however that some attributes can have the same name. What is the best way to achieve this, should I extend ActiveRecord::Base? Is there a function or callback I can use?

By : Technoh


Answers

If you have a list of records like games = Game.all and Game objects have associated DLC objects, you can do this:

game_hashes_with_dlc_info = games.map do |game|
  game.attributes.merge(dlc: game.dlc.map(&:attributes))
end

in response to your comment

Given a hash like { foo: "1", foo: "2", bar: "1", bar: "2" }

that you want to turn into { foo: ["1", "2"], bar: ["1", "2"] }

hash_1.reduce({}) do |new_hash, (hash_1_key, hash_1_val)|
  if new_hash.has_key? hash_1_key
    new_hash[hash_1_key].push(hash_1_val)
  else
    new_hash[hash_1_key] = [hash_1_val]
  end
  new_hash
end

this has the effect of making all the hash values arrays, which may not be what you want.



CRTP.

template<class Storage>
class FooReaderImpl {
public:
  FooData readFooDataAndAdvance() {
    // the point here is that the algorithm is stateful
    // and relies upon the m_offset member
    return get_storage()->m_foo[get_storage()->m_offset++];
  }
private:
  Storage const* get_storage() const { return static_cast<Storage const*>(this); }
  Storage * get_storage() { return static_cast<Storage*>(this); }
};
template<class Storage>
class FooWriterImpl {
public:
  void writeFooDataAndAdvance(const FooData& foodata) {
    // the point here is that the algorithm is stateful
    // and relies upon the m_offset member
    get_storage()->m_foo[get_storage()->m_offset++] = foodata;
  }
private:
  Storage const* get_storage() const { return static_cast<Storage const*>(this); }
  Storage * get_storage() { return static_cast<Storage*>(this); }
};

template<class T>
struct storage_with_offset {
  T* m_foo = nullptr;
  std::size_t m_offset = 0;
};
struct FooReader:
  FooReaderImpl<FooReader>,
  storage_with_offset<const Foo>
{
  FooReader(Foo const* p):
    storage_with_offset<const Foo>{p}
  {}
};
struct FooWriter:
  FooWriterImpl<FooWriter>,
  storage_with_offset<Foo>
{
  FooWriter(Foo* p):
    storage_with_offset<Foo>{p}
  {}
};
struct FooReaderWriter:
  FooWriterImpl<FooReaderWriter>,
  FooReaderImpl<FooReaderWriter>,
  storage_with_offset<Foo>
{
  FooReaderWriter(Foo const* p):
    storage_with_offset<Foo>{p}
  {}
};

If you need an abstract interface for runtime polymorphism, inherit FooReaderImpl and FooWriterImpl from them.

Now, FooReaderWriter obeys the ducktype contract of FooReader and FooWriter. So if you use type erasure instead of inheritance, it will qualify for either (at point of use).

I'd be tempted to change them to

using FooReader = std::function<FooData()>;
using FooWriter = std::function<void(FooData const&)>;

and then implement a multi-signature std::function for FooReaderWriter. But I'm strange and a bit unhinged that way.

By : Yakk


One simplistic solution is to recreate the table, e.g.

CREATE TABLE my_temp_table (
    -- add column definitions here, just like the original table
);
INSERT INTO my_temp_table SELECT DISTINCT * FROM original_table;
DROP TABLE original_table;
ALTER TABLE my_temp_table RENAME TO original_table;

or even

CREATE TABLE my_temp_table AS SELECT DISTINCT * FROM original_table;
DROP TABLE original_table;
ALTER TABLE my_temp_table RENAME TO original_table;
By : redneb


This video can help you solving your question :)
By: admin