Greg
Greg

Reputation: 305

Ruby-on-Rails: How to access create action, without leaving index

Trying to create new tweets to display in my index, but I don't wanna leave the page. My app's desired function is to track tweets from tweetstream, and automatically pass that info into my tweet object and save to db.

Controller:

class TweetsController < ApplicationController

  TWITTER_COMSUMER_KEY = "GfqdzJKb5kIyEnYlQuNGlg"
  TWITTER_CONSUMER_SECRET = "A3Fe0IvDbhlKgowCVmV1WVLlcdYgQ8w9clrDSegCQ"
  TWITTER_OATH_TOKEN = "34012133-caUYq3eiNC7Z9L9KvTgG51VgyctqVxkXP0tKIXDk0"
  TWITTER_OATH_TOKEN_SECRET = "DSLA3F8BPssyEeEP2wZgQ1OJRL5kIVPZfON4GYZFw"

  TweetStream.configure do |config|
    config.consumer_key = TWITTER_COMSUMER_KEY
    config.consumer_secret = TWITTER_CONSUMER_SECRET
    config.oauth_token = TWITTER_OATH_TOKEN
    config.oauth_token_secret = TWITTER_OATH_TOKEN_SECRET
  end

  def index
    @tweets = Tweet.all
  end

  def new
    @tweet = Tweet.new
  end

  def create
    TweetStream.track('bed', 'morning', 'breakfast') do |status|

      temp = status.text
      if(temp.include? "http")
         @tweet = Tweet.new(status.text)
         if @tweet.save
         else
           render "new"
         end
      end
    end
  end

  def show

  end
end

Index.html.erb

<h1>Tweet Tracker</h1>

<% @tweets.each do |tweet| %>
    <p><%= tweet.content %></p>
    <hr />
<% end %>

Upvotes: 2

Views: 160

Answers (2)

user684934
user684934

Reputation:

You've got two big pieces of this:

  • Listening to the streaming API and creating objects
  • Updating the user's view as new objects are created

The second bit can be done by some simple javascript polling, as in @AustinMullins's answer.

The first bit should not be done in a controller - they are for responding to requests and deviating from that kind of job may end up with unexpected behavior or performance issues.

For example, I found that on a site that's running on Phusion Passenger, the server would create a thread to handle a request, then kill it after a certain amount of time if it didn't finish by itself, which of course it would not if the controller starts listening to a neverending input stream.

Instead, you should get a separate script that you can start from the command line. Here's an example similar to what I'm using:

script/tracker.rb

#!/usr/bin/env ruby
ENV["RAILS_ENV"] ||= "production"
require File.dirname(__FILE__) + "/../config/environment"
TweetStream.configure do |config|
  config.consumer_key = TWITTER_COMSUMER_KEY
  config.consumer_secret = TWITTER_CONSUMER_SECRET
  config.oauth_token = TWITTER_OATH_TOKEN
  config.oauth_token_secret = TWITTER_OATH_TOKEN_SECRET
end

client = TweetStream::Client.new
client.track(*Keyword.pluck(:name)).each do |status|
  Tweet.create(status.text)
end

script/tracker_ctl

#!/usr/bin/env ruby
require 'rubygems'
require 'daemons'
Daemon.new('tracker.rb')

Now, you could ssh into your server and run script/tracker_ctl start or script/tracker_ctl stop. But what you probably want to do is make a controller action issue those commands instead.

Upvotes: 1

Austin Mullins
Austin Mullins

Reputation: 7427

This is an interesting approach to the RESTful resource structure (index/show/create/edit/destroy), in that we're utilizing an automated tracker to do the creating rather than any user input. The problem is, your controller code is only run when accessed by a POST request (unless you've messed with routes.rb to make it respond to something else).

The cleanest solution in my mind is to write a coffee script to periodically post data to the controller and show the results.

/app/assets/javascripts/tweets.js.coffee

$ ->
    setInterval ->
      $.post("/tweets", "", (html)->
            $("#tweets").append html)
     , 1000

/app/views/tweets/create.html.erb

<p><%= @tweet.content %></p>
<br/>

For this to work right, we need to wrap the tweets area in a div with id="tweets":

/app/views/tweets/index.html.erb

<h1>Tweet Tracker</h1>

<div id="tweets">
<% @tweets.each do |tweet| %>
   <p><%= tweet.content %></p>
   <br/>
<% end %>
</div>

You'll also want to replace render "new" in the controller's create method with return. This way, nothing is rendered when a save to the database fails, and everything continues with no errors.

Upvotes: 1

Related Questions