• No results found

The current setup for the feed reader application looks like the typical Rails deploy- ment mentioned earlier. There is an application server with background processing. The logic for sending out emails and performing updates of feeds occurs in these back- ground jobs. Everything is tied together fairly tightly. There are separate models for the emails and the feeds, but the logic of actually running these tasks is held within a single process.

The code in the social reader application looks like a regular Rails application. There are models, controllers, and views. When looking at services, most of what gets pulled into a service is the code and logic that lies at the model level. It helps to have in mind what the model schema and relationships for the data look like. The follow- ing sections outline the models in the application. Each one is annotated with its data- base schema using the annotate models gem (see http://github.com/ctran/ annotate_models/tree/master).

ptg

The User Model

The user model is the common starting place for most applications. Many of the other models are tied in some way to the user model, and the social feed reader application is no different. Every model can be reached through an association on the user model. When breaking up the data into services, some of these associations will have to cross service boundaries. The user model looks as follows:

class User < ActiveRecord::Base has_many :follows

has_many :followed_users, :through => :follows has_many :followings, :class_name => "Follow",

:foreign_key => :followed_user_id

has_many :followers, :through => :followings, :source => :user

has_many :comments has_many :votes

has_many :subscriptions

has_many :feeds, :through => :subscriptions has_many :activities,

:conditions => ["activities.following_user_id IS NULL"] has_many :followed_activities, :class_name => "Activity",

:foreign_key => :following_user_id end

# == Schema Information #

# Table name: users #

# id :integer not null, primary key # name :string(255)

# email :string(255) # bio :string(255) # created_at :datetime # updated_at :datetime

This example shows only the associations, which happen to add the finders that are needed for the application. There are a few has_many :through associations. Users

ptg can access their followers, who they are following, the comments they have made, the

feeds they are subscribed to, the activities they have performed in the system, and the activities of the users they are following.

The activities are contained in a single denormalized table. Another option would be to map activities through a polymorphic relationship, but such joins can be expensive. A denormalized structure makes retrieving the activities a quick operation. To get a sense of why the relationship for activities looks the way it does, let’s look at the activity model.

The Activity Model

The activity model is for keeping a record of user activity such as following another user, subscribing to a feed, or commenting or voting on an entry. It is a denormalized model, so there is some data duplication with the comment, follow, subscription, and vote models. The activity model looks as follows:

class Activity < ActiveRecord::Base belongs_to :user def self.write(event) create(event.attributes) event.user.followers.each do |user| create(event.attributes.merge(:following_user_id => user.id)) end end end

class CommentActivity < Activity end

class SubscriptionActivity < Activity belongs_to :feed

end

class VoteActivity < Activity end

ptg

class FollowActivity < Activity

belongs_to :followed_user, :class_name => "User" end

# == Schema Information #

# Table name: activities #

# id :integer not null, primary key # user_id :integer # type :string(255) # feed_id :integer # followed_user_id :integer # entry_id :integer # content :text # following_user_id :integer # created_at :datetime # updated_at :datetime

The activity model uses single-table inheritance (STI) to keep each type of activ- ity in the same table. The parent class defines a write method that should be called when a comment, subscription, vote, or follow is created. First, it writes an activity without followed_user_id, which is used in the user model to find the activities that the specific user performed. Then write creates a new activity for each of the user’s

followers. This is another instance of data duplication, but it cuts down on the num- ber of joins that must be performed to pull the activity for all the users an individual is following.

The Follow Model

The follow model is the join model that specifies which users are following the oth- ers. It looks like this:

class Follow < ActiveRecord::Base belongs_to :user

ptg

after_create {|record| FollowActivity.write(record)} end

# == Schema Information #

# Table name: follows #

# id :integer not null, primary key # user_id :integer

# followed_user_id :integer # created_at :datetime # updated_at :datetime

The follow model contains only the two user IDs of the follower and followee. The logic for creating activities after create is contained in the model.

The Feed Model

The feed model contains the basic data for RSS or Atom feeds in the system. Here’s how it looks:

class Feed < ActiveRecord::Base has_many :entries

has_many :subscriptions

has_many :users, :through => :subscriptions end

# == Schema Information #

# Table name: feeds #

# id :integer not null, primary key # title :string(255)

# url :string(255) # feed_url :string(255) # created_at :datetime # updated_at :datetime

The relationships for the feed model show that it has many entries (the specific blog posts) and many users through the subscriptions.

ptg

The Subscription Model

The subscription model maps users to the feeds they are subscribed to. It looks like this:

class Subscription < ActiveRecord::Base belongs_to :user

belongs_to :feed

after_create {|record| SubscriptionActivity.write(record)} end

# == Schema Information #

# Table name: subscriptions #

# id :integer not null, primary key # user_id :integer

# feed_id :integer # created_at :datetime # updated_at :datetime

The subscription model is simple, with only a relationship with the user and the feed. The logic to create subscription activities is in the after create block.

The Entry Model

The entry model contains all the information for a specific article or blog post from a feed. Here’s how it looks:

class Entry < ActiveRecord::Base belongs_to :feed

has_many :comments end

# == Schema Information #

# Table name: entries #

ptg

# id :integer not null, primary key # feed_id :integer # title :string(255) # url :string(255) # content :text # published_date :datetime # up_votes_count :integer # down_votes_count :integer # comments_count :integer # created_at :datetime # updated_at :datetime

The entry model has relationships to the feed that it belongs to and the comments associated with it. There are also counters for the number of up votes, down votes, and comments. It could also contain a has-many relationship to those votes, but from the entry’s point of view, the only important thing for the application to keep track of is the count of vote types.

The Vote Model

The vote model uses STI to define the two different types of votes, the up and down votes:

class Vote < ActiveRecord::Base belongs_to :user

end

class UpVote < Vote

belongs_to :entry, :counter_cache => true

after_create {|record| VoteActivity.write(record)} end

class DownVote < Vote

belongs_to :entry, :counter_cache => true end

ptg

# == Schema Information #

# Table name: votes #

# id :integer not null, primary key # user_id :integer # entry_id :integer # type :string(255) # rating :integer # created_at :datetime # updated_at :datetime

The parent class vote defines the relationship to user that both the up and down

vote classes require. The up and down votes both define their relationships to the entry because of the automatic counter cache. This gets incremented in up_votes_count or down_votes_count on the entry object. Finally, only the up vote writes activity. This

is because the users probably don’t want to see entries that the people they are follow- ing thought were bad.

The Comment Model

The comment model is very basic. It holds only the text of a comment from a user and the associated entry:

class Comment < ActiveRecord::Base belongs_to :user

belongs_to :entry, :counter_cache => true

after_create {|record| CommentActivity.write(record)} end

# == Schema Information #

# Table name: comments #

# id :integer not null, primary key # user_id :integer

ptg

# entry_id :integer # content :text # created_at :datetime # updated_at :datetime

The relationships to the user and entry are here in the comment model. Also, a counter cache is kept up on the entry to store the number of comments. Finally, after creation of a new comment, the activity is written.