I have to make a change to an API developed in Ruby On Rails that looks like this:
class V0::PythonsController < ApplicationController
skip_before_action :authorize_request
# POST v0/python/import
def import
result = { status: :error }
data = eval(AwsTool.decrypt(params["code"])).first
if data.class == Hash
user = User.find_by(id: data[:ot_user_id])
activity_type = ActivityType.find_by(id: data[:activity_type])
if user.nil?
result[:msg] = "user not found"
elsif activity_type.nil?
result[:msg] = "activity type not found"
else...
I pass to it some data in the "code" param, that is then decrypted and then explored. I want to add an if clause so when I call the API from a different origin no encryption and decryption takes place. So I have made this change:
class V0::PythonsController < ApplicationController
skip_before_action :authorize_request
# POST v0/python/import
def import
result = { status: :error }
if params["origin"] != 'trusted'
data = eval(AwsTool.decrypt(params["code"])).first
else
data = params["code"]
end
if data.class == Hash
user = User.find_by(id: data[:ot_user_id])
activity_type = ActivityType.find_by(id: data[:activity_type])
...
The problem is that data.class is not a Hash object, its a String. I have tried different solutions to convert the object from String to Hash like t_hash and other similar functions but they didn't work. I got some errors about params not being permitted, I tried to add the permit to them but still fails.
Any other idea?
It is failing because you forgot to call eval on the code. Do this:
data = eval(params["code"])
By the way, evaling input is very dangerous. I hope you trust whoever is using this API.
Related
I am working on a project to do CRUD Operations to firebase. I made use of this to help facilitate and link my ruby project to firebase.
Functions:
def delete_firebase(event_params,rootpath="Events/")
query = init_firebase.delete(rootpath,event_params)
end
def new_firebase(event_params,rootpath="Events")
query = init_firebase.push(rootpath,event_params)
end
def init_firebase # Inits firebase project with URL and secret
firebaseURL = "myfirebaseprojecturl"
firebaseSecret = "myfirebasesecret"
firebase = Firebase::Client.new(firebaseURL, firebaseSecret)
end
Event params consist of my event parameters as shown below
def event_params
params.require(:event).permit(:eventID, :eventName, :attachment, :eventNoOfPpl, :eventAdminEmail, {eventpics: []})
end
I encountered an issue. When I push with push() into firebase, there is a random key like -LSFOklvcdmfPOWrxgBo. In such case, the structure of the document would look like this:
But I cannot delete anything from -LSFOklvcdmfPOWrxgBo as I do not have the value. I used delete() from Oscar's firebase-ruby gem. I would appreciate any help with this issue.
I re-read the gem docs, and got some help from my friends and came up with two solutions
The body's response has response.body # => { 'name' => "-INOQPH-aV_psbk3ZXEX" } and thus, you're able to find out the name if you'd like
Change the index, and don't use .push, instead I made use of .set and did a random number for every event
Final solution
def load_firebase(root_path = "Events")
firebase_json = init_firebase.get(root_path)
if valid_json?(firebase_json.raw_body)
#json_object = JSON.parse(firebase_json.raw_body)
end
end
def update_firebase(event_params, root_path = "Events/")
init_firebase.update("#{root_path}#{event_params["eventID"]}", event_params)
end
def delete_firebase(event_params, root_path = "Events/")
init_firebase.delete("#{root_path}#{event_params["eventID"]}")
end
def save_firebase(event_params, root_path = "Events/")
init_firebase.set("#{root_path}#{event_params["eventID"]}", event_params)
end
I have a rails api, in which I'm using Active Model Serializers (version 0.10.6) to serializer json responses.
I have two arrays, both are of type "FeatureRequest" in an endpoint that returns a list of requests a user has made, and a second list of requests that a user is tagged in. Ideally, I'd like to serialize the response to look something like this:
{
"my_requests" : {
...each serialized request...
},
"tagged_requests" : {
...each serialized request, using the same serializer"
}
}
Is there some way to do this?
Here's my relevant controller method:
def index_for_user
user = User.includes(leagues: :feature_requests).find(params[:user_id])
# Find all requests that this user created
#users_created_feature_requests = FeatureRequest.requests_for_user(user.id)
#feature_requests_in_tagged_leagues = []
user.leagues.each do |league|
#feature_requests_in_tagged_leagues << league.feature_requests
end
...some serialized response here
end
In this code, the two lists are #users_created_feature_requests, and #feature_requests_in_tagged_leagues
Thanks!
Assuming you have a serializer like FeatureRequestSerializer, you can achieve that in the following way:
def index_for_user
user = ...
users_created_feature_requests = ...
feature_requests_in_tagged_leagues = user.leagues.map(&:feature_requests)
# serialized response
render json: {
my_requests: serialized_resource(users_created_feature_requests)
tagged_requests: serialized_resource(feature_requests_in_tagged_leagues)
}
end
private
def serialized_resource(collection, adapter = :attributes)
ActiveModelSerializers::SerializableResource.new(collection,
each_serializer: FeatureRequestSerializer,
adapter: adapter
).as_json
end
I'm trying to implement a counter filter in my json. When I access the url api/v1/contacts?Results=2 [sic], I would like to get only two results from my json.
For this, I created two methods in my controller: an index that takes the information from the database and turns render into json, and a method that returns the number of times the json is returned.
class Api::V1::ContactsController < ApplicationController
before_action :results, only: [:index]
def index
#contacts = Contact.all
render json: {results: #contacts[0..#number_results]}
end
def results
if params[:results]
results = params[:results]["results"]
#number_results = results.to_i
else
#number_results = 3
end
end
end
Regardless of the value entered for results =, the value of #number_results is set to 0, so whenever I type results = I, get only the first result of json. If I do not type results = in the url, it sets #number_results to 3, and shows four results on the screen.
Can anyone help?
First, in the url you propose, "Results" is capitalized. If that is how you intend to submit it, then you'll need to search for it that way on the back end. But since all your code uses lowercase "results", we'll go with that. You should modify your url in the same way: api/v1/contacts?results=2.
If that's what your url looks like then the number you pass in is accessible in params[:results]. If you pass no parameter, then params[:results] will return nil. Don't call #to_i on the param before you check for its existence, because nil.to_i is 0.
All that said, here's what you probably want:
class Api::V1::ContactsController < ApplicationController
def index
number_results = params[:results] || 3
#contacts = Contact.all.limit(number_results.to_i)
render json: {results: #contacts}
end
end
If the result of params[:results] is nil, then number_results is assigned 3.
Now we use #limit to return only the number of contacts that was requested. This allows us to do away with the #results method entirely. You can also get rid of the before_action callback.
Right now I call:
def child(parent_id, child_id, params = {})
if #api_token
self.class.get("/parents/#{parent_id}/children/#{child_id}", query: params,
:headers=>{"Authorization"=>"Token token=#{#api_token}"})
else
self.class.get("/parents/#{parent_id}/children/#{child_id}", query: params)
end
end
It returns the JSON response directly from the API as a hash. Is there an easy way for me to standardize the response so that it parses the JSON and generates a class?
Example:
Original Response
--------
{'child' : { 'name' : 'John Doe', 'age' : 23 } }
Desired Response
--------
res.name # John Doe
res.age # 23
res.class # APIClient::Child
It can be achieved via custom parser passed to request call (however I would strongly advise not to do it and leave it as it is now)
an example of parser you could pass is
class InstanceParser < HTTParty::Parser
def parse
#assuming you always have a json in format { 'one_key_mapping_to_model' => data }
body_as_json = JSON.parse(body) #string parsed to json
model_class_name = body_as_json.keys.first # == 'one_key_mapping'
model_class_data = body_as_json[model_class_name] # == data
class_instance = model_class_name.camelize.constantize.new(model_class_data) # will create new instance of OneKeyMapping
class_instance
end
end
and then in your api call pass self.class.get("/parents/#{parent_id}/children/#{child_id}", query: params, parser: InstanceParser)
Pass the hash to an initializer.
class APIClient::Child
attr_accessor :foo, :bar
def initialize(hash = {})
hash.each do |k,v|
public_send("#{k}=", v)
end
end
end
Then in your API client you would map between responses and objects:
def child(parent_id, child_id, params = {})
opts = { query: params }
opts.merge!(:headers=>{"Authorization"=>"Token token=#{#api_token}"}) if #api_token
begin
res = self.class.get("/parents/#{parent_id}/children/#{child_id}", opts)
case response.code
when 200
APIClient::Child.new(res[:child])
when 404
# not found - raise some sort of error that the API client consumer
# can catch maybe?
else
# probably just log it since there is little we can do here.
end
rescue HTTParty::Error
# probaly just log it. Could be a connection error or something else.
end
end
This is probably a lot less magical than what you have hoped for but what is the role of a API Client if not to map between HTTP requests and objects suitable for consumption. Most of the boooring boilerplate code here when it comes to passing tokens and handling errors can be farmed out parent classes or modules.
I wonder if it possible to work with HTTParty request results as an object.
Currently I use string keys to access to values of result: result["imageurl"] or result["address"]["street"]
If i were in JavaScript I could simply use: result.imageurl or result.address.street
Use the Mash class of the hashie gem.
tweet = Hashie::Mash.new(
HTTParty.get("http://api.twitter.com/1/statuses/public_timeline.json").first
)
tweet.user.screen_name
I wrote this helper class a few days ago:
class ObjectifiedHash
def initialize hash
#data = hash.inject({}) do |data, (key,value)|
value = ObjectifiedHash.new value if value.kind_of? Hash
data[key.to_s] = value
data
end
end
def method_missing key
if #data.key? key.to_s
#data[key.to_s]
else
nil
end
end
end
Usage example:
ojson = ObjectifiedHash.new(HTTParty.get('http://api.dribbble.com/players/simplebits'))
ojson.shots_counts # => 150