What would I need in a Rails 3 controller to process incoming emails and insert them into the database? - ruby

I'm trying to set up my RoR 3 application to receive emails and then process those emails and insert them into the database.
In my application, I have a "jobs" controller. Users can create jobs. I also have a "comments" controller. Users can create comments on jobs.
Here is (part of) what I have in my comments controller:
def new
#job = Job.find(params[:job_id])
#comment = #job.comments.build
end
def create
#job = Job.find(params[:job_id])
#comment = #job.comments.build(params[:comment])
#comment.user_id = current_user.id
#comment.commenter = current_user.login
if #comment.save
redirect_to #job
else
render :action => "new"
end
end
When users add a comment, the admin receives an email.
When admins add a comment, users receive an email.
(This is already functioning.)
I'm using Cloudmailin to help me receive incoming mail. I've set up the Cloudmailin address to point to http://myapp.com/incoming_mails.
class IncomingMailsController < ApplicationController
require 'mail'
skip_before_filter :verify_authenticity_token
def create
another_comment = Comment.create(:title =>
params[:subject], :body => params[:plain])
render :text => 'success', :status => 200 #status of 404 rejects mail
end
end
Looking at the above comment controller, it looks like I'll need the job_id, current_user.id, and current_user.login.
Here's what I am having trouble sorting out: what goes in my "incoming_mails" controller? How can I make sure that when a user responds via email that the controller in "incoming_mails" is able to find the job_id and the current_user.id (or user_id) and then insert that information into the database?
I'm thinking that I'll need to grab the user's email address and then also have the job_id in the subject line...hmmm....
Does anyone have experience setting up incoming mail processing in Rails 3?

