2011-03-10 3 views
2

Je voudrais être en mesure d'ajouter des informations "méta" à un modèle, essentiellement des champs définis par l'utilisateur. Ainsi, par exemple, imaginons un modèle d'utilisateur:Rails méta-modèle personnalisé?

Je définis des champs pour le prénom, le nom, l'âge, le sexe. Je voudrais que les utilisateurs puissent définir des "méta-informations", essentiellement pour aller dans leur page de profil et partager d'autres informations. Ainsi, un utilisateur peut vouloir ajouter "hobbies", "occupation", et "ville natale", et un autre pourrait vouloir ajouter "hobbies", et "éducation". Alors

, je voudrais être en mesure d'avoir une vue standard pour ce genre de choses, donc par exemple dans la vue que je pourrais faire quelque chose comme (en HAML):

- for item in @meta 
    %li 
    %strong= item.key + ":" 
    = item.value 

De cette façon, je peut garantir que les informations sont affichées de manière cohérente, plutôt que de simplement fournir à l'utilisateur une zone de texte de démarquage qu'il peut formater de différentes manières. Je voudrais aussi pouvoir cliquer sur meta et voir d'autres utilisateurs qui ont donné la même chose, donc dans l'exemple ci-dessus les deux utilisateurs ont défini "hobbies", ce serait bien de pouvoir dire que je veux pour voir les utilisateurs qui ont des hobbies partagés - ou mieux encore je veux voir les utilisateurs dont les hobbies sont _ __.

Donc, puisque je ne sais pas quels champs les utilisateurs voudront définir à l'avance, quelles sont les options pour fournir ce genre de fonctionnalités?

Existe-t-il une gemme qui gère les méta-informations personnalisées sur un modèle comme celui-ci, ou du moins de la même manière? Quelqu'un a-t-il eu de l'expérience avec ce genre de problème? Si oui, comment l'avez-vous résolu?

Merci!

Répondre

3

Si chaque utilisateur est autorisé à définir ses propres attributs, une option peut être d'avoir une table avec trois colonnes: user_id, attribute_name, attribute_value. Il pourrait ressembler à:

| user_id | attribute_name | attribute_value | 
| 2  | hobbies  | skiing   | 
| 2  | hobbies  | running   | 
| 2  | pets   | dog    | 
| 3  | hobbies  | skiing   | 
| 3  | colours  | green   | 

Ce tableau sera utilisé pour trouver d'autres utilisateurs qui ont les mêmes passe-temps/animaux/etc.

Pour des raisons de performances (cette table va grossir), vous pouvez conserver plusieurs emplacements dans lesquels l'information est stockée - différentes sources d'informations à des fins différentes. Je ne pense pas qu'il soit mauvais de stocker la même information dans plusieurs tables si cela est absolument nécessaire pour la performance.

Tout dépend de la fonctionnalité dont vous avez besoin. Peut-être finira-t-il par comprendre que chaque paire clé/valeur de chaque utilisateur est sérialisée dans une colonne de chaîne sur la table des utilisateurs (Rails fournit un bon support pour ce type de sérialisation), donc quand vous affichez des informations pour un utilisateur particulier même besoin de toucher la grande table. Ou peut-être vous finirez par avoir une autre table qui ressemble à ceci:

| user_id | keys    | values    | 
| 2  | hobbies, pets | skiing, running, dog | 
| 3  | hobbies, colours | skiing, green  | 

Ce tableau serait utile si vous avez besoin de trouver tous les utilisateurs qui ont passe-temps (courir comme sql contre la colonne clés), ou tous les utilisateurs avoir quelque chose à voir avec un chien (lancer LIKE sql contre la colonne des valeurs).

C'est la meilleure réponse que je peux donner aux exigences que vous avez données. Peut-être qu'il existe une solution tierce disponible, mais je suis sceptique. Ce n'est pas vraiment un type de problème "pop dans une gemme".

+0

Cela semble être une très bonne solution. Merci! – Andrew

2

Dans ce cas, je considérerais au moins un documentdb comme mongo ou canapé, qui peut gérer ce type de scénario beaucoup plus facilement qu'un rdms.

Si ce n'est pas le cas, je finirais probablement par faire quelque chose dans le sens de ce que Mike A. a décrit.

7

La mise en œuvre du champ dynamique dépend de facteurs suivants:

  1. Possibilité d'ajouter dynamiquement des attributs
  2. Capacité à soutenir les nouveaux types de données
  3. Possibilité de récupérer les attributs dynamiques sans requête supplémentaire
  4. Capacité à accéder aux attributs dynamiques comme les attributs normaux
  5. Possibilité d'interroger les objets en fonction des attributs dynamiques. (par exemple: trouver les utilisateurs avec loisirs de ski)

Typiquement, une solution ne répond pas à toutes les exigences. La solution de Mike adresse 1 et 5 élégamment. Vous devriez utiliser sa solution si 1 & 5 sont importants pour vous.

est ici une solution à long qui traite 1,2,3, 4 et 5

Mise à jour de la table users

Ajouter un champ text appelé meta à la table des utilisateurs.

Mettez à jour votre modèle User

class User < ActiveRecord::Base 
    serialize :meta, Hash 

    def after_initialize 
    self.meta ||= {} if new_record? 
    end 

end 

Ajout d'un nouveau champ méta

u = User.first 
u.meta[:hobbies] = "skiing" 
u.save 

Accès à un champ méta

puts "hobbies=#{u.meta[:hobbies]}" 

Itération les champs méta

u.meta.each do |k, v| 
    puts "#{k}=#{v}" 
end 

Pour répondre au 5ème exigence que vous devez utiliser Solr Ou Sphinx moteurs de recherche en texte intégral. Ils sont efficaces que de s'appuyer sur DB pour LIKE requêtes.

Voici une approche si vous utilisez Solr via Sunspot gem.

class User  
    searchable do 
    integer(:user_id, :using => :id) 
    meta.each do |key, value| 
     t = solr_type(value) 
     send(t, key.to_sym) {value} if t 
    end 
    end 

    def solr_type(value) 
    return nil  if value.nil? 
    return :integer if value.is_a?(Fixnum) 
    return :float if value.is_a?(Float) 
    return :string if value.is_a?(String) 
    return :date if value.is_a?(Date) 
    return :time if value.is_a?(Time) 
    end  

    def similar_users(*args) 
    keys = args.empty? ? meta.keys : [args].flatten.compact 
    User.search do 
     without(:user_id, id) 
     any_of do 
     keys.each do |key| 
      value = meta[key] 
      with(key, value) if value 
     end 
     and 
    end 
    end 
end 

La recherche d'utilisateurs similaires

u = User.first 
u.similar_users # matching any one of the meta fields 
u.similar_users :hobbies # with matching hobbies 
u.similar_users :hobbies, :city # with matching hobbies or the same city 

Le gain de performance ici est importante.

+0

C'est une très belle approche, j'apprécie vraiment que vous le partagiez! – Andrew

Questions connexes