rails_newsfeed 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d6b107405806ed747a2935a326a486593789a6ec
4
+ data.tar.gz: f2cac89d046dc7ea51988adf593664ca10842bb5
5
+ SHA512:
6
+ metadata.gz: f2d9dd1c7e8061a93c8f4a90ad9264b03d60acdad038905e3fe2401a52aeb16fba319b8102603599963d5a1149dc9c56b861c78c24346e87082ce57004c7a4eb
7
+ data.tar.gz: d6ec820776305a46a9fefa99c568252507cd5612df42e1eaa230a75cfd9b0b197a01dc10f3ac7e984b0120ea953d0393f5db121bf530f95cfed96e78a93c2384
data/.codeclimate.yml ADDED
@@ -0,0 +1,20 @@
1
+ ---
2
+ engines:
3
+ duplication:
4
+ enabled: true
5
+ config:
6
+ languages:
7
+ - ruby
8
+ fixme:
9
+ enabled: true
10
+ rubocop:
11
+ enabled: true
12
+ config: ".rubocop.yml"
13
+ ratings:
14
+ paths:
15
+ - "**.rb"
16
+ exclude_paths:
17
+ - config/
18
+ - spec/
19
+ - travis/
20
+ - tasks/
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .rubocop.yml
11
+ app_rails_newsfeed
12
+ cassandra.txt
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format documentation
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,22 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.8
5
+ - 2.2.0
6
+ - 2.2.4
7
+ - 2.3.0
8
+
9
+ install:
10
+ - bundle install
11
+
12
+ services:
13
+ - cassandra
14
+
15
+ before_script:
16
+ - cqlsh -f travis/create-schema.cql
17
+
18
+ after_script:
19
+ - cqlsh -f travis/drop-schema.cql
20
+
21
+ script:
22
+ - bundle exec rspec spec
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Thanh (Adam) T. NGUYEN
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Adam Nguyen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # Newsfeed for Rails
2
+ [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/adamnguyenit/rails_newsfeed/master/LICENSE)
3
+ [![Build Status](https://travis-ci.org/adamnguyenit/rails_newsfeed.svg?branch=master)](https://travis-ci.org/adamnguyenit/rails_newsfeed)
4
+ [![Dependency Status](https://gemnasium.com/adamnguyenit/rails_newsfeed.svg)](https://gemnasium.com/adamnguyenit/rails_newsfeed)
5
+ [![Code Climate](https://codeclimate.com/github/adamnguyenit/rails_newsfeed/badges/gpa.svg)](https://codeclimate.com/github/adamnguyenit/rails_newsfeed)
6
+ [![Coverage Status](https://coveralls.io/repos/github/adamnguyenit/rails_newsfeed/badge.svg?branch=master)](https://coveralls.io/github/adamnguyenit/rails_newsfeed?branch=master)
7
+ [![Gem Version](https://badge.fury.io/rb/rails_newsfeed.svg)](https://badge.fury.io/rb/rails_newsfeed)
8
+ ![Download](http://ruby-gem-downloads-badge.herokuapp.com/rails_newsfeed)
9
+ [![GitHub stars](https://img.shields.io/github/stars/adamnguyenit/rails_newsfeed.svg)](https://github.com/adamnguyenit/rails_newsfeed/stargazers)
10
+ [![GitHub issues](https://img.shields.io/github/issues/adamnguyenit/rails_newsfeed.svg)](https://github.com/adamnguyenit/rails_newsfeed/issues)
11
+ ![Repo Size](https://reposs.herokuapp.com/?path=adamnguyenit/rails_newsfeed)
12
+
13
+ This is a gem for newsfeed module on rails. It uses Cassandra >= 2.x to store data and control your feeds system.
14
+
15
+ ## Installation
16
+ *Requirement*
17
+
18
+ Cassandra >= 2.x
19
+ Rails 4.x
20
+ After cassandra installed, it is recommend to increase `batch_size_fail_threshold_in_kb` in your `cassandra.yaml` and restart cassandra. Depends on your size of relation between models.
21
+ *Gem*
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ ```ruby
26
+ gem 'rails_newsfeed'
27
+ ```
28
+
29
+ And then execute:
30
+
31
+ $ bundle
32
+
33
+ Or install it yourself as:
34
+
35
+ $ gem install rails_newsfeed
36
+
37
+ ## Usage
38
+
39
+ First, let this gem generate the cassandra config file. Run rails generation
40
+
41
+ $ rails g rails_newsfeed:config
42
+
43
+ and change the configuration follows your system.
44
+ Then let create the schema by generator
45
+
46
+ $ rails g rails_newsfeed:init
47
+
48
+ And create a model by
49
+
50
+ $ rails g rails_newsfeed:model user_feed --type_of_id=bigint
51
+
52
+ Change the type_of_id option to match the type of your model
53
+
54
+ # Quick start
55
+
56
+ Save the activity first
57
+ ```ruby
58
+ activity = RailsNewsfeed::Activity.new(content: 'user 1 upload photo 1')
59
+ activity.save
60
+ ```
61
+
62
+ Add a feed
63
+ ```ruby
64
+ user_feed = UserFeed.new(id: 1)
65
+ user_feed.insert(activity)
66
+ ```
67
+
68
+ Get feeds
69
+ ```ruby
70
+ user_feed = UserFeed.new(id: 1)
71
+ feeds = user_feed.feeds
72
+ next_page_token = user_feed.next_page_token
73
+ ```
74
+
75
+ Delete a feed
76
+ ```ruby
77
+ activity = RailsNewsfeed::Activity.find(feed_id)
78
+ activity.delete
79
+ ```
80
+
81
+ Register to another feed model
82
+ ```ruby
83
+ user_a_feed = UserFeed.new(id: 1)
84
+ user_b_feed = UserFeed.new(id: 2)
85
+ user_a_feed.register(user_a_feed)
86
+ ```
87
+
88
+
89
+ ## Development
90
+
91
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
92
+
93
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
94
+
95
+ Before running test you must apply these cqls into cassandra.
96
+ ```ruby
97
+ CREATE KEYSPACE rails_newsfeed_test WITH REPLICATION = { 'class': 'SimpleStrategy', 'replication_factor': 3 };
98
+ USE rails_newsfeed_test;
99
+ CREATE TABLE activity (id uuid, content text, time timestamp, object text, PRIMARY KEY (id));
100
+ CREATE TABLE activity_index (id uuid, content text, time timestamp, object text, PRIMARY KEY ((object), id));
101
+ CREATE TABLE feed_table (table_class text, PRIMARY KEY (table_class));
102
+ CREATE TABLE relation (id uuid, from_class text, from_id text, to_class text, to_id text, PRIMARY KEY ((from_class, from_id), id));
103
+ CREATE TABLE relation_index(id uuid, from_class text, from_id text, to_class text, to_id text, PRIMARY KEY ((from_class, from_id, to_class, to_id)));
104
+ CREATE TABLE user_feed (id bigint, activity_id uuid, activity_content text, activity_object text, activity_time timestamp, PRIMARY KEY ((id), activity_id));
105
+ INSERT INTO feed_table (table_class) VALUES ('UserFeed');
106
+ ```
107
+ We use rspec to test this gem.
108
+
109
+ ## Contributing
110
+
111
+ Bug reports and pull requests are welcome on GitHub at https://github.com/adamnguyenit/rails_newsfeed. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
112
+
113
+
114
+ ## License
115
+
116
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ Dir.glob('tasks/**/*.rake').each(&method(:import))
data/bin/console ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'rails_newsfeed'
5
+ require 'irb'
6
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
@@ -0,0 +1,16 @@
1
+ default: &default
2
+ hosts:
3
+ - '127.0.0.1'
4
+ port: 9042
5
+ keyspace: rails_newsfeed_test
6
+
7
+ development:
8
+ <<: *default
9
+
10
+ test:
11
+ <<: *default
12
+
13
+ production:
14
+ <<: *default
15
+ username: 'cassandra'
16
+ password: 'cassandra'
@@ -0,0 +1,12 @@
1
+ require 'rails/generators'
2
+ require 'rails_newsfeed/railtie'
3
+
4
+ module RailsNewsfeed
5
+ class ConfigGenerator < Rails::Generators::Base
6
+ desc 'This generator creates the config/cassandra.xml file to config cassandra server for newsfeed'
7
+ source_root File.expand_path('../templates', __FILE__)
8
+ def process
9
+ template 'config/cassandra.yml'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ require 'rails/generators'
2
+ require 'rails_newsfeed/connection'
3
+ require 'rails_newsfeed/activity'
4
+ require 'rails_newsfeed/feed_table'
5
+ require 'rails_newsfeed/relation'
6
+
7
+ module RailsNewsfeed
8
+ class InitGenerator < Rails::Generators::Base
9
+ desc 'This generator initials your cassandra schema db for feed'
10
+ def process
11
+ cfg = RailsNewsfeed::Connection.config
12
+ connection = Cassandra.cluster(cfg || {}).connect('system')
13
+ connection.execute("DROP KEYSPACE IF EXISTS #{cfg['keyspace']}")
14
+ connection.execute("CREATE KEYSPACE #{cfg['keyspace']}
15
+ WITH REPLICATION={ 'class': 'SimpleStrategy', 'replication_factor': 3 }")
16
+ connection.execute("USE #{cfg['keyspace']}")
17
+ connection.execute("CREATE TABLE #{RailsNewsfeed::Activity.table_name}
18
+ (id uuid, content text, time timestamp, object text, PRIMARY KEY (id))")
19
+ connection.execute("CREATE TABLE #{RailsNewsfeed::Activity.index_table_name}
20
+ (id uuid, content text, time timestamp, object text, PRIMARY KEY ((object), id))
21
+ WITH CLUSTERING ORDER BY (id DESC)")
22
+ connection.execute("CREATE TABLE #{RailsNewsfeed::FeedTable.table_name}
23
+ (table_class text, PRIMARY KEY (table_class))")
24
+ connection.execute("CREATE TABLE #{RailsNewsfeed::Relation.table_name}
25
+ (id uuid, from_class text, from_id text, to_class text, to_id text, PRIMARY KEY ((from_class, from_id), id))")
26
+ connection.execute("CREATE TABLE #{RailsNewsfeed::Relation.index_table_name}
27
+ (id uuid, from_class text, from_id text, to_class text, to_id text,
28
+ PRIMARY KEY ((from_class, from_id, to_class, to_id)))")
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,41 @@
1
+ require 'rails/generators'
2
+ require 'rails_newsfeed/feed_table'
3
+
4
+ module RailsNewsfeed
5
+ class ModelGenerator < Rails::Generators::NamedBase
6
+ class_option :type_of_id, type: :string, require: false
7
+ def process
8
+ case behavior
9
+ when :invoke
10
+ invoke
11
+ when :revoke
12
+ revoke
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def invoke
19
+ t = options.key?('type_of_id') ? options['type_of_id'] : 'bigint'
20
+ RailsNewsfeed::Connection.exec_cql("DROP TABLE IF EXISTS #{file_name}")
21
+ RailsNewsfeed::Connection.exec_cql("CREATE TABLE #{file_name}
22
+ (id #{t}, activity_id uuid, activity_content text, activity_object text, activity_time timestamp,
23
+ PRIMARY KEY ((id), activity_id)) WITH CLUSTERING ORDER BY (activity_id DESC)")
24
+ RailsNewsfeed::Connection.exec_cql("INSERT INTO #{RailsNewsfeed::FeedTable.table_name} (table_class)
25
+ VALUES ('#{class_name}')")
26
+ create_file "app/models/#{file_name}.rb", <<-FILE
27
+ class #{class_name} < RailsNewsfeed::NewsfeedModel
28
+ type_of_id :#{t}
29
+ end
30
+ FILE
31
+ end
32
+
33
+ def revoke
34
+ RailsNewsfeed::Connection.exec_cql("DROP TABLE IF EXISTS #{file_name}")
35
+ RailsNewsfeed::Connection.exec_cql("DELETE FROM #{RailsNewsfeed::FeedTable.table_name}
36
+ WHERE table_class='#{class_name}'")
37
+ @behavior = :invoke
38
+ remove_file "app/models/#{file_name}.rb"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ # reads more options from http://datastax.github.io/ruby-driver/api/#cluster-class_method
2
+ <% app_name = RailsNewsfeed::Railtie.app_name %>
3
+ default: &default
4
+ hosts:
5
+ - '127.0.0.1'
6
+ port: 9042
7
+
8
+ development:
9
+ <<: *default
10
+ keyspace: <%= app_name %>_dev
11
+
12
+ test:
13
+ <<: *default
14
+ keyspace: <%= app_name %>_test
15
+
16
+ production:
17
+ <<: *default
18
+ keyspace: <%= app_name %>
19
+ username: 'cassandra'
20
+ password: 'cassandra'
@@ -0,0 +1,10 @@
1
+ require 'cassandra'
2
+ require 'rails_newsfeed/version'
3
+ require 'rails_newsfeed/connection'
4
+ require 'rails_newsfeed/newsfeed_model'
5
+ require 'rails_newsfeed/activity'
6
+ require 'rails_newsfeed/relation'
7
+ require 'rails_newsfeed/feed_table'
8
+
9
+ module RailsNewsfeed
10
+ end
@@ -0,0 +1,195 @@
1
+ module RailsNewsfeed
2
+ class Activity
3
+ attr_reader :id
4
+ attr_accessor :content
5
+ attr_accessor :object
6
+ attr_accessor :time
7
+ attr_reader :new_record
8
+
9
+ # gets table name
10
+ def self.table_name
11
+ 'activity'
12
+ end
13
+
14
+ # gets index table name
15
+ def self.index_table_name
16
+ "#{table_name}_index"
17
+ end
18
+
19
+ # gets schema
20
+ # DO NOT override this method unless you know what you are doing
21
+ def self.schema
22
+ { id: :uuid, content: :text, object: :text, time: :timestamp }
23
+ end
24
+
25
+ # gets all activities
26
+ def self.all(opt = {})
27
+ rs = Connection.select(table_name, schema, '*') if opt.empty?
28
+ rs = Connection.select(index_table_name, schema, '*', object: opt[:object]) if opt.key?(:object)
29
+ acts = []
30
+ rs.each { |r| acts.push(create_from_cass(:act, r)) } if rs
31
+ acts
32
+ end
33
+
34
+ # creates activity
35
+ def self.create(opt = {})
36
+ act = new(opt)
37
+ return nil unless act.save
38
+ act
39
+ end
40
+
41
+ # creates without failling
42
+ def self.create!(opt = {})
43
+ create(opt)
44
+ rescue
45
+ nil
46
+ end
47
+
48
+ # finds activity by id
49
+ def self.find(id)
50
+ r = Connection.select(table_name, schema, '*', { id: id }, page_size: 1).first
51
+ return nil unless r
52
+ create_from_cass(:act, r)
53
+ end
54
+
55
+ # finds without failling
56
+ def self.find!(id)
57
+ find(id)
58
+ rescue
59
+ nil
60
+ end
61
+
62
+ # deletes activities
63
+ def self.delete(opt = {}, show_last = true)
64
+ if opt.key?(:id)
65
+ act = find(opt[:id])
66
+ return true unless act
67
+ return act.delete(show_last)
68
+ elsif opt.key?(:object)
69
+ all(object: opt[:object]).each { |a| a.delete(false) }
70
+ return true
71
+ else
72
+ # truncates activity table and feed tables
73
+ Connection.exec_cql("TRUNCATE #{table_name}")
74
+ Connection.exec_cql("TRUNCATE #{index_table_name}")
75
+ FeedTable.all.each { |r| Connection.exec_cql("TRUNCATE #{r.class.table_name}") }
76
+ return true
77
+ end
78
+ end
79
+
80
+ # deletes without failling
81
+ def self.delete!(opt = {}, show_last = true)
82
+ delete(opt, show_last)
83
+ rescue
84
+ false
85
+ end
86
+
87
+ # creates from feed cassandra
88
+ def self.create_from_cass(type, res)
89
+ h = {}
90
+ temp = type == :act ? '' : 'activity_'
91
+ schema.keys.each do |k|
92
+ key = "#{temp}#{k}"
93
+ val = k == :id || k == :time ? res[key].to_s : res[key]
94
+ h[k] = val
95
+ end
96
+ h[:new_record] = false
97
+ new(h)
98
+ end
99
+
100
+ # initializes
101
+ def initialize(options = {})
102
+ @id = options.key?(:id) ? options[:id] : nil
103
+ @content = options.key?(:content) ? options[:content] : nil
104
+ @object = options.key?(:object) ? options[:object] : nil
105
+ @time = options.key?(:time) ? options[:time] : nil
106
+ @new_record = options.key?(:new_record) ? options[:new_record] : true
107
+ end
108
+
109
+ # saves
110
+ def save
111
+ return insert if @new_record
112
+ update
113
+ end
114
+
115
+ # saves without failling
116
+ def save!
117
+ save
118
+ rescue
119
+ false
120
+ end
121
+
122
+ # deletes including activities from feed tables
123
+ def delete(show_last = true)
124
+ return false if @new_record
125
+ Connection.delete(self.class.table_name, self.class.schema, id: @id)
126
+ Connection.delete(self.class.index_table_name, self.class.schema, object: @object, id: @id) if @object
127
+ return delete_from_feed(@id, nil) unless show_last
128
+ delete_from_feed(@id, last)
129
+ end
130
+
131
+ # deletes without failling
132
+ def delete!(show_last = true)
133
+ delete(show_last)
134
+ rescue
135
+ false
136
+ end
137
+
138
+ # converts to hash
139
+ def to_h(prefix = nil)
140
+ { "#{prefix}id".to_sym => @id, "#{prefix}content".to_sym => @content,
141
+ "#{prefix}object".to_sym => @object, "#{prefix}time".to_sym => @time }
142
+ end
143
+
144
+ protected
145
+
146
+ # inserts
147
+ def insert
148
+ return false unless @content
149
+ @id ||= Cassandra::Uuid::Generator.new.now.to_s
150
+ @time ||= Cassandra::Types::Timestamp.new(DateTime.current).to_s
151
+ Connection.insert(self.class.table_name, self.class.schema, to_h)
152
+ Connection.insert(self.class.index_table_name, self.class.schema, to_h) unless @object.nil?
153
+ @new_record = false
154
+ true
155
+ end
156
+
157
+ # updates
158
+ def update
159
+ Connection.update(self.class.table_name, self.class.schema, { id: @id }, to_h)
160
+ # updates from all feed models
161
+ cqls = []
162
+ FeedTable.all.each do |i|
163
+ i_tbl = i.class.table_name
164
+ i_schema = i.class.schema
165
+ Connection.select(i_tbl, i_schema, '*', { activity_id: @id }, filtering: true).each do |r|
166
+ cqls.push(Connection.update(i_tbl, i_schema, { id: r['id'], activity_id: @id }, to_h('activity_'), true))
167
+ end
168
+ end
169
+ Connection.batch_cqls(cqls)
170
+ true
171
+ end
172
+
173
+ # deletes from all feed tables
174
+ def delete_from_feed(id, last = nil)
175
+ cqls = []
176
+ FeedTable.all.each do |i|
177
+ i_tbl = i.class.table_name
178
+ i_schema = i.class.schema
179
+ Connection.select(i_tbl, i_schema, '*', { activity_id: id }, filtering: true).each do |r|
180
+ cqls.push(Connection.delete(i_tbl, i_schema, { id: r['id'], activity_id: r['activity_id'].to_s }, true))
181
+ next unless last
182
+ cqls.push(Connection.insert(i_tbl, i_schema, NewsfeedModel.from_cass_act(r['id'], last), true))
183
+ end
184
+ end
185
+ Connection.batch_cqls(cqls.uniq)
186
+ true
187
+ end
188
+
189
+ # gets last activity of object
190
+ def last
191
+ return nil unless @object
192
+ Connection.select(self.class.index_table_name, self.class.schema, '*', object: @object).first
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,105 @@
1
+ module RailsNewsfeed
2
+ class Connection
3
+ class << self
4
+ private
5
+
6
+ # exports a value to cassandra value
7
+ def cass_val(val, type)
8
+ return 'null' if val.nil?
9
+ case type
10
+ when :ascii, :text, :varchar, :timestamp
11
+ return "'#{val.to_s.gsub("'", "''")}'"
12
+ else
13
+ val
14
+ end
15
+ end
16
+
17
+ # exports values to cassandra values
18
+ def cass_vals(schema, vals)
19
+ cass_vals = []
20
+ vals.each do |col, val|
21
+ cass_vals.push(cass_val(val, schema[col]))
22
+ end
23
+ cass_vals.join(',')
24
+ end
25
+
26
+ # exports column value pair
27
+ def exported_col_val(schema, col_val_pair)
28
+ a = []
29
+ col_val_pair.each do |col, val|
30
+ a.push("#{col}=#{cass_val(val, schema[col])}")
31
+ end
32
+ a
33
+ end
34
+ end
35
+
36
+ # gets config
37
+ def self.config
38
+ YAML.load_file('config/cassandra.yml')[Rails.env]
39
+ end
40
+
41
+ # gets cassandra connection
42
+ def self.connection
43
+ return @connection if @connection
44
+ cfg = config
45
+ @connection = Cassandra.cluster(cfg || {}).connect(cfg['keyspace'])
46
+ end
47
+
48
+ # executes cql
49
+ def self.exec_cql(cql, options = {})
50
+ connection.execute(cql, options)
51
+ end
52
+
53
+ # executes batch
54
+ def self.batch_cqls(cqls, options = {})
55
+ return true if cqls.empty?
56
+ batch = connection.batch do |b|
57
+ cqls.each do |cql|
58
+ b.add(cql)
59
+ end
60
+ end
61
+ exec_cql(batch, options)
62
+ end
63
+
64
+ # inserts
65
+ def self.insert(tbl, schema, vals, to_cql = false)
66
+ cql = "INSERT INTO #{tbl} (#{vals.keys.join(',')}) VALUES (#{cass_vals(schema, vals)})"
67
+ return cql if to_cql
68
+ exec_cql(cql)
69
+ true
70
+ end
71
+
72
+ # updates
73
+ def self.update(tbl, schema, conditions, vals, to_cql = false)
74
+ # unsets primary keys
75
+ conditions.keys.each { |k| vals.delete(k) }
76
+ val_s = exported_col_val(schema, vals).join(',')
77
+ cql = "UPDATE #{tbl} SET #{val_s} WHERE #{exported_col_val(schema, conditions).join(' AND ')}"
78
+ return cql if to_cql
79
+ exec_cql(cql)
80
+ true
81
+ end
82
+
83
+ # deletes
84
+ def self.delete(tbl, schema, conditions, to_cql = false)
85
+ cql = "DELETE FROM #{tbl} WHERE #{exported_col_val(schema, conditions).join(' AND ')}"
86
+ return cql if to_cql
87
+ exec_cql(cql)
88
+ true
89
+ end
90
+
91
+ # selects
92
+ def self.select(tbl, schema = {}, columns = '*', conditions = {}, options = {})
93
+ cql = 'SELECT '
94
+ cql += columns unless columns.is_a?(Array)
95
+ cql += columns.join(',') if columns.is_a?(Array)
96
+ cql += " FROM #{tbl}"
97
+ cql += " WHERE #{exported_col_val(schema, conditions).join(' AND ')}" unless conditions.empty?
98
+ if options[:filtering]
99
+ cql += ' ALLOW FILTERING'
100
+ options.delete(:filtering)
101
+ end
102
+ exec_cql(cql, options)
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,36 @@
1
+ module RailsNewsfeed
2
+ class FeedTable
3
+ # gets table name
4
+ def self.table_name
5
+ 'feed_table'
6
+ end
7
+
8
+ # gets schema
9
+ # DO NOT override this method unless you know what you are doing
10
+ def self.schema
11
+ { table_class: :text }
12
+ end
13
+
14
+ # adds table
15
+ def self.create(tbl_class)
16
+ Connection.insert(table_name, schema, table_class: tbl_class)
17
+ end
18
+
19
+ # removes table
20
+ def self.delete(tbl_class)
21
+ Connection.delete(table_name, schema, table_class: tbl_class)
22
+ end
23
+
24
+ # gets all feed tables
25
+ def self.all
26
+ items = []
27
+ Connection.select(table_name).each do |r|
28
+ cons = r['table_class'].safe_constantize
29
+ next unless cons
30
+ ins = cons.new
31
+ items.push(ins) if ins
32
+ end
33
+ items
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,161 @@
1
+ module RailsNewsfeed
2
+ class NewsfeedModel
3
+ SALT_KEY = 'fQEEumGosuS92VcVVMPm'.freeze
4
+ SECRET_KEY = 'sL6PySf7Ijo8L0ZhU7R2'.freeze
5
+
6
+ class_attribute :id_type, instance_writer: false
7
+ self.id_type = :bigint
8
+
9
+ attr_reader :id
10
+ attr_reader :next_page_token
11
+
12
+ # sets type of id
13
+ def self.type_of_id(type)
14
+ self.id_type = type
15
+ end
16
+
17
+ # gets table name
18
+ def self.table_name
19
+ name.demodulize.underscore
20
+ end
21
+
22
+ # gets schema
23
+ # DO NOT override this method unless you know what you are doing
24
+ def self.schema
25
+ { id: id_type, activity_id: :uuid, activity_content: :text, activity_object: :text, activity_time: :timestamp }
26
+ end
27
+
28
+ # inserts
29
+ def self.insert(id, activity, related = true, hide_old = true)
30
+ new(id: id).insert(activity, related, hide_old)
31
+ end
32
+
33
+ # deletes by id
34
+ def self.delete(id, act_id = nil, related = false)
35
+ new(id: id).delete(act_id, related)
36
+ end
37
+
38
+ # gets feeds by id
39
+ def self.feeds(id, page_size = 10, next_page_token = nil)
40
+ new(id: id, next_page_token: next_page_token).feeds(page_size)
41
+ end
42
+
43
+ def self.from_cass_act(id, res)
44
+ { id: id, activity_id: res['id'].to_s, activity_content: res['content'],
45
+ activity_object: res['object'], activity_time: res['time'].to_s }
46
+ end
47
+
48
+ # initializes
49
+ def initialize(options = {})
50
+ @id = options.key?(:id) ? options[:id] : nil
51
+ @next_page_token = options.key?(:next_page_token) ? options[:next_page_token] : nil
52
+ end
53
+
54
+ # inserts an activity into table
55
+ def insert(activity, related = true, hide_old = true)
56
+ record = { id: @id, activity_id: activity.id, activity_content: activity.content,
57
+ activity_object: activity.object, activity_time: activity.time }
58
+ Connection.insert(self.class.table_name, self.class.schema, record)
59
+ ins_arr = []
60
+ cqls = []
61
+ if related
62
+ Relation.related_of(self).each do |ins|
63
+ record[:id] = ins.id
64
+ cqls.push(Connection.insert(ins.class.table_name, ins.class.schema, record, true))
65
+ ins_arr.push(ins) if hide_old
66
+ end
67
+ end
68
+ if hide_old
69
+ ins_arr.push(self)
70
+ cqls |= cql_hide_old_feeds_of(activity, ins_arr)
71
+ end
72
+ Connection.batch_cqls(cqls)
73
+ true
74
+ end
75
+
76
+ # deletes an activity or all activities from this feed
77
+ def delete(act_id = nil, related = true)
78
+ tbl = self.class.table_name
79
+ schema = self.class.schema
80
+ if act_id
81
+ return Activity.delete(act_id, true) if related
82
+ return Connection.delete(tbl, schema, id: @id, activity_id: act_id)
83
+ end
84
+ cqls = []
85
+ Connection.select(tbl, schema, '*', id: @id).each do |r|
86
+ if related
87
+ Activity.delete(r['id'].to_s, true)
88
+ else
89
+ cqls.push(Connection.delete(tbl, schema, { id: @id, activity_id: r['id'].to_s }, true))
90
+ end
91
+ end
92
+ Connection.batch_cqls(cqls.uniq)
93
+ true
94
+ end
95
+
96
+ # gets feeds
97
+ def feeds(page_size = 10)
98
+ @feeds = []
99
+ options = { page_size: page_size.to_i }
100
+ options[:paging_state] = decoded_next_page_token if @next_page_token
101
+ result = Connection.select(self.class.table_name, self.class.schema, '*', { id: @id }, options)
102
+ result.each { |r| @feeds.push(Activity.create_from_cass(:feed, r)) }
103
+ encoded_next_page_token(result)
104
+ after_feeds
105
+ @feeds
106
+ end
107
+
108
+ # overrides this method to implement your own
109
+ def after_feeds
110
+ end
111
+
112
+ # registers to another
113
+ def register(to, options = {})
114
+ Relation.create(to, self, options)
115
+ end
116
+
117
+ # deregisters to another
118
+ def deregister(to, options = {})
119
+ Relation.delete(to, self, options)
120
+ end
121
+
122
+ # checks related
123
+ def register?(to)
124
+ Relation.related?(to, self)
125
+ end
126
+
127
+ protected
128
+
129
+ # encodes next_page_token
130
+ def encoded_next_page_token(result)
131
+ if result.nil? || result.last_page?
132
+ @next_page_token = nil
133
+ else
134
+ key = ActiveSupport::KeyGenerator.new(SECRET_KEY).generate_key(SALT_KEY)
135
+ @next_page_token = ActiveSupport::MessageEncryptor.new(key).encrypt_and_sign(result.paging_state)
136
+ end
137
+ @next_page_token
138
+ end
139
+
140
+ # decodes next_page_token
141
+ def decoded_next_page_token
142
+ key = ActiveSupport::KeyGenerator.new(SECRET_KEY).generate_key(SALT_KEY)
143
+ ActiveSupport::MessageEncryptor.new(key).decrypt_and_verify(@next_page_token)
144
+ end
145
+
146
+ # generates cqls to hide old feeds
147
+ def cql_hide_old_feeds_of(activity, ins_arr)
148
+ return [] unless activity.object
149
+ cqls = []
150
+ cond = { object: activity.object }
151
+ Connection.select(activity.class.index_table_name, activity.class.schema, '*', cond).each do |r|
152
+ id = r['id'].to_s
153
+ next if id == activity.id
154
+ ins_arr.each do |t|
155
+ cqls.push(Connection.delete(t.class.table_name, t.class.schema, { id: t.id, activity_id: id }, true))
156
+ end
157
+ end
158
+ cqls.uniq
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,7 @@
1
+ module RailsNewsfeed
2
+ class Railtie < Rails::Railtie
3
+ def self.app_name
4
+ Rails.application.railtie_name.sub(/_application$/, '')
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,64 @@
1
+ module RailsNewsfeed
2
+ class Relation
3
+ # gets table name
4
+ def self.table_name
5
+ 'relation'
6
+ end
7
+
8
+ # gets index table name
9
+ def self.index_table_name
10
+ "#{table_name}_index"
11
+ end
12
+
13
+ # gets schema
14
+ # DO NOT override this method unless you know what you are doing
15
+ def self.schema
16
+ { id: :uuid, from_class: :text, from_id: :text, to_class: :text, to_id: :text }
17
+ end
18
+
19
+ # creates relations between two objects
20
+ def self.create(from, to, options = {})
21
+ id = Cassandra::Uuid::Generator.new.now.to_s
22
+ record = { id: id, from_class: from.class.name, from_id: from.id, to_class: to.class.name, to_id: to.id }
23
+ return false unless Connection.insert(table_name, schema, record)
24
+ Connection.insert(index_table_name, schema, record)
25
+ return true unless options.key?(:side) && options[:side] == :both
26
+ create(to, from)
27
+ end
28
+
29
+ # deletes relations between two objects
30
+ def self.delete(from, to, options = {})
31
+ cond = { from_class: from.class.name, from_id: from.id, to_class: to.class.name, to_id: to.id }
32
+ i = Connection.select(index_table_name, schema, '*', cond).first
33
+ if i
34
+ Connection.delete(table_name, schema, from_class: from.class.name, from_id: from.id, id: i['id'].to_s)
35
+ Connection.delete(index_table_name, schema, cond)
36
+ end
37
+ return true unless options.key?(:side) && options[:side] == :both
38
+ delete(to, from)
39
+ end
40
+
41
+ # gets relateds of object
42
+ def self.related_of(from)
43
+ relateds = []
44
+ result = Connection.select(table_name, schema, '*', from_class: from.class.name, from_id: from.id)
45
+ result.each do |r|
46
+ cons = r['to_class'].safe_constantize
47
+ next unless cons
48
+ ins = cons.new(id: r['to_id'])
49
+ relateds.push(ins) if ins
50
+ end
51
+ relateds
52
+ end
53
+
54
+ # checks is related
55
+ def self.related?(from, to)
56
+ cond = { from_class: from.class.name, from_id: from.id, to_class: to.class.name, to_id: to.id }
57
+ i = Connection.select(index_table_name, schema, '*', cond).first
58
+ return false unless i
59
+ cond = { from_class: from.class.name, from_id: from.id, id: i['id'].to_s }
60
+ return false unless Connection.select(table_name, schema, '*', cond).first
61
+ true
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,3 @@
1
+ module RailsNewsfeed
2
+ VERSION = '0.0.4'.freeze
3
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rails_newsfeed/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'rails_newsfeed'
8
+ spec.version = RailsNewsfeed::VERSION
9
+ spec.authors = ['Adam Nguyen']
10
+ spec.email = ['adamnguyen.itdn@gmail.com']
11
+ spec.summary = 'News Feed module for ruby'
12
+ spec.description = 'News Feed module for ruby'
13
+ spec.homepage = 'https://github.com/adamnguyenit/rails_newsfeed'
14
+ spec.license = 'MIT'
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = 'exe'
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ['lib']
19
+ spec.required_ruby_version = '>= 2.0'
20
+ spec.add_dependency 'rails', '>= 4.0.0'
21
+ spec.add_dependency 'cassandra-driver', '~> 3.0.0.rc'
22
+ spec.add_development_dependency 'bundler'
23
+ spec.add_development_dependency 'rake'
24
+ spec.add_development_dependency 'rspec', '~> 3.4'
25
+ spec.add_development_dependency 'rspec-collection_matchers'
26
+ spec.add_development_dependency 'factory_girl', '~> 4.5'
27
+ spec.add_development_dependency 'faker', '~> 1.6'
28
+ spec.add_development_dependency 'rubocop'
29
+ spec.add_development_dependency 'coveralls'
30
+ spec.add_development_dependency 'codeclimate-test-reporter'
31
+ end
data/tasks/rspec.rake ADDED
@@ -0,0 +1,3 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,9 @@
1
+ CREATE KEYSPACE rails_newsfeed_test WITH REPLICATION = { 'class': 'SimpleStrategy', 'replication_factor': 3 };
2
+ USE rails_newsfeed_test;
3
+ CREATE TABLE activity (id uuid, content text, time timestamp, object text, PRIMARY KEY (id));
4
+ CREATE TABLE activity_index (id uuid, content text, time timestamp, object text, PRIMARY KEY ((object), id));
5
+ CREATE TABLE feed_table (table_class text, PRIMARY KEY (table_class));
6
+ CREATE TABLE relation (id uuid, from_class text, from_id text, to_class text, to_id text, PRIMARY KEY ((from_class, from_id), id));
7
+ CREATE TABLE relation_index(id uuid, from_class text, from_id text, to_class text, to_id text, PRIMARY KEY ((from_class, from_id, to_class, to_id)));
8
+ CREATE TABLE user_feed (id bigint, activity_id uuid, activity_content text, activity_object text, activity_time timestamp, PRIMARY KEY ((id), activity_id));
9
+ INSERT INTO feed_table (table_class) VALUES ('UserFeed');
@@ -0,0 +1 @@
1
+ DROP KEYSPACE rails_newsfeed_test;
metadata ADDED
@@ -0,0 +1,227 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_newsfeed
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Adam Nguyen
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-03-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 4.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 4.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: cassandra-driver
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.0.rc
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0.0.rc
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec-collection_matchers
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: factory_girl
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '4.5'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '4.5'
111
+ - !ruby/object:Gem::Dependency
112
+ name: faker
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.6'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.6'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: coveralls
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: codeclimate-test-reporter
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description: News Feed module for ruby
168
+ email:
169
+ - adamnguyen.itdn@gmail.com
170
+ executables: []
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - ".codeclimate.yml"
175
+ - ".gitignore"
176
+ - ".rspec"
177
+ - ".travis.yml"
178
+ - CODE_OF_CONDUCT.md
179
+ - Gemfile
180
+ - LICENSE
181
+ - LICENSE.txt
182
+ - README.md
183
+ - Rakefile
184
+ - bin/console
185
+ - bin/setup
186
+ - config/cassandra.yml
187
+ - lib/generators/rails_newsfeed/config_generator.rb
188
+ - lib/generators/rails_newsfeed/init_generator.rb
189
+ - lib/generators/rails_newsfeed/model_generator.rb
190
+ - lib/generators/rails_newsfeed/templates/config/cassandra.yml
191
+ - lib/rails_newsfeed.rb
192
+ - lib/rails_newsfeed/activity.rb
193
+ - lib/rails_newsfeed/connection.rb
194
+ - lib/rails_newsfeed/feed_table.rb
195
+ - lib/rails_newsfeed/newsfeed_model.rb
196
+ - lib/rails_newsfeed/railtie.rb
197
+ - lib/rails_newsfeed/relation.rb
198
+ - lib/rails_newsfeed/version.rb
199
+ - rails_newsfeed.gemspec
200
+ - tasks/rspec.rake
201
+ - travis/create-schema.cql
202
+ - travis/drop-schema.cql
203
+ homepage: https://github.com/adamnguyenit/rails_newsfeed
204
+ licenses:
205
+ - MIT
206
+ metadata: {}
207
+ post_install_message:
208
+ rdoc_options: []
209
+ require_paths:
210
+ - lib
211
+ required_ruby_version: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: '2.0'
216
+ required_rubygems_version: !ruby/object:Gem::Requirement
217
+ requirements:
218
+ - - ">="
219
+ - !ruby/object:Gem::Version
220
+ version: '0'
221
+ requirements: []
222
+ rubyforge_project:
223
+ rubygems_version: 2.4.5.1
224
+ signing_key:
225
+ specification_version: 4
226
+ summary: News Feed module for ruby
227
+ test_files: []