Rails 4: prevent 'to_json' from adding a root node? - ruby-on-rails

This feels like it should be simple, but after Googling for an hour I can't figure this out.
I'm POSTing an Amazon S3 'policy document' as JSON to my server.
I need to encode the JSON as is, but Rails is adding stuff to 'params' which is cluttering the JSON I need to encode.
Here is what I have:
class Api::Amazons3Controller < Api::BaseController
def sign_policy
policy_document = params.except(:action, :controller)
encoded_policy_document = Base64.encode64(policy_document.to_json).gsub(/\n|\r/, '')
signature = Base64.encode64(
OpenSSL::HMAC.digest(
OpenSSL::Digest::Digest.new('sha1'),
ENV['AWS_SECRET_ACCESS_KEY'],
policy_document)
).gsub(/\n/, '')
response = { policy: policy_document, signature: signature }
render json: response
end
end
I'm trying to 'clean up' the params with params.except(:action, :controller), but policy_document.to_json adds a root note called 'amazons3' (the controller name) around the JSON, which I don't want. I just need to encode the pure json from the request.
Any help would be highly appreciated!

class Api::Amazons3Controller < Api::BaseController
self.include_root_in_json = false
end

Try this then
config/initializers/wrap_parameters.rb
if defined?(ActiveRecord)
ActiveRecord::Base.include_root_in_json = false
end

I was able to disable the Parameter Wrapping for this Controller by adding:
class Api::Amazons3Controller < Api::BaseController
wrap_parameters format: []

Related

HttParty: set default header for all requests

In some HTTP Clients theres one way to do something like:
HttpClient.default_headers = { "my-header": "a-value" }
And then every request done with that client will include those headers.
Is there any way to do this with jnunemaker/httparty?
There is!
class MyIntegrationClass
include HTTParty
base_uri 'https://somedomain.com/api'
headers 'my-header' => 'a-value'
def some_method
response = get('/endpoint')
JSON.parse(response.to_s, symbolize_names: true)
end
end
The get request will have the my-header header set.
I found one way to make this work running without having to use class methods:
HTTParty::Basement.headers 'my-header' => 'a value'
You could use it with hooks from rails like this:
require "httparty"
module DefaultHeadersForHttparty
extend ActiveSupport::Concern
included do
before_action :apply_http_party_headers
private
def apply_http_party_headers
HTTParty::Basement.headers 'my-header' => 'a value'
end
end
end

Access to Rails request inside ActiveSupport::LogSubscriber subclass

