I have been using ruby to make API calls and operating strictly in the terminal for some time. I am now in the process of learning more about rails and trying to get out of my terminal. How can I, using rails 4.0, put a variable to the screen from an already existing .rb file? I am confused as to where I should write the API request to get the variable- Is it a controller, can I write it directly in a view, etc.
Sample idea:
#test.rb
call= "/api/v2/surveys/"
auth = {:username => "test", :password => "password"}
url = HTTParty.get("https://surveys.com#{call}",
:basic_auth => auth,
:headers => { 'ContentType' => 'application/json' } )
response = JSON.parse(url.body)
survey_ids = response["surveys"].map { |s| s["id"] }
survey_ids.each do |i|
puts i
end
That is a sample .rb script I already have. The difference is I would like for puts i to happen on a web app when a page is loaded instead of me running the script in my terminal. What would I use in rails to make that happen?
It depends entirely on how your application is going to be set up but here's a basic example:
Say you have a Survey model:
class Survey < ActiveRecord::Base
attr_accessible :survey_id
end
You can place your call for a list of surveys (I'm assuming that's what your code does) in the SurveysController:
class SurveysController < ApplicationController
def index
#surveys = Survey.all
end
def show
#survey = Survey.find(params[:id])
end
def pull_surveys
call= "/api/v2/surveys/"
auth = {:username => "test", :password => "password"}
url = HTTParty.get("https://surveys.com#{call}",
:basic_auth => auth,
:headers => { 'ContentType' => 'application/json' } )
response = JSON.parse(url.body)
survey_ids = response["surveys"].map { |s| s["id"] }
survey_ids.each do |i|
Survey.create(survey_id: i)
end
end
After calling the pull_surveys method, you'll actually have surveys your view can load so in your views for the Survey Model you can use #surveys or #survey (depending on which view you're in) and serve up whatever you want (e.g #survey.survey_id in show to show that specific survey's ID).
Note that you'll want to be careful about where you place your API call methods - I placed it in the controller for simplicity's sake but you may not want to do this.
There's lots of useful info in the rails guides to get you started: http://guides.rubyonrails.org/index.html
I have a SOAP web service created with washout that has an action with a parameter of type base64Binary(Which I assume is the equivalent of byte[] in C#).
I'm sending the following variable to my web service parameter:
file_data = Base64.encode64(File.binread("/path/to/my/file"))
It comes back with the error:
RuntimeError (Invalid WashOut simple type: base64Binary)
Am I not creating the datatype correctly from my file path?
Here is the controller for the web service:
class ServiceController < ApplicationController
include WashOut::SOAP
soap_action "import_file",
:args => { :data => :base64Binary, :name => :string},
:return => :string
def import_file
render :soap => ("response")
end
# You can use all Rails features like filtering, too. A SOAP controller
# is just like a normal controller with a special routing.
before_filter :dump_parameters
def dump_parameters
Rails.logger.debug params.inspect
end
end
Here is the code for my client:
require 'savon'
class ServiceTester
def self.initiate
# create a client for your SOAP service
client = Savon.client do
wsdl "http://localhost:3000/service/wsdl"
end
file_data = Base64.encode64(File.binread("/path/to/my/file"))
response = client.call(:import_file, message: { data: file_data, name: "myfilename" })
end
end
Where did you get :base64Binary? There is no such type for WSDL parameter:
operation = case type
when 'string'; :to_s
when 'integer'; :to_i
when 'double'; :to_f
when 'boolean'; nil
when 'date'; :to_date
when 'datetime'; :to_datetime
when 'time'; :to_time
else raise RuntimeError, "Invalid WashOut simple type: #{type}"
end
Try :string instead.
I'm working with the Buffer App API with HTTParty to try and add posts via the /updates/create method, but the API seems to ignore my "text" parameter and throws up an error. If I do it via cURL on the command line it works perfectly. Here's my code:
class BufferApp
include HTTParty
base_uri 'https://api.bufferapp.com/1'
def initialize(token, id)
#token = token
#id = id
end
def create(text)
BufferApp.post('/updates/create.json', :query => {"text" => text, "profile_ids[]" => #id, "access_token" => #token})
end
end
And I'm running the method like this:
BufferApp.new('{access_token}', '{profile_id}').create('{Text}')
I've added debug_output $stdout to the class and it seems to be posting OK:
POST /1/updates/create.json?text=Hello%20there%20why%20is%20this%20not%20working%3F&profile_ids[]={profile_id}&access_token={access_token} HTTP/1.1\r\nConnection: close\r\nHost: api.bufferapp.com\r\n\r\n"
But I get an error. Am I missing anything?
I reviewed the API, and the updates expect the JSON to be in the POST body, not the query string. Try :body instead of :query:
def create(text)
BufferApp.post('/updates/create.json', :body => {"text" => text, "profile_ids[]" => #id, "access_token" => #token})
end
I'm attempting to add a subscription to Google Reader, using it's API, however I'm getting the following error:
execution expired
I've had no problems reading (using 'get') a list of subscriptions or tags. But it times out when I attempt to add a sub (using 'post')
The code is written in Ruby on Rails and I'm using HTTParty to handle the communication with the web service.
My code is as follows (I'm still new to Ruby/Rails so sorry for any bad practices included below. I'm more than happy to have them pointed out to me):
class ReaderUser
# Include HTTParty - this handles all the GET and POST requests.
include HTTParty
...
def add_feed(feed_url)
# Prepare the query
url = "http://www.google.com/reader/api/0/subscription/quickadd?client=scroll"
query = { :quickadd => feed_url, :ac => 'subscribe', :T => #token }
query_as_string = "quickadd=#{CGI::escape(feed_url)}&ac=subscribe&T=#{CGI::escape(#token.to_s)}"
headers = { "Content-type" => "application/x-www-form-urlencoded; charset=UTF-8", "Content-Length" => query_as_string.length.to_s, "Authorization" => "GoogleLogin auth=#{#auth}" }
# Execute the query
self.class.post(url, :query => query, :headers => headers)
end
...
end
For reference, this is how I'm obtaining the token:
# Obtains a token from reader
# This is required to 'post' items
def get_token
# Populate #auth
get_auth
# Prepare the query
url = 'http://www.google.com/reader/api/0/token'
headers = {"Content-type" => "application/x-www-form-urlencoded", "Authorization" => "GoogleLogin auth=#{#auth}" }
# Execute the query
#token = self.class.get(url, :headers => headers)
end
# Obtains the auth value.
# This is required to obtain the token and for other queries.
def get_auth
# Prepare the query
url = 'https://www.google.com/accounts/ClientLogin'
query = { :service => 'reader', :Email => #username, :Passwd => #password }
# Execute the query
data = self.class.get(url, :query => query)
# Find the string positions of AUTH
auth_index = data.index("Auth=") + 5
# Now extract the values of the auth
#auth = data[auth_index,data.length]
end
I'd be happy to provide any additional information required.
Thanks in advance!
After a great deal of messing around, I've found the solution!
I simply had to set the Content-Length to "0". Previously I was setting it to the length of the 'query' as per the PHP class I was basing it on (greader.class.php). I mention this just in case someone else has the same problem.
I'm looking to send raw post data (e.g. unparamaterized JSON) to one of my controllers for testing:
class LegacyOrderUpdateControllerTest < ActionController::TestCase
test "sending json" do
post :index, '{"foo":"bar", "bool":true}'
end
end
but this gives me a NoMethodError: undefined method `symbolize_keys' for #<String:0x00000102cb6080> error.
What is the correct way to send raw post data in ActionController::TestCase?
Here is some controller code:
def index
post_data = request.body.read
req = JSON.parse(post_data)
end
I ran across the same issue today and found a solution.
In your test_helper.rb define the following method inside of ActiveSupport::TestCase:
def raw_post(action, params, body)
#request.env['RAW_POST_DATA'] = body
response = post(action, params)
#request.env.delete('RAW_POST_DATA')
response
end
In your functional test, use it just like the post method but pass the raw post body as the third argument.
class LegacyOrderUpdateControllerTest < ActionController::TestCase
test "sending json" do
raw_post :index, {}, {:foo => "bar", :bool => true}.to_json
end
end
I tested this on Rails 2.3.4 when reading the raw post body using
request.raw_post
instead of
request.body.read
If you look at the source code you'll see that raw_post just wraps request.body.read with a check for this RAW_POST_DATA in the request env hash.
Version for Rails 5:
post :create, body: '{"foo": "bar", "bool": true}'
See here - body string parameter is treated as raw request body.
I actually solved the same issues just adding one line
before simulating the rspec post request. What you do
is to populate the "RAW_POST_DATA". I tried to remove
the attributes var on the post :create, but if I do so,
it do not find the action.
Here my solution.
def do_create(attributes)
request.env['RAW_POST_DATA'] = attributes.to_json
post :create, attributes
end
In the controller the code you need to read the JSON is
something similar to this
#property = Property.new(JSON.parse(request.body.read))
Looking at stack trace running a test you can acquire more control on request preparation:
ActionDispatch::Integration::RequestHelpers.post => ActionDispatch::Integration::Session.process =>
Rack::Test::Session.env_for
You can pass json string as :params AND specify a content type "application/json". In other case content type will be set to "application/x-www-form-urlencoded" and your json will be parsed properly.
So all you need is to specify "CONTENT_TYPE":
post :index, '{"foo":"bar", "bool":true}', "CONTENT_TYPE" => 'application/json'
For those using Rails5+ integration tests, the (undocumented) way to do this is to pass a string in the params argument, so:
post '/path', params: raw_body, headers: { 'Content-Type' => 'application/json' }
I was searching very long for how to post raw JSON content in a integration test (Rails 5.1). I guess my solution could also help in this case.
I looked up the documentation and source code for the post method: https://api.rubyonrails.org/v5.1/classes/ActionDispatch/Integration/RequestHelpers.html#method-i-post
This directed me to the process method for more details: https://api.rubyonrails.org/v5.1/classes/ActionDispatch/Integration/Session.html#method-i-process
Thanks to this, I finally found out what parameters are accepted by the process and thus post method.
Here's what my final solution looked like:
post my_url, params: nil, headers: nil, env: {'RAW_POST_DATA' => my_body_content}, as: :json
If you are using RSpec (>= 2.12.0) and writing Request specs, the module that is included is ActionDispatch::Integration::Runner. If you take a look at the source code you can notice that the post method calls process which accepts a rack_env parameter.
All this means that you can simply do the following in your spec:
#spec/requests/articles_spec.rb
post '/articles', {}, {'RAW_POST_DATA' => 'something'}
And in the controller:
#app/controllers/articles_controller.rb
def create
puts request.body.read
end
Using Rails 4, I was looking to do this to test the processing of raw xml that was being posted to the controller. I was able to do it by just providing the string to the post:
raw_xml = File.read("my_raw.xml")
post :message, raw_xml, format: :xml
I believe if the parameter provided is a string, it just gets passed along to the controller as the body.
In rails, 5.1 the following work for me when doing a delete request that needed data in the body:
delete your_app_url, as: :json, env: {
"RAW_POST_DATA" => {"a_key" => "a_value"}.to_json
}
NOTE: This only works when doing an Integration test.
The post method expects a hash of name-value pairs, so you'll need to do something like this:
post :index, :data => '{"foo":"bar", "bool":true}'
Then, in your controller, get the data to be parsed like this:
post_data = params[:data]
As of Rails 4.1.5, this was the only thing that worked for me:
class LegacyOrderUpdateControllerTest < ActionController::TestCase
def setup
#request.headers["Content-Type"] = 'application/json'
end
test "sending json" do
post :index, '{"foo":"bar", "bool":true}'.to_json, { account_id: 5, order_id: 10 }
end
end
for a url at /accounts/5/orders/10/items. This gets the url params conveyed as well as the JSON body. Of course, if orders is not embedded then you can leave off the params hash.
class LegacyOrderUpdateControllerTest < ActionController::TestCase
def setup
#request.headers["Content-Type"] = 'application/json'
end
test "sending json" do
post :index, '{"foo":"bar", "bool":true}'.to_json
end
end
In Rails 4 (at least in 4.2.11.3) there's no easy way to test your controllers that consume json (functional tests). For parsing json in a running server the ActionDispatch::ParamsParser middleware is responsible. Controller tests though rely on Rack, which can't parse json to this day (not that it should).
You can do:
post :create, body_params.to_json
or:
post :update, body_parmas.to_json, url_params
But body_params won't be accessible in the controller via params. You've got to do JSON.parse(request.body.read). So the only thing that comes to mind is:
post :update, url_params.merge(body_params)
That is, in tests pass everything via parameters (application/x-www-form-urlencoded). In production the body will be parsed by ActionDispatch::ParamsParser to the same effect. Except that your numbers become strings (and possibly more):
# test/controllers/post_controller_test.rb
post :update, {id: 1, n: 2}
# app/controller/posts_controller.rb
def update
p params # tests:
# {"id"=>"1", "n" => "2", "controller"=>"posts", "action"=>"update"}
# production
# {"id"=>"1", "n" => 2, "controller"=>"posts", "action"=>"update"}
end
If you're willing to parse json in controllers yourself though you can do:
# test/controllers/post_controller_test.rb
post_json :update, {n: 2}.to_json, {id: 1}
# app/controller/posts_controller.rb
def update
p JSON.parse(request.body.read) # {"id"=>"1", "n" => 2, "controller"=>"posts", "action"=>"update"}
end
post :index, {:foo=> 'bar', :bool => 'true'}