Webhooks in Rails Controller for Shopify App - ruby-on-rails

I have a webhooks controller which listens for when a new customer is created in Shopify.
def customer_created_callback
#first_name = params[:first_name]
#last_name = params[:last_name]
#email = params[:email]
#customer = Customer.new(first_name: #first_name, last_name: #last_name, email: #email)
if #customer.save
redirect_to customers_url
else
puts "error"
end
end
I am wondering if I should be creating the customer in this controller action. It seems like I should be handling the customer creation in Customer#create. But I'm not sure if it's a good idea to be passing all these params to a new action.
What's a good example of where I should be redirecting_to? I thought the idea of a webhook is that it's happening in the background so no page is actually going to be rendered..
Hope those make sense.

It seems like I should be handling the customer creation in Customer#create
Where the code lives is up to you but you must keep it DRY. Personally I like to use Service Objects since they make testing easier.
What's a good example of where I should be redirecting_to?
You need to return a 200 response with no content:
render :nothing => true, :status => 200
Typically you'll use a background job which will use the service object when it runs. This post on Webhook Best Practices is an excellent resource to get acquainted with the in's and out's of web hooks.

Related

Best way to save attribute inside save block?

In my controllers I often have functionality like this:
#account = Account.new(account_params)
if #account.save
if #account.guest?
...
else
AccountMailer.activation(#account).deliver_later
#account.update_column(:activation_sent_at, Time.zone.now)
flash[:success] = "We've sent you an email."
redirect_to root_path
end
end
What's the best way to send an email and update the activation_sent_at attribute without having to save the record twice? Calling update_column doesn't feel right to me here because AFAIK it creates an extra SQL query (correct me if I'm wrong).
Your code is fine. I wouldn't change it.
For example, you might be tempted to do something like this:
#account = Account.new(account_params)
#account.activation_sent_at = Time.zone.now unless #account.guest?
if #account.save
if #account.guest?
...
else
AccountMailer.activation(#account).deliver_later
flash[:success] = "We've sent you an email."
redirect_to root_path
end
end
Aside from the small issue that there's now repeated logic around #account.guest?, what happens if AccountMailer.activation(#account).deliver_later fails? (When I say "fails", I mean - for example - AccountMailer has been renamed, so the controller returns a 500 error.)
In that case, you'd end up with a bunch of account records which have an activation_sent_at but were never sent an email; and you'd have no easy way to distinguish them.
Therefore, this code warrants running two database calls anyway: One to create the record, and then another to confirm that an email was sent. If you refactor the code to only perform a single database call, then you'll become vulnerable to either:
Sending an email to a non-created user, or
Marking a user with activation_sent_at despite o email being sent.
The controller should be doing two transactions, not one. Which is why I said: Don't change it.

There is a better way to validate in Rails?

The example below is how I'm authenticating my users today:
def create
if is_internal_request(params[:authenticity_token])
#user = User.authenticate(params[:email], params[:password])
if #user
session[:user_id] = #user.id
render :json => true
else
render :json =>
{
error:
{
code: 1,
message: t('globals.errors.authentication.user-not-found')
}
}.to_json
end
end
end
Pay attention to this fragment:
render :json =>
{
error:
{
code: 1,
message: t('globals.errors.authentication.user-not-found')
}
}.to_json
Based on it, I want to know if it's organized and solid. I mean, is that something in the edge of the right way?
Forward thinking
Lets suppose that there are some places in my application that check about user's e-mail availability. I want to be DRY and reuse that verification every time I need to use it. If the way that I'm doing the validation (as below) isn't "perfect" at all, how can I create a validation layer that can be useful every time I want?
I mean, instead of create and recreate that fragment of code each time that I want to validate, what's the best way to do something like this?:
email = params[:email]
unless email_is_available(email)
render :json => { message: 'E-mail isn't available' }
end
With "what's the best way to do something like this?" I'm saying, where I have to place email_is_available function to get it working right?
Every Controller can access ApplicationController methods as they inherit from it.
Anyways, I'd recommend using a gem like Devise for a more complete solution.
GL & HF.
What about something like this?
if #user.valid?
render :json => true
else
error_hash = {}
#user.errors.each{|k,v| error_hash[k] = "#{k.capitalize} #{v}"}
#this will give you a hash like {"email" => "Email is not available"}
render :json => {:errors => error_hash}
end
At the client end, you will get this back (eg as an object called data), see if there is a value for data.errors and then display the various error messages to the user.
Note i haven't plugged in any translation stuff but you can do that :)
I'd suggest you take a look at Ryan Bates' Railscast on authentication from scratch. It walks you through all the issues and is much lighter weight than relying on something as big and heavy as Devise.
http://railscasts.com/episodes/250-authentication-from-scratch

Rails service objects

I'm using service objects to abstract a stripe payment feature into it's own class. I'm using this method https://gist.github.com/ryanb/4172391 talked about by ryan bates.
class Purchase
def initialize(order)
#order = order
end
def create_customer
customer = stipe create customer
if customer
charge_customer(customer)
else
stipe error in creating customer
end
end
def charge_customer(customer)
if charge is sucessfull
update_order(charge details)
else
stripe error in charging card
end
end
def update_order
#order.update(payment attributes)
end
end
Then in the order controller i'm doing something like
def create
#order = Order.new(params[:order])
if #order.save
payment = Payment.new(#order)
else
render 'new' with flash message "payment error"
end
end
My question is, how do i get the stipe error messages("stipe error in creating customer" and "stripe error in charging card") to display to the user?
Or can i call a service object in the order model and add it to order error messages? For example,
Order controller
#order.save_with_payment
Order model
def save_with_payement
payment = Payment.new(self)
#update order
self.payment_token = payment.token
etc
end
If i can do that with the model, how to i make a validation that shows the stripe errors?
Thanks in advance.
First of all try to separate concerns as possible. It already feels like Your Purchase/Payment class is doing too much, probably it should delegate part of it's routines to other service objects.
Second, I agree with phoet. I don't see the reason why You wouldn't pass params hash to service object. In our latest project we fully rely on service objects we call Factories to produce/manipulate our model objects. In Your case You could do like this:
class OrderFactory
def self.create(params)
new(params).create
end
def initialize(params)
#order = Order.new(params[:order])
end
def create
#payment = Payment.new(self)
#order.payment_token = payment.token
#....
end
end
Talking about validations - You can add validation methods to plain Ruby objects. For example by using ActiveModel:
http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/
Then You can create custom validators, like Ryan suggested.
Or You can use a gem like Virtus and add some custom validation rules.
i think that the service should be responsible for handling all this. so it might look like this
def create
#payment = Payment.new(params[:order])
if #order = #payment.execute_transaction
[...]
else
[...]
end
end
so the payment service would handle creating and persisting the order and also might be adding validation errors. you could also return some kind of #result object that might consist of the order and depending error messages.
I asked a very similar question just one day ago. The response I received was very helpful indeed (credit to #gary-s-weaver) so take a look.
Rails generic errors array
I went with the rescue_from approach in the end and it works well for me. Let me know how you get on as I'm very interested in this area. And if you need any code samples give me a shout.
I've written a gem for service objects. Check out peafowl gem.

Testing an API resource with RSpec

I've done a bit of googling on the topic, and I'm still confused.
I'm building a custom help page with the Zendesk API Ruby Client, and I'm at a stage when I need to test the creation of the ZendeskAPI::Ticket resource. The following code is in the spec/features directory. It fills out a form with valid values and submits the form to the #create action. Fairly standard, simple stuff.
require 'spec_helper'
feature 'ticket creation' do
scenario 'user creates new ticket' do
visit root_path
fill_in 'Name', with: 'Billybob Joe'
fill_in 'Email', with: 'joe#test.com'
fill_in 'Subject', with: 'Aw, goshdarnit!'
fill_in 'Issue', with: 'My computer spontaneously blew up!'
click_button 'Create Ticket'
expect(page).to have_content('Ticket details')
end
end
And here is the relevant part of the Tickets controller. The ticket_valid? method supplies minimal validations for the options hash and client is an instance of the ZendeskAPI::Client.
def create
options = { subject: params[:subject], comment: { value: params[:issue] },
requester: params[:email], priority: 'normal' }
if ticket_valid? options
flash[:success] = 'Ticket created.'
#ticket = ZendeskAPI::Ticket.create(client, options)
redirect_to ticket_path(#ticket.id)
else
flash[:error] = 'Something went wrong. Try again.'
redirect_to root_url
end
end
Problem is, whenever I run the tests, an actual ticket is created in the Zendesk backend that I'll have to delete manually later when I just want to test for successful form submission without actually creating a ticket.
So my question is, how can I test the ticket creation form without creating an actual ticket in the Zendesk backend whenever I run the tests?
The articles and blogs I've been reading as a result of my googling vaguely refers to using RackTest, while others suggest not using Capybara at all for this sort of thing, which leaves me even more confused. I'm still relatively new to RSpec and even newer to dealing with building Rails apps with an API, so a clear explanation would be great.
Thanks in advance!! You're awesome.
One way to do this would be abstract away your interface to ZenDesk into your own class and then mock it in your tests.
For example, you could create an interface class:
class ZendeskGateway
def create_ticket(client, options)
ZendeskAPI::Ticket.create(client, options)
end
end
Then, in your code, you replace the usage of the Zendesk API in your controller with your interface class:
class TicketsController < ApplicationController
attr_accessor :zendesk_gateway
after_initialize :init
def init
#zendesk_gateway = ZendeskGateway.new
end
def create
options = { subject: params[:subject], comment: { value: params[:issue] },
requester: params[:email], priority: 'normal' }
if ticket_valid? options
flash[:success] = 'Ticket created.'
#ticket = #zendesk_gateway.create_ticket(client, options)
redirect_to ticket_path(#ticket.id)
else
flash[:error] = 'Something went wrong. Try again.'
redirect_to root_url
end
end
end
Now that it is abstracted, you can using a mocking framework (like mocha) to stub out the method during your test so that it doesn't actually call out to Zendesk:
zendesk_ticket = ZendeskAPI::Ticket.new(client, :id => 1, :priority => "urgent")
#controller.zendesk_gateway.expects(:create_ticket).returns(zendesk_ticket)
This was a very quick/dirty example. But hopefully you can see the general idea.
If you don't want to call the Zendesk, you'll have to create a "test double" instead of the actual call. The test double capability that comes with RSpec is described minimally at https://github.com/rspec/rspec-mocks, but is covered more comprehensively in blogs and books.
The answer posted simultaneously to this discusses creating a separate class, but still seems to involve creating a Zendesk ticket. You don't actually need a separate class and you don't need to create any ZendeskAPI objects at all. Instead, you would stub ZendeskAPI::Ticket#create to return a test double which in turn would need to serve up whatever Zendesk ticket methods the rest of your test needs, which at least includes id.
The use of Capybara is really a secondary issue and refers to how you drive the test. But note that your test currently requires rendering the ticket page and checking the content of that page. If you want to test "just" your controller, then you can/should just test that it makes the proper calls (e.g. to ZendeskAPI::Ticket) and redirects to the appropriate page. Further, if you do just that, you have much less to simulate in your test double.

Delaying render_to with Resque

I am trying to replicate the setup Ryan Bates has in this railscast on Resque, where he queues up a third party service web request and then updates his results page with results.
I am designing an application that will interact with another Rails app, not a browser, and would like to replicate analogous behavior, with key difference being that only JSON output is expected
Currently I have something like this: (my models are Lists and Tasks, a List has_many Tasks and a Task belongs_to a List.
My lists_controller.rb
def show
Resque.enqueue(TaskDataFetcher,params[:id])
# confused if I need to have a render_to below this.
end
In task_data_fetcher.rb
require "net/http"
require "uri"
class TaskDataFetcher
#queue = :tasks_queue
def self.perform(id)
list = List.new(:id => id)
url = "taskservice.com/" + id + ".json"
uri = URI.parse(url)
response = Net::HTTP.get_response(uri)
task = Task.new(:contents => response.body)
task.list = list
# how to return this to the requesting server????
end
end
In the Railscast you see that result doesn't automatically update after the Resque task finishes, he has to reload the page several times, re-making the show request. So if you want to replicate this behaviour you could do something like:
def show
list = List.find(params[:id])
if list
respond_to do |format|
format.json {render :json => list.to_json}
end
else
Resque.enqueue(TaskDataFetcher, params[:id])
render :nothing => true, :status => 202
end
end
Requerement:
So your user is requesting your service to see some tasks. And you have to fetch those from another service taskservice.com. Then i think you have to do this through database for persistency.
Suggestion:
You can have a model like TaskRequest having attributes
`id` # must ;)
`task_list` # might be xml or whatever format suits you best
`is_received` # boolean
In your show method,
You create a TaskRequest entry and render a view which will show a loading type thing and will be requesting for task via ajax. The ajax response should return the task list and the is_received. However, once you get is_received true with a content you should request again.
In parallel, your TaskDataFetcher should receive two ids. One that you are sending now and another is of TaskRequest id. So after fetching the data from the service it will store that in the TaskRequest table and will update the is_recieve to true. Setting it true will eventually turn off requesting for this data anymore.
well the whole explanation might seem a bit hazy. Just let me know if you didnt any part or you need anything else specifically.
Note: It is something like the way SO shows the code formatting while answering a question ;)

Resources