JSON data for rspec tests - ruby-on-rails

I'm creating an API that accepts JSON data and I want to provide testing data for it.
Is there anything similar to factories for JSON data? I would like to have the same data available in an object and in JSON, so that I can check if import works as I intended.
JSON has strictly defined structure so I can't call FactoryGirl(:record).to_json.

In cases like this, I'll create fixture files for the JSON I want to import. Something like this can work:
json = JSON.parse(File.read("fixtures/valid_customer.json"))
customer = ImportsData.import(json)
customer.name.should eq(json["customer"]["name"])
I haven't seen something where you could use FactoryGirl to set attributes, then get it into JSON and import it. You'd likely need to create a mapper that will take your Customer object and render it in JSON, then import it.

Following Jesse's advice, in Rails 5 now you could use file_fixture (docs)
I just use a little helper for reading my json fixtures:
def json_data(filename:)
file_content = file_fixture("#{filename}.json").read
JSON.parse(file_content, symbolize_names: true)
end

Actually you can do the following with factorygirl.
factory :json_data, class: OpenStruct do
//fields
end
FactoryGirl.create(:json_data).marshal_dump.to_json

Sometime ago we implemented FactoryJSON gem that addresses this issue. It worked quite well for us so far. Readme file covers possible use cases.

Here's something that works well for me. I want to create deeply nested structures without specifying individual factories for each nesting. My usecase is stubbing external apis with webmock. Fixtures don't cut it for me since I need to stub in a variety of different data.
Define the following base factory and support code:
FactoryBot.define do
factory :json, class: OpenStruct do
skip_create # Don't try to persist the object
end
end
class JsonStrategy < FactoryBot::Strategy::Create
def result(evaluation)
super.json.to_json
end
def to_sym
:json
end
end
# Makes the function FactoryBot.json available,
# which automatically returns the hash as a json string.
FactoryBot.register_strategy(:json, JsonStrategy)
I can then define the actual factory like this:
FactoryBot.define do
factory :json_response, parent: :json do
# You can define any attributes you want here because it uses OpenStruct
ids { [] }
# This attribute will be plucked by the custom strategy. All others like
# ids above will be ignored. You can still use them here though.
json do
ids.map do |id|
{
score: 90,
data: {
id: id,
},
}
end
end
end
end
Finally you can use it like this:
FactoryBot.json(:json_response, ids: [1,2])
=> "[{\"score\":90,\"data\":{\"id\":1}},{\"score\":90,\"data\":{\"id\":2}}]"

Related

Dynamically include associations for objects in Rails

I am currently developing a small Rails 5 application where I need to pass on an ActiveRecord object to an external service based on certain events. In my model I have defined the following:
# /models/user.rb
after_create :notify_external_service_of_user_creation
def notify_external_service_of_user_creation
EventHandler.new(
event_kind: :create_user,
content: self
)
end
The EventHandler is then converting this object to JSON and is sending it through an HTTP request to the external service. By calling .to_json on the object this renders a JSON output which would look something like this:
{
"id":1234,
"email":"test#testemail.dk",
"first_name":"Thomas",
"last_name":"Anderson",
"association_id":12,
"another_association_id":356
}
Now, I need a way to include all first level associations directly into this, instead of just showing the foreign_key. So the construct I am looking for would be something like this:
{
"id":1234,
"email":"test#testemail.dk",
"first_name":"Thomas",
"last_name":"Anderson",
"association_id":{
"attr1":"some_data",
"attr2":"another_value"
},
"another_association_id":{
"attr1":"some_data",
"attr2":"another_value"
},
}
My first idea was to reflect upon the Model like so: object.class.name.constantize.reflect_on_all_associations.map(&:name), where object is an instance of a user in this case, and use this list to loop over the associations and include them in the output. This seems rather tedious though, so I was wondering if there would be a better way of achieving this using Ruby 2.4 and Rails 5.
If you don't want to use an external serializer, you can override as_json for each model. as_json gets called by to_json.
module JsonWithAssociations
def as_json
json_hash = super
self.class.reflect_on_all_associations.map(&:name).each do |assoc_name|
assoc_hash = if send(assoc_name).respond_to?(:first)
send(assoc_name).try(:map, &:as_json) || []
else
send(assoc_name).as_json
end
json_hash.merge!(assoc_name.to_s => assoc_hash)
end
json_hash
end
end
You'll need to prepend this particular module so that it overrides the default as_json method.
User.prepend(JsonWithAssociations)
or
class User
prepend JsonWithAssociations
end

