Is there a way to manually serialize a collection? - ruby-on-rails

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)

Related

How can I extend ActiveRecord model with new propery?

I'm developing a REST API and before send results (fetched from DB with ACtiveRecord) to client I need to extend it with some additional data. Here is how I suppose it to be:
def index
(categories = Category.all()).each() do |category|
category.image = "some image"
end
render json: categories
end
It seems like Ruby does not add object proporties on the fly, like JS would do, so this code ends up with error message
undefined method `image=' for #<Category:0x007f89f0513d40>
I read a lot and found solution to add attr_accessor :image in my Category model. Now it does not throw error but also image property does not appeared in JSON output.
Instead of playing with the object it self, you might want to take a few mins and learn how jbuilder works, it's very simple and it would make your life a lot easier, especially when the json format includes a little extra/different data from your original database object,
Here's a simple format of what you might do
# app/views/categories/index.json.jbuilder
json.array! #categories do |category|
json.prop1 category.prop1
json.prop2 category.prop2
json.image whatever_image_function_here
end
Other solution would be overriding the as_json method of the class
Use as_json method with attr_accessor :image in category model.
def index
categories = Category.all.each do |category|
category.image = 'some image'
end
render json: categories.as_json(methods: :image)
end
In rails < 4.2 it has different name - to_json

How to define a Sparse Fieldset in a Rails request

Is there a simple way to implement requesting a Sparse Fieldset in a rails JSON request?
/some/endpoint.json?fields=id,name,favourite_colour
One solution I've found is to do it within a serialiser.
module V2
class BaseSerializer < ActiveModel::Serializer
self.root = true
def include?(field)
if #options.key?(:fields)
return #options[:fields].include? field.to_s
end
super field
end
end
end
In your controller you can do something like
render json: #sth, only: params[:fields].split(',').map(&:to_sym)
you should also wrap it in some strong params, to disallow non existing attributes
(but I doubt for it to be best possible solution)

JSON data for rspec tests

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}}]"

How do I modify Rails ActiveRecord query results before returning?

I have a table with data that needs to be updated at run-time by additional data from an external service. What I'd like to do is something like this:
MyModel.some_custom_scope.some_other_scope.enhance_with_external_data.each do |object|
puts object.some_attribute_from_external_data_source
end
Even if I can't use this exact syntax, I would like the end result to respect any scopes I may use. I've tried this:
def self.enhance_with_external_data
external_data = get_external_data
Enumerator.new do |yielder|
# mimick some stuff I saw in ActiveRecord and don't quite understand:
relation.to_a.each do |obj|
update_obj_with_external_data(obj)
yielder.yield(obj)
end
end
end
This mostly works, except it doesn't respect any previous scopes that were applied, so if I do this:
MyModel.some_custom_scope.some_other_scope.enhance_with_external_data
It gives back ALL MyModels, not just the ones scoped by some_custom_scope and some_other_scope.
Hopefully what I'm trying to do makes sense. Anyone know how to do it, or am I trying to put a square peg in a round hole?
I figured out a way to do this. Kind of ugly, but seems to work:
def self.merge_with_extra_info
the_scope = scoped
class << the_scope
alias :base_to_a :to_a
def to_a
MyModel.enhance(base_to_a)
end
end
the_scope
end
def self.enhance(items)
items.each do |item|
item = add_extra_item_info(item)
end
items
end
What this does is add a class method to my model - which for reasons unknown to me seems to also make it available to ActiveRecord::Relation instances. It overrides, just for the current scope object, the to_a method that gets called to get the records. That lets me add extra info to each record before returning. So now I get all the chainability and everything like:
MyModel.where(:some_attribute => some_value).merge_with_extra_info.limit(10).all
I'd have liked to be able to get at it as it enumerates versus after it's put into an array like here, but couldn't figure out how to get that deep into AR/Arel.
I achieved something similar to this by extending the relation:
class EnhancedModel < DelegateClass(Model)
def initialize(model, extra_data)
super(model)
#extra_data = extra_data
end
def use_extra_data
#extra_data.whatever
end
end
module EnhanceResults
def to_a
extra_data = get_data_from_external_source(...)
super.to_a.map do |model_obj|
EnhancedModel.new(model_obj, extra_data)
end
end
end
models = Model.where('condition')
models.extend(EnhanceResults)
models.each do |enhanced_model|
enhanced_model.use_extra_data
end

Ruby/Rails: Is it possible to execute a default method when calling an instance (#instance == #instance.all IF "all" is the default method)?

I understand my question is a bit vague but I don't know how else to describe it. I've asked in numerous places and no one seems to understand why I want to do this. But please bear with me, and I'll explain why I want something like this.
I'm using Liquid Templates to allow users to make some dynamic pages on my site. And for those that don't know, Liquid uses a class of theirs called LiquidDrop to expose certain items to the user. Any method in the drop can be called by the Liquid template.
class PageDrop < Liquid::Drop
def initialize(page)
#page = page
end
def name
#page.name
end
def children
PagesDrop.new(#page.children)
end
end
class PagesDrop < Liquid::Drop
def initialize(pages)
#pages = pages
end
def group_by
GroupByDrop.new(#pages)
end
def all
#pages.all
end
def size
#pages.size
end
end
For example, I want to be able to do this:
#page_drop = PageDrop.new(#page)
#page_drop.children # to get an array of children
instead of
#page_drop.children.all
Why do I have a pages drop?
Because I want to be able to cleanly split up the methods I can do to an array of pages, and methods I can do to a single page. This allows me to group pages like so:
#page_drop.children.group_by.some_method_here_that_the_group_drop_contains
To make it simpler for my users, I don't want them to have to think about adding "all" or not to a drop instance to get the "default" object/s that it contains. To reiterate:
#pages_drop = PagesDrop.new(Page.all)
#pages_drop == #pages_drop.pages #I want this to be true, as well as
#pages_drop == #pages_drop.all
Where did I get this idea?
In Rails, a scope (association object) (#person.friends) seems to return the array when you do certain things to it: #person.friends.each, for person in #person.friends
This isn't really possible. When you write #instance you aren't really calling an instance as you describe, you're getting a reference to the object that #instance refers to.
The reason it seems to work with the collections for Rails' associations is that the the association objects are instances of Array that have had some of their methods overridden.
I would consider removing PagesDrop and using the group_by(&:method) syntax if you want a concise way to express groupings. If you do want to keep it then you can get some way towards what you want by implementing each and [] on PagesDrop and having them delegate to #pages. That will let you use #page_drop.children in for loops, for instance.
It looks like you want to implement has_many outside of rails. Will the following work?
class PageDrop < Liquid::Drop
attr_accessor :children
def initialize(page)
#page = page
#children = []
end
def name
#page.name
end
end
This allows you to do the following:
#page_drop = PageDrop.new(#page)
#page_drop.children.size # => 0
#page_drop.children # => []
This also gives you all the standard array functions (group_by, size, each, etc). If you want to add your own methods, create a class that inherits from Array and add your methods there.
class PageArray < Array
def my_method
self.each{|a| puts a}
end
end
class PageDrop < Liquid::Drop
attr_accessor :children
def initialize(page)
#page = page
#children = PageArray.new
end
[...]
end
#page_drop = PageDrop.new(#page)
#page_drop.children.size # => 0
#page_drop.children # => []
#page_drop.children.my_method # Prints all the children
Then any functions you don't define in PageArray fall through to the Ruby Array methods.

Resources