I am trying to make a bit of a custom Rails logger which ultimately will log to a database. However, I don't have access to things like the request object, which I very much would like to have.
I'm currently trying to use the LogSubscriber (notification) interface to do the bulk of this; perhaps this is not the right approach. I do know I could abuse Thread.current[] but I was hoping to avoid doing that.
Here's the code I have which is as basic as I can get it for an example. This is loaded in an initializer.
module RequestLogging
class LogSubscriber < ActiveSupport::LogSubscriber
def process_action(event)
pp request # <--- does not work
pp event
end
end
RequestLogging::LogSubscriber.attach_to :action_controller
Probably you need to override process_action in ActionController::Instrumentation and then request object will be accessible like event.payload[:request]. I think you can put code somewhere in config/initializers, code example:
ActionController::Instrumentation.class_eval do
def process_action(*args)
raw_payload = {
controller: self.class.name,
action: self.action_name,
params: request.filtered_parameters,
format: request.format.try(:ref),
method: request.method,
path: (request.fullpath rescue "unknown"),
request: request,
session: session
}
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
result = super
payload[:status] = response.status
append_info_to_payload(payload)
result
end
end
end
you can get the even.payload then pass it your own CustomLogger(formatted_log(even.payload) and then there you can define a module and save it.
You may want to customise your formatted_log function to beautify the payload accordingly.
def process_action(event)
CustomLogger.application(formattedLog(event.payload))
end
def formattedLog(payload)
# some restructuring of data.
end

Are there any libraries to manage json content in a database field of a rails app?

I have a rails 2 app that manages a json field within a table. It needs to:
Read the json
Convert the json to model attributes and form fields
Save edits to the form fields into the json field
Add validation for some of the json values
Be applicable to multiple models
Currently, I have a library file that manually adds methods to extract and save into the json, like so:
module Configuration
def configuration_json
configuration? ? JSON.parse(self.configuration) : {}
end
def some_json_value
if !self.configuration.nil? && configuration_json["parentKey"]
configuration_json["parentKey"]["someJsonValue"]
end
end
def some_json_value=(val)
new_config = configuration_json.deep_merge({
"FeatureConfiguration" => { "someJsonValue" => val }
})
self.configuration = new_config.to_json
end
def some_json_value_validation
# ...
end
end
And in the models I include this
class SomeModel < ActiveRecord::Base
include Configuration
validate :some_json_value_validation
# ...
end
Is there a better/DRY-er way? Currently it's really clunky when the json structure changes, as there are quite a few steps to modify in the rails app.
I can't change the use of the json field, as it's for configuration of another application, which is the main application that the rails app supports.
The best way would be to make a Configuration model, and simply make a to_json method that builds the correct json object.
If you really want to parse the json and convert it back on every operation, you could make a helper to create the methods for you, like json_attr_accessor
Example:
module Configuration
def configuration_json
configuration.present? ? JSON.parse(configuration) : {}
end
module ModelExtensions
def json_attr_accessor(*symbols)
symbols.each do |sym|
key = sym.to_s.camelize(:lower)
define_method(sym) do
['FC', key].reduce(configuration_json) do |json, key|
json and json[key]
end
end
define_method("#{sym}=") do |val|
hash = configuration_json.deep_merge 'FC' => { key => val }
self.configuration = hash.to_json
end
end
end
end
def self.included(base)
base.extend ModelExtensions
end
end
And in the model:
class SomeModel < ActiveRecord::Base
include Configuration
json_attr_accessor :some_json_value
end
Here's a link to help for custom validators:
http://www.perfectline.ee/blog/building-ruby-on-rails-3-custom-validators

Rails - Serialize Model to JSON with camelize

I need to serialize a model to json and have all of the keys be camelized. I see that there's an option in to_xml to allow camel case. I can't seem to coerce the json serialization into giving me back a camelized hash. Is this something that's possible in rails?
I had a similar issue. After a bit of research I wrapped the as_json ActiveModel method with a helper that would camelize Hash keys. Then I would include the module in the relevant model(s):
# lib/camel_json.rb
module CamelJson
def as_json(options)
camelize_keys(super(options))
end
private
def camelize_keys(hash)
values = hash.map do |key, value|
[key.camelize(:lower), value]
end
Hash[values]
end
end
# app/models/post.rb
require 'camel_json'
class Post < ActiveRecord::Base
include CamelJson
end
This worked really well for our situation, which was relatively simplistic. However if you're using JBuilder, apparently there's a configuration to set camel case as the default: https://stackoverflow.com/a/23803997/251500
If you are using rails, skip the added dependency and use Hash#deep_transform_keys. It has the added benefit of also camelizing nested keys (handy if you are doing something like user.as_json(includes: :my_associated_model)):
h = {"first_name" => "Rob", "mailing_address" => {"zip_code" => "10004"}}
h.deep_transform_keys { |k| k.camelize(:lower) }
=> {"firstName"=>"Rob", "mailingAddress"=>{"zipCode"=>"10004"}}
Source: https://github.com/rails/rails/blob/4-2-stable/activesupport/lib/active_support/core_ext/hash/keys.rb#L88
For my case,I was required to customize some key names.
Usage
puts self.camelize_array(array:Post.all.to_a,conditions:{id: "_id",post_type: "type"})
Implementation
def self.camelize_array(array:,conditions: {})
final = JSON.parse array.to_json
final.each do |a|
a.transform_keys! do |key|
if conditions.keys.include? key.to_sym
key = conditions[key.to_sym]
else
key.camelize(:lower)
end
end
end
final.to_json
end
Working with RABL Renderer directly, you can pass an inline template, instead of fetching it from a file:
Rabl::Renderer.new("\nattributes :name, :description", object).render
The \n character is necessary at the beginning of the string.
It seems weird to me to use camelized attribute names in Rails, let alone json. I would stick to the conventions and use underscored variable names.
However, have a look at this gem: RABL. It should be able to help you out.

Add api key to every request in ActiveResource

I have 2 RESTful Rails apps I'm trying to make talk to each other. Both are written in Rails 3 (beta3 at the moment). The requests to the service will require the use an api key which is just a param that needs to be on every request. I can't seem to find any information on how to do this.
You define the url the resource connects to via the site= method. There should be an equivalent query_params= method or similar.
There is one good blog post I found related to this and it's from October 2008, so not exactly useful for Rails 3.
Update: I had a thought. Would a small Rack middleware or Metal be the answer to this? It could just pass through the request, tacking it's api_key on.
Use model#prefix_options which is a hash for passing params into query string (or even as substitions for parts of the Model.prefix, e.g. "/myresource/:param/" will be replaced by the value of prefix_options[:param] . Any hash keys not found in the prefix will be added to the query string, which is what we want in your case).
class Model < ActiveResource::Base
class << self
attr_accessor :api_key
end
def save
prefix_options[:api_key] = self.class.api_key
super
end
end
Model.site = 'http://yoursite/'
Model.api_key = 'xyz123'
m = Model.new(:field_1 => 'value 1')
# hits http://yoursite:80/models.xml?api_key=xyz123
m.save
I recently was faced with a similar issue, if you are on Rails3, it supports using custom header which makes life much easier for these situations.
On the side you are making the request from, add
headers['app_key'] = 'Your_App_Key'
to the class you are inheriting from ActiveResource::Base
On you are server, for Authentication, simply receive it as
request.headers['HTTP_APP_KEY']
For Example:
class Magic < ActiveResource::Base
headers['app_key'] = 'Your_App_Key'
end
now Magic.get, Magic.find, Magic.post will all send the app_key
I have much nicer solution ! I try with Rack in middleware but i no find any solution in this way....
I propose you this module for override methods of ActiveReouse::Base
Add this lib in /lib/active_resource/extend/ directory don't forget uncomment
"config.autoload_paths += %W(#{config.root}/lib)" in config/application.rb
module ActiveResource #:nodoc:
module Extend
module AuthWithApi
module ClassMethods
def element_path_with_auth(*args)
element_path_without_auth(*args).concat("?auth_token=#{self.api_key}")
end
def new_element_path_with_auth(*args)
new_element_path_without_auth(*args).concat("?auth_token=#{self.api_key}")
end
def collection_path_with_auth(*args)
collection_path_without_auth(*args).concat("?auth_token=#{self.api_key}")
end
end
def self.included(base)
base.class_eval do
extend ClassMethods
class << self
alias_method_chain :element_path, :auth
alias_method_chain :new_element_path, :auth
alias_method_chain :collection_path, :auth
attr_accessor :api_key
end
end
end
end
end
end
in model
class Post < ActiveResource::Base
include ActiveResource::Extend::AuthWithApi
self.site = "http://application.localhost.com:3000/"
self.format = :json
self.api_key = 'jCxKPj8wJJdOnQJB8ERy'
schema do
string :title
string :content
end
end
Based on Joel Azemar's answer, but I had to make some changes..
First of all, in the active resource gem I used (2.3.8), there is no 'new_element_path', so aliasing that obviously failed..
Second, I updated the way the token is added to the query, because as was, it would break as soon as you add more params yourself. E.g. request for http://example.com/api/v1/clients.xml?vat=0123456789?token=xEIx6fBsxy6sKLJMPVM4 (notice ?token= i.o. &token=)
Here's my updated snippet auth_with_api.rb;
module ActiveResource #:nodoc:
module Extend
module AuthWithApi
module ClassMethods
def element_path_with_auth(id, prefix_options = {}, query_options = nil)
query_options.merge!({:token => self.api_key})
element_path_without_auth(id, prefix_options, query_options)
end
def collection_path_with_auth(prefix_options = {}, query_options = nil)
query_options.merge!({:token => self.api_key})
collection_path_without_auth(prefix_options, query_options)
end
end
def self.included(base)
base.class_eval do
extend ClassMethods
class << self
alias_method_chain :element_path, :auth
# alias_method_chain :new_element_path, :auth
alias_method_chain :collection_path, :auth
attr_accessor :api_key
end
end
end
end
end
end
An Active Resource currently has no good way of passing an api key to the remote service. Passing api_key as a parameter will add it to the objects attributes on the remote service, I assume that this is not the behaviour you'd except. It certainly wasn't the behaviour I needed
I'd recommend that you have a base class inheriting from ActiveResource::Base and override the self.collection_path and self.element_path class methods to always inject the API KEY something like:
class Base < ActiveResource::Base
def self.collection_path(prefix_options = {}, query_options = {})
super(prefix_options, query_options.merge(api_key: THE_API_KEY))
end
def self.element_path(id, prefix_options = {}, query_options = {})
super(id, prefix_options, query_options.merge(api_key: THE_API_KEY))
end
end
class User < Base; end
User.all # GET /users/?api_key=THE_API_KEY
This will always inject your API_KEY in your ActiveResource method calls.
An Active Resource Object behaves much like a (simplified) Active Record object.
If you wish to pass through a new param, then you can set it on the AR object by adding it as an attribute. eg:
jane = Person.create(:first => 'Jane', :last => 'Doe', :api_key => THE_API_KEY)
it should pass the api_key as a parameter, along with all the others.

Resources