2

I have a Groups model that has_many Topics. The Topics model has_many Posts, my Users model only read posts etc. I also have a users_posts model which is used to keep track whether a user has read the post and whether they 'liked' the post, it has :group_id, :user_id and :read and :liked boolean fields. A user creates a group and adds posts under different topics.

The Groups show action is; @posts = Group.topics.includes(:posts)

My question is when and how do I add records to the users_posts table? Should it be when a post is first created? or only when a user views the post for the first time?

and what is the best way to add the attributes from user_posts to each record in @posts? at the minute I'm using virtual attributes, is this the right way?

class Post < ActiveRecord::Base
  belongs_to :topic
  has_many :users, through: :user_entries
  attr_accessor :read, :liked
raphael_turtle
  • 7,154
  • 10
  • 55
  • 89

1 Answers1

2

ID

Firstly, you won't need to use :group_id in your user_posts model / table

Rails, and relational databases, use primary keys to give elements unique id's. This means regardless of whether a post is a member of a group, you'll still be referencing it with the post_id foreign key -

#users_posts
id | user_id | post_id | read | updated | created_at | updated_at

--

Attributes

add the attributes from user_posts to each record in @posts

The way to do this will be to associate user_posts with the User and Post models -

#app/models/user.rb
Class User < ActiveRecord::Base
   has_many :user_posts
   has_many :posts, through: :user_posts
end

#app/models/post.rb
Class Post < ActiveRecord::Base
   has_many :user_posts
   has_many :users, through: :user_posts
end

This will allow you to call @user.posts

If you want to attach extra attributes to each post associative object, you'll need to either use an ALIAS SQL join, or an ActiveRecord Association Extension:

#app/models/post.rb
Class User < ActiveRecord::Base
   has_many :user_posts
   has_many :posts, through: :user_posts, extend: PostUser
end

#app/models/concerns/post_user.rb
module PostUser

    #Load
    def load
        reads.each do |caption|
            proxy_association.target << read
        end
    end

    #Private
    private

    #Attributes
    def captions
        return_array = []
        through_collection.each_with_index do |through,i|
            associate = through.send(reflection_name)
            associate.assign_attributes({read: items[i]}) if items[i].present?
            return_array.concat Array.new(1).fill( associate )
        end
        return_array
    end

    #######################
    #      Variables      #
    #######################

    #Association
    def reflection_name
        proxy_association.source_reflection.name
    end

    #Foreign Key
    def through_source_key
        proxy_association.reflection.source_reflection.foreign_key
    end

    #Primary Key
    def through_primary_key
        proxy_association.reflection.through_reflection.active_record_primary_key
    end

    #Through Name
    def through_name
        proxy_association.reflection.through_reflection.name
    end

    #Through
    def through_collection
        proxy_association.owner.send through_name
    end

    #Captions
    def items
        through_collection.map(&:read)
    end

    #Target
    def target_collection
        #load_target
        proxy_association.target
    end

end

--

System

Bottom line is I think your system will be best run like this:

1. Set up a semi-persistent data store to track user / post reads (REDIS)
2. Every time you call a `Post`, you'll be able to call the associated `REDIS` references for it

This will give you the ability to create a system which is firstly modular, but you can also create an instance method to determine whether a user has read the post or not, like this:

#app/models/post.rb
Class Post < ActiveRecord::Base
   def read?
      ##Redis lookup for user with post - if "read" attribute is true, return true
   end

   def liked?
      ##Redis lookup for user with post - if "liked" attribute is true, return true
   end
end

This will allow you to run @user.posts.first.read?

Community
  • 1
  • 1
Richard Peck
  • 76,116
  • 9
  • 93
  • 147
  • 1
    thanks for the detailed answer but a user will only see posts within the context of the group, users don't create posts only read them, they come from an external source. The redis solution looks like the best way to handle likes etc. – raphael_turtle Jun 27 '14 at 10:36
  • also why do you say the redis store is semi persistent? – raphael_turtle Jun 27 '14 at 10:41
  • Hey Raphael, being honest, I was a little confused with the setup, so I just figured I'd post anything which may help – Richard Peck Jun 27 '14 at 10:41
  • Because Redis is basically like RAM - it's only meant to store data for a period, before committing to a permanent way of keeping it. That's how I see it anyway (it's why lots of the queuing systems in Rails are done with Redis) – Richard Peck Jun 27 '14 at 10:42
  • Further to my last comment - the "RAM" idea would be like if you wanted to keep the likes for the current month. You'll keep them in a REDIS instance, and then after the month, commit them to the DB – Richard Peck Jun 27 '14 at 10:47