There's a few ways to do this. A common technique is to set the from/reply_to as a custom email address that allows you to do a lookup of the original object. Something like:
class Comment < ActiveRecord::Base
belongs_to :commentable
has_many :comments, :as => :commentable
before_validation :generate_token, :on => :create
validates :token, :presence => true, :uniqueness => true
attr_accessible :token
private
def generate_token
...
end
end
Email is sent with a from/reply_to address like [token]#msg.yoursite.com
(You can also use a comments+[token_or_id]#msg.yoursite.com if you'd like - see the :disposable param supplied by cloudmailin)
class IncomingMailsController < ApplicationController
def create
#comment = Comment.find_by_token(params[:to].split('#')[0])
#comment.comments.create(:body => params[:plain])
render :text => 'success', :status => 200
end
end
If you go the [token]#msg.yoursite.com then you have to setup your dns records properly as described here.
Another option is to store content in the headers of the email. Maybe your headers would look something like this:
X-YOURAPP-OBJECT-ID = 44
X-YOURAPP-OBJECT-TYPE = Job
X-YOURAPP-TARGET-ASSOC = comments
X-YOURAPP-TARGET-ATTR = body
Then your controller would look more like this:
class IncomingMailsController < ApplicationController
def create
headers = Mail::Header.new(params[:message])
object= headers[:x_yourapp_object_type].constantize.find(headers[:x_yourapp_object_id])
object.send(headers[:x_yourapp_target_assoc]).create(headers[:x_yourapp_target_attr] => params[:plain])
render :text => 'success', :status => 200
end
end
The advantage to doing it this way is that it's completely generic. You can have comments on jobs, comments on comments, or whatever. If you create another model that you also want to allow email reply on... just update the headers and you're set. The only issue is that you have to be careful of these headers being stripped out by mail clients. Take a look at this thread for more information about that.
I'm currently using the first technique and it's working very well. However, I'll be refactoring and trying the second technique at the end of this week.

I'm not sure this is a full answer but let me give it a quick shot and I can update it when I have a little more time.
Firstly I would start by having the job perhaps as part of the to address. Use comment+1#domain.com with 1 signifying that this is job number one. This part after the plus is called the is the disposable part in CloudMailin (params[:disposable]. The users email address can then be taken from the message body either using params[:from] or Mail.new(params[:message]).from (sometimes the from address given to the SMTP server is different to the one in the header)
Next you have the comment itself which can simply be the plain param.
This gives something like the following:
def create
#job = Job.find(params[:disposable])
if comment = #job.comments.create(:text => params[:plain], :commenter => User.find_by_email(params[:from])
render :text => 'Success', :status => 401
else
render :text => comment.errors.inspect, :status => 422, :content_type => 'text/plain'
end
I hope that helps. It's a bit rough but hopefully it gives the right idea.

Related

How to test Stripe's invoice_pdf property when it keeps changing?

In my Rails 6 app I have a very simple controller that displays download links to a user's Stripe invoice PDFs:
class ReceiptsController < ApplicationController
before_action :signed_in_user
def index
receipts = current_account.receipts
end
def show
receipt = current_account.receipts.find(params[:id])
stripe_invoice = Stripe::Invoice.retrieve(receipt.stripe_invoice_id)
redirect_to stripe_invoice.invoice_pdf
end
end
Since Stripe doesn't provide permanent invoice URLs (please correct me if I am wrong), I am storing each invoice's Stripe ID in the database and then use that ID to lookup the current URL to the invoice PDF from the Stripe API.
The problem is that this works most of the time but not all the time. The spec that I created for the controller show action fails in about 20 % of cases because the two URLs do not match:
describe ReceiptsController, :type => :controller do
before :each do
#account = FactoryBot.create(:activated_account)
#user = #account.users.create(FactoryBot.attributes_for(:user))
sign_in(#user)
end
describe 'GET #show' do
# The implementation details of this block don't really matter
before :each do
Customers::FindOrCreate.call(#account)
stripe_subscription = Subscriptions::CreateRemote.call(#account,:payment_behavior => "default_incomplete")
#stripe_invoice = stripe_subscription.latest_invoice
#receipt = Receipts::Create.call(#stripe_invoice)
end
# This test fails in about 20 % of cases because the redirect does not go to #stripe_invoice.invoice_pdf but a slightly different URL
it "redirects to Stripe invoice PDF" do
get :show, :params => {:id => #receipt}
expect(response).to redirect_to #stripe_invoice.invoice_pdf
end
end
end
How can this be? Does the invoice_pdf property of a Stripe invoice change every few seconds? I've been trying to work this out for days now but can't get my head around it.
Addition:
This is a typical test failure that I get quite often:
Expected response to be a redirect to <https://pay.stripe.com/invoice/acct_105jfm2HzYSlmhv7/test_YWNjdF8xMDJqc20yS3pZUmxzaHc0LF9NMGZONnFzNUpPTjlObVprd0hvdGpIdWFUamJHTTVxLDQ3Njc3MDY30200oOxX3A1/pdf?s=ap> but was a redirect to <https://pay.stripe.com/invoice/acct_105jfm2HzYSlmhv7/test_YWNjdF8xMDJqc20yS3pZUmxzaHc0LF9NMGZONnFzNUpPTjlObVprd0hvdGpIdWFUamJHTTVxLDQ3Njc3MDY402001iYCSUbn/pdf?s=ap>.
Expected "https://pay.stripe.com/invoice/acct_105jfm2HzYSlmhv7/test_YWNjdF8xMDJqc20yS3pZUmxzaHc0LF9NMGZONnFzNUpPTjlObVprd0hvdGpIdWFUamJHTTVxLDQ3Njc3MDY30200oOxX3A1F/pdf?s=ap" to be === "https://pay.stripe.com/invoice/acct_105jfm2HzYSlmhv7/test_YWNjdF8xMDJqc20yS3pZUmxzaHc0LF9NMGZONnFzNUpPTjlObVprd0hvdGpIdWFUamJHTTVxLDQ3Njc3MDY402001iYCSUbn/pdf?s=ap".

cramp framework sync 'render' correct way using em-synchrony

To describe my problem I attach simple Cramp http://cramp.in/ class.
I add some modification but its mainly work like https://github.com/lifo/cramp-pub-sub-chat-demo/blob/master/app/actions/chat_action.rb
class ChatAction < Cramp::Websocket
use_fiber_pool
on_start :create_redis
on_finish :handle_leave, :destroy_redis
on_data :received_data
def create_redis
#redis = EM::Hiredis.connect('redis://127.0.0.1:6379/0')
end
def destroy_redis
#redis.pubsub.close_connection
#redis.close_connection
end
def received_data(data)
msg = parse_json(data)
case msg[:action]
when 'join'
handle_join(msg)
when 'message'
handle_message(msg)
else
# skip
end
end
def handle_join(msg)
#user = msg[:user]
subscribe
publish(:action => 'control', :user => #user, :message => 'joined the chat room')
end
def handle_leave
publish :action => 'control', :user => #user, :message => 'left the chat room'
end
def handle_message(msg)
publish(msg.merge(:user => #user))
# added only for inline sync tests
render_json(:action => 'message', :user => #user, :message => "this info should appear after published message")
end
private
def subscribe
#redis.pubsub.subscribe('chat') do |message|
render(message)
end
end
def publish(message)
#redis.publish('chat', encode_json(message))
end
def encode_json(obj)
Yajl::Encoder.encode(obj)
end
def parse_json(str)
Yajl::Parser.parse(str, :symbolize_keys => true)
end
def render_json(hash)
render encode_json(hash)
end
end
More about what i try to do is in handle_message method.
I try send messages to client in correct order. First publish message to all subscribers, second render some internal info only for current connected client.
For above code client receives:
{"action":"message","user":"user1","message":"this info should appear after published message"}
{"action":"message","message":"simple message","user":"user1"}
Its not synchronized, because of em-hiredis defferable responses, probably.
So I try to synchronized it this way:
def handle_message(msg)
EM::Synchrony.sync publish(msg.merge(:user => #user))
EM::Synchrony.next_tick do # if I comment this block messages order is still incorrect
render_json(:action => 'message', :user => #user, :message => "this info should appear after published message")
end
end
Now, client handle messages with correct order.
{"action":"message","message":"simple message","user":"user1"}
{"action":"message","user":"user1","message":"this info should appear after published message"}
My questions are:
When I comment EM::Synchrony.next_tick block, messages order is still incorrect. What meaning have EM::Synchrony.next_tick block in this example?
Is this good way to handle inline sync with Cramp or EventMachine ?
Is there a better, clearer way to handle it ?
Thank you!
I found solution of this problem, em-synchrony should work inline out of the box by requiring this library:
require 'em-synchrony/em-hiredis'
class ChatAction < Cramp::Websocket
Using EM::Synchrony.next_tick block is bad idea, with big help of em-synchrony community I add em-hiredis 0.2.1 compatibility patch on github
So now handle_message method looks like this:
def handle_message(msg)
publish(msg.merge(:user => #user))
render_json(:action => 'message', :user => #user, :message => "this info should appear after published message")
end
Don`t forget to take this gem from github
gem 'em-synchrony', :git=> 'git://github.com/igrigorik/em-synchrony.git'
Hope it helps someone.

Showing users unique info when facebook authenticate , with Sinatra/ Ruby

Super beginner here.
Here's what I am trying to do:
Build a basic to do list app, where User X logs in with facebook, adds some items, sees them, logs out. User Y/Z/M/etc, should be able to log in see their OWN list, add their OWN items, etc.
AKA: a standard web app where you log in to your account and see your own info.
What I have so far:
Ability to build a list, log in with Facebook and having it know your name.
However, the list stays the same whether I log in or whether my friend logs in with her account.
What I need to do, and don't know how:
I need each user to be able to create and see their own list, and be able to come back to it and still see it/ add to it, etc.
I don't even know how this is called, would this be a database of users each with their own set of data? Would the lists need to be set up so they could be stored as a chunk of data?
Does it have something to do with this :Sessions in Sinatra using Facebook authentication If so, what?
If anyone could be give me some really really basic directions as to where to go from here, any tutorials or what I should be googling for, that'd be awesome.
Here's my main piece of code (warning: it's really messy) :
require 'sinatra'
require 'data_mapper'
require 'time'
require 'rubygems'
require 'json'
require 'omniauth'
require 'omniauth-facebook'
#TODO require 'omniauth-att'
SCOPE = 'email,read_stream'
DataMapper::setup(:default, "sqlite3://#{Dir.pwd}/queue.db")
class SinatraApp < Sinatra::Base
configure do
set :sessions, true
set :inline_templates, true
set :protection, :except => :frame_options
end
class Note
include DataMapper::Resource
property :id, Serial
property :content, Text, :required => true
property :complete, Boolean, :required => true, :default => false
property :created_at, DateTime
property :updated_at, DateTime
end
class User
include DataMapper::Resource
property :id, Serial
property :uid, String
property :name, String
property :created_at, DateTime
end
###### no clue what this does ##############
DataMapper.finalize
DataMapper.auto_upgrade!
enable :session
use OmniAuth::Builder do
provider :facebook, '464630283595639','5e4c7ad43bf111c10287c981d51127a3',:scope => SCOPE, :display => "popup"
#provider :att, 'client_id', 'client_secret', :callback_url => (ENV['BASE_DOMAIN']
end
###### root ##############
get '/' do
if current_user
#notes = Note.all :order => :id.desc
#title = 'Movie Queue'
erb :home
else
' sign in with Facebook'
end
end
###### authentication ##############
["/sign_in/?", "/signup/?"].each do |path|
get path do
redirect '/auth/facebook'
end
end
get '/auth/:name/callback' do
auth = request.env["omniauth.auth"]
user = User.first_or_create({ :uid => auth["uid"]}, {
:uid => auth["uid"],
:name => auth["first_name"],
:created_at => Time.now })
session[:user_id] = user.id
redirect '/'
end
helpers do
def current_user
#current_user ||= User.get(session[:user_id]) if session[:user_id]
end
end
##list making part###
post '/' do
n = Note.new
n.content = params[:content]
n.save
redirect '/'
end
get '/:id/delete' do
n = Note.get params[:id]
if n.destroy
redirect '/', :notice => 'Note deleted successfully.'
else
redirect '/', :error => 'Error deleting note.'
end
end
get '/:id/complete' do
n = Note.get params[:id]
n.complete = n.complete ? 0 : 1 # flip it
n.save
redirect '/'
end
########## logout and error handlers #############
get '/logout' do
session[:user_id] = nil
redirect '/'
end
get '/auth/failure' do
erb "<h1>Authentication Failed:</h1><h3>message:<h3> <pre>#{params}</pre>"
end
get '/auth/:provider/deauthorized' do
erb "#{params[:provider]} has deauthorized this app."
end
get '/protected' do
throw(:halt, [401, "Not authorized\n"]) unless session[:authenticated]
erb "<pre>#{request.env['omniauth.auth'].to_json}</pre><hr>
<a href='/logout'>Logout</a>"
end
end
########## don't know what this is #############
SinatraApp.run! if __FILE__ == $0
Disclaimer: I don't know Datamapper, but this should get you going.
There needs to be a way to associate a note with a user. This needs a table in the database, some would call it users_notes, personally I prefer users_rel_notes, (perhaps Datamapper has a convention for this… YMMV). Anyway, the table will have a minimum of 2 columns - the user's id, and the note id. You don't need a separate table as I wrote before (I'm lacking a bit of sleep, sorry!), that would be for a many to many relationship where a user could have several notes and a note could be associated with several users. For what you have, where only the owner of a note has access to it, it requires a one to many relationship. You could add a column to the notes table to store the user id.
Then, in the User class, add an association to the Note class, it's a one to many association and in Datamapper that's a has n, e.g.
has n, :notes
Now when you have a user instance, you can (probably) call the notes for that user via:
user.notes
I see you have the helper current_user defined, so if someone is logged on you could call current_user.notes to get back all the notes for the logged in user.
Remember, when you add a note to make sure you add a record to the association table, (probably, read the link) via user.notes << my_new_note.
The session is the information you keep around to identify the user and any other little bits of info that you may recurrently need. The likelyhood is, you're just storing an id for the user, or the facebook token that identifies them, and then during a request, if it's needed then you'll look inside the cookie, grab the id, look up that user by the id and get a user instance. Session info can be stored in several ways, most often cookies but you can use anything you would use to store any other data.

RSpec Testing Controller with Model.create

I'm trying to test a controller to ensure that only an authorized party can view the correct child object using RSpec. I cant figure out what I'm doing wrong as I'm getting this error:
ActiveRecord::RecordInvalid: Validation failed: Company can't be blank
I have a Plan object and a Company object. The Store can have many plans (think of a pest control Company). I want to test that given a known scenario I can retrieve the plan fo the Company (assuming there is only one).
The Plan looks like this:
class Plan < ActiveRecord::Base
before_save :default_values
# Validation
validates :amount, :presence => true
validates :company, :presence => true
# Plans belong to a particular company.
belongs_to :company, :autosave => true
scope :find_all_plans_for_company, lambda {
|company| where(:company_id => company.id)
}
# Other code ...
end
The Company looks like this:
class Company < ActiveRecord::Base
validates :name, :presence => true
validates :phone1, :presence => true
validates_format_of :phone1, :phone2,
:with => /^[\(\)0-9\- \+\.]{10,20}$/,
:message => "Invalid phone number, must be 10 digits. e.g. - 415-555-1212",
:allow_blank => true,
:allow_nil => true
has_many :users
has_many :plans
end
.. controller looks like this
def index
#plans = Plan.find_all_plans_for_company(current_user.company)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #plans }
end
end
.. and my RSpec test looks like this (excuse me if its full of gimmickery, I'm just splunking around with it and cannot get it to work).
describe PlansController do
def valid_attributes
{
:company_id => 1,
:amount => 1000
}
end
describe "GET index" do
it "should return the Plans for which this users company has" do
#company = mock_model(Company, :id => 1, :name => "Test Company", :phone1 => "555-121-1212")
Company.stub(:find).with(#company.id).and_return(#company)
controller.stub_chain(:current_user, :company).and_return(#company)
plan = Plan.create! valid_attributes
get :index, {}
assigns(:plans).should eq([plan])
end
# Other tests ...
end
end
The problem is, when I try this (or any of the crazy other variants I've tried) I get this error:
ActiveRecord::RecordInvalid: Validation failed: Company can't be blank
I'm not sure why this is happening as I thought the Company.stub call would handle this for me. But apparently not.
What am I missing here and what am I doing wrong? How can I get this test to pass?
Let's peel back the layers on this spec, to make sure things make sense (and to make sure I understand what's going on). First, what are you testing?
it "should return the Plans for which this users company has" do
...
assigns(:plans).should eq([plan])
So you want to check that the plans associated with the company of the current user are assigned to #plans. We can stub or mock out everything else.
Looking at the controller code, we have:
def index
#plans = Plan.find_all_plans_for_company(current_user.company)
What do we need to get this to work, without hitting the database and without depending on the models?
First of all, we want to get a mock company out of current_user.company. This is what these two lines in your spec code do:
#company = mock_model(Company, :id => 1, :name => "Test Company", :phone1 => "555-121-1212")
controller.stub_chain(:current_user, :company).and_return(#company)
This will cause current_user.company to return the mock model #company. So far so good.
Now to the class method find_all_plans_for_company. This is where I'm a bit confused. In your spec, you stub the find method on Company to return #company for id = 1.
But really, wouldn't it suffice just to do something like this in your controller code?:
#plans = current_user.company.plans
If you did it this way, then in your test you could just mock a plan, and then return it as the plans association for your mock company:
#plan = mock_model(Plan)
#company = mock_model(Company, :plans => [ #plan ])
controller.stub_chain(:current_user, :company).and_return(#company)
Then the assignment should work, and you don't need to actually create any model or hit the database. You don't even need to give your mock company an id or any other attributes, which anyway are irrelevant to the spec.
Maybe I'm missing something here, if so please let me know.
Why do you need to mock?
My standard testing setup is to use Database Cleaner which clears out the database from any records created during tests. In this way, the tests are run with real database records which are consequently deleted from the test database after each test.
You might also like taking a look at Factory Girl for creating instances of your models during testing (makes it easy to create 10 company records, for example).
See:
http://rubygems.org/gems/database_cleaner
http://rubygems.org/gems/factory_girl
I have three thoughts coming up that could resolve your issue:
Try adding attr_accessible :company_id to Plan class.
Because mock_model does not actually save to the database when you create a Plan with company_id of 1 it fails validation since it is not present in the database.
Ensure before_save :default_values in Plan class does not mess with company_id attribute of the newly created instance.

Ruby JSON issue

I know the title is a bit vague, but I dont know what to put on there.
I'm developing an API with Sinatra for our backend in Ruby. The thing is that I need to be able to pass JSON to the service representing a User. The problem I'm facing is that when I run my tests it does not work, but doing it manually against the service it does work. I'm guessing there is an issue with the JSON format.
I've updated my User model to rely on the helpers from ActiveModel for the JSON serialization. I was running in too much problems with manual conversions. This is what the base User model looks like:
class User
include ActiveModel::Serializers::JSON
attr_accessor :login, :email, :birthday, :created_at, :updated_at, :password_sha, :password_salt
# Creates a new instance of the class using the information stored
# in the hash. If data is missing then nill will be assigned to the
# corresponding property.
def initialize(params = {})
return if params.nil?
self.login = params[:login] if params.key?("login")
self.email = params[:email] if params.key?("email")
self.birthday = Time.parse(params[:birthday]) rescue Time.now
if params.key?("password_salt") && params.key?("password_sha")
self.password_salt = params["password_salt"]
self.password_sha = params["password_sha"]
elsif params.key?("password")
self.set_password(params[:password])
end
self.created_at = Time.now
end
def attributes
{:login => self.login, :email => self.email, :birthday => self.birthday, :created_at => self.created_at, :updated_at => self.updated_at, :password_sha => self.password_sha, :password_salt => self.password_salt}
end
def attributes=(params = {})
self.login = params['login']
self.email = params['email']
self.birthday = params['birthday']
self.created_at = params['created_at']
self.updated_at = params['updated_at']
self.password_sha = params['password_sha']
self.password_salt = params['password_salt']
end
end
I'm using Cucumber, Rack::Test and Capybara to test my API implementation.
The code of the API application looks like this:
# This action will respond to POST request on the /users URI,
# and is responsible for creating a User in the various systems.
post '/users' do
begin
user = User.new.from_json(request.body.read)
201
rescue
400
end
end
In the above piece I expect the json representation in the request body. For some reason the params hash is empty here, don't know why
The test section that makes the actuall post looks like this:
When /^I send a POST request to "([^\"]*)" with the following:$/ do |path, body|
post path, User.new(body.hashes.first).to_json, "CONTENT_TYPE" => "application/json"
end
The example output JSON string generated by the User.rb file looks like this:
"{"user":{"birthday":"1985-02-14T00:00:00+01:00","created_at":"2012-03-23T12:54:11+01:00","email":"arne.de.herdt#gmail.com","login":"airslash","password_salt":"x9fOmBOt","password_sha":"2d3afc55aee8d97cc63b3d4c985040d35147a4a1d312e6450ebee05edcb8e037","updated_at":null}}"
The output is copied from the Rubymine IDE, but when I submit this to the application, I cannot parse it because:
The params hash is empty when using the tests
doing it manually gives me the error about needing at least 2 octets.

Resources