Is there a way to manually serialize a collection?

I'd like to manually serialize a collection for testing purposes. I tried this:
JSONAPI::ResourceSerializer.new(PostResource).
serialize_to_hash(PostResource.new(Post.all))
This doesn't work. It appears you can only serialize a single resource here. How would one return a serialized collection of all posts?
I was trying the same thing - but it is not possible. JSONAPI::Resource does not have any helper to easily convert Relation object or array of records into JSONAPI::Resource instances, so you can't pass the collection like this.
serialize_to_hash expects array of JSONAPI::Resource, so you would have to do something horrible like this:
result = []
Post.all.each do |post|
result << PostResource.new(post)
end
JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(result)
JSONAPI::Resources expects that the JSONAPI itself should be sufficient, so no implementation of methods like index should be needed - gem will handle it itself without the need for manual serialization. But it is true, that I can imagine some scenarios, where I would want to be able to mannualy serialize collection of records, so there should be some easy way to do this...unfortunately, It looks like there is no easy way for now.
UPDATE:
I had some further questions myself so I have asked the creators of the gem here: https://github.com/cerebris/jsonapi-resources/issues/460
Another way to manually serialize data, based on JSONAPI Resources features, is by using the jsonapi-utils gem:
With the jsonapi_serialize method:
class API::V1::UsersController < API::V1::BaseController
def index
users = User.all
render json: jsonapi_serialize(users)
end
end
Or the high-level jsonapi_render method:
class API::V1::UsersController < API::V1::BaseController
def index
jsonapi_render json: User.all
end
end
Hope it's useful for you :-)
If you are using ActiveModelSerializers (https://github.com/rails-api/active_model_serializers)
Try to do helper like this:
def serialize_resource(resource)
JSON.parse(ActiveModelSerializers::SerializableResource.new(resource).to_json)
end
You can use this pattern both for one resource or many resources.
In your case it will be:
JSON.parse(ActiveModelSerializers::SerializableResource.new(Post.all).to_json)

Rails return JSON serialized attribute with_indifferent_access

I previously had:
serialize :params, JSON
But this would return the JSON and convert hash key symbols to strings. I want to reference the hash using symbols, as is most common when working with hashes. I feed it symbols, Rails returns strings. To avoid this, I created my own getter/setter. The setter is simple enough (JSON encode), the getter is:
def params
read_attribute(:params) || JSON.parse(read_attribute(:params).to_json).with_indifferent_access
end
I couldn't reference params directly because that would cause a loop, so I'm using read_attribute, and now my hash keys can be referenced with symbols or strings. However, this does not update the hash:
model.params.merge!(test: 'test')
puts model.params # => returns default params without merge
Which makes me think the hash is being referenced by copy.
My question is twofold. Can I extend active record JSON serialization to return indifferent access hash (or not convert symbols to strings), and still have hash work as above with merge? If not, what can I do to improve my getter so that model.params.merge! works?
I was hoping for something along the lines of (which works):
def params_merge!(hash)
write_attribute(:params, read_attribute(:params).merge(hash))
end
# usage: model.params_merge!(test: 'test')
Better yet, just get Rails to return a hash with indifferent access or not convert my symbols into strings! Appreciate any help.
use the built-in serialize method :
class Whatever < ActiveRecord::Base
serialize :params, HashWithIndifferentAccess
end
see ActiveRecord::Base docs on serialization for more info.
Posting comment as answer, per #fguillen's request... Caveat: I am not typically a Rubyist… so this may not be idiomatic or efficient. Functionally, it got me what I wanted. Seems to work in Rails 3.2 and 4.0...
In application_helper.rb:
module ApplicationHelper
class JSONWithIndifferentAccess
def self.load(str)
obj = HashWithIndifferentAccess.new(JSON.load(str))
#...or simply: obj = JSON.load(str, nil, symbolize_names:true)
obj.freeze #i also want it set all or nothing, not piecemeal; ymmv
obj
end
def self.dump(obj)
JSON.dump(obj)
end
end
end
In my model, I have a field called rule_spec, serialized into a text field:
serialize :rule_spec, ApplicationHelper::JSONWithIndifferentAccess
Ultimately, I realized I just wanted symbols, not indifferent access, but by tweaking the load method you can get either behavior.
Using HashWithIndifferentAccess is great, but it still acts like a Hash, and it can only serialize as YAML in the database.
My preference, using Postgres 9.3 and higher, is to use the json column type in Postgres. This means that when the table is read, ActiveRecord will get a Hash directly from Postgres.
create_table "gadgets" do |t|
t.json "info"
end
ActiveRecord serialize requires that you provide it a single class that is both responsible for reading/writing the data and serializing/deserializing it.
So you can create an object that does the job by inheriting from HashWithIndifferentAccess, or my preference, Hashie::Mash. Then you implement the serialization as the dump and load class methods.
class HashieMashStoredAsJson < Hashie::Mash
def self.dump(obj)
ActiveSupport::JSON.encode(obj.to_h)
end
def self.load(raw_hash)
new(raw_hash || {})
end
end
In your model, you can specify this class for serialization.
class Gadget < ActiveRecord::Base
serialize :info, HashieMashStoredAsJson
# This allows the field to be set as a Hash or anything compatible with it.
def info=(new_value)
self[:info] = HashieMashStoredAsJson.new new_value
end
end
If you don't use the json column type in Postgres, the implementation changes slightly
Full code and documentation here: using a JSON column type and using a string column type.
I ended up using a variation on bimsapi's solution that you can use not only with simple un-nested JSON but any JSON.
Once this is loaded...
module JsonHelper
class JsonWithIndifferentAccess
def self.load(str)
self.indifferent_access JSON.load(str)
end
def self.dump(obj)
JSON.dump(obj)
end
private
def self.indifferent_access(obj)
if obj.is_a? Array
obj.map!{|o| self.indifferent_access(o)}
elsif obj.is_a? Hash
obj.with_indifferent_access
else
obj
end
end
end
end
then instead of calling
JSON.load(http_response)
you just call
JsonHelper::JsonWithIndifferentAccess.load(http_response)
Does the same thing but all the nested hashes are indifferent access.
Should serve you well but think a little before making it your default approach for all parsing as massive JSON payloads will add significant ruby operations on top of the native JSON parser which is optimised in C and more fully designed for performance.

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.

ActiveModel based class does not create the same results as an ActiveRecord equivilent

I am developing a Rails 3 app in a largely tabless capacity. I am using savon_model and ActiveModel to generate similar behaviour to ActiveRecord equivalents. Below is my code:
class TestClass
include Savon::Model
include ActiveModel::Validations
# Configuration
endpoint "http://localhost:8080/app/TestService"
namespace "http://wsns.test.com/"
actions :getObjectById, :getAllObjects
attr_accessor :id, :name
def initialize(hash)
#id = hash[:id]
#name = hash[:name]
end
client do
http.headers["Pragma"] = "no-cache"
end
def self.all
h = getAllObjects(nil).to_array
return convert_array_hash_to_obj(h, :get_all_objects_response)
end
def self.find(id)
h = getObjectById(:arg0 => id).to_hash
return convert_hash_to_obj(h, :get_object_by_id_response)
end
private
def self.convert_array_hash_to_obj(arrayhash, returnlabel)
results = Array.new
arrayhash.each do |hash|
results << convert_hash_to_obj(hash, returnlabel)
end
return results
end
def self.convert_hash_to_obj(hash, returnlabel)
return TestClass.new(hash[returnlabel][:return])
end
end
OK, so everything works as expected; values are pulled from the web service and onto the page. Unfortunately, when I look at the html produced at the client side there are some issues. The Show links are along the following lines:
/testclasses/%23%3CTestClass:0xa814cb4%3E
instead of...
/testclasses/1
So, I did a print of the object (hash?) to the console to compare the outputs.
[#<System:0xa814cb4 #id="1", #name="CIS">]
instead of what I believe it should be...
[#<System id="1", name="CIS">]
I have three questions:
1: What is the hex suffix on my class name when it is printed out
2: How can I modify my class to match the desired output when printed to the console?
3: Why are the frontend links (Show, Edit, Delete) broken and is there an easy fix?
Thanks so much for your time and apologies for rubbish code / stupid questions. This is my first Ruby or Rails app!
Gareth
The hex suffix is the object id of your instance of System
You can manipulate the output on the console by implementing an inspect instance method
The Rails url helpers use the to_param instance method to build these links. You should implement this if you are going to use your class as an ActiveRecord substitute.
Generally speaking, if you want to use all the Rails goodies with an own implementation of a model class, you should use ActiveModel:Lint::Test to verify which parts of the ActiveModel APIs are working as expected.
More information can be found here: http://api.rubyonrails.org/classes/ActiveModel/Lint/Tests.html

Resources