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)
Related
I have rails 5 based api app, using fast_jsonapi
and after a while I observe that all most all my actions are having one common pattern
def action_name
#some_object.perform_action_name # this returns #some_object
render json: ControllerNameSerializer.new(#some_object).to_h
end
I do not wish to write the last render line here and it should work, for that I want that the returned value by the action should be processed by any hidden responder like thing, Serializer klass can be made out looking at controller name.
Perhaps this could be achieved by adding a small middleware. However at first, I find it not a good idea/practise to go for a middleware. In middleware, we do get rendered response, we need a hook prior to that.
I would imagine like
class SomeController ...
respond_with_returned_value
def action_name
#some_object.perform_action_name # this returns #some_object
end
Any suggestions?
Note, do not worry about error/failure cases, #some_object.errors could hold them and I have a mechanism to handle that separately.
Sketched out...
class ApplicationController < ...
def respond_with_returned_value
include MyWrapperModule
end
...
end
module MyWrapperModule
def self.included(base)
base.public_instance_methods.each do |method_name|
original_method_name = "original_#{method_name}".to_sym
rename method_name -> original_method_name
define_method(method_name) { render json: send(original_method_name) }
end
end
end
Seems like there really should be some blessed way to do this - or like someone must have already done it.
If I have a controller
class MyController < ApplicationController
vals = [...]
def new
...
end
def create
if save
...
else
render 'new'
end
end
how can I make the "vals" variable accessible to both methods? In my "new" view I want to use the "vals" variable for a drop-down menu, but rails is giving me errors. Of course, I could just copy the variable twice, but this solution is inelegant.
As Sebastion mentions a before_ hook / callback is one way to go about it, however as you mentioned it is for a dropdown menu, I am guessing it is a non-changing list, if so I would suggest perhaps using a Constant to define the values, perhaps in the model they are specific to, or if it is to be used in many places a PORO would do nicely to keep things DRY. This will then also allow you to easily access it anywhere, for example in models for a validation check, or to set the options of the dropdown menu in the view, or in the controller if you so wish:
class ExampleModel
DROPDOWN_VALUES = [...].freeze
validates :some_attr, inclusion: { in: DROPDOWN_VALUES }
end
class SomeController < ApplicationController
def new
# can call ExampleModel::DROPDOWN_VALUES here
end
def create
# also here, anywhere actually
end
end
You could use a before_* callback, e.g a before_action, this way you sets your vals variable as an instance one and make it to be available for your both new and create methods, something like:
class SomeController < ApplicationController
before_action :set_vals, only: [:new, :create]
def new
...
# #vals is available here
end
def create
if save
...
# and here
else
render 'new'
end
end
private
def set_vals
#vals = [...]
end
end
A different way from the ones before (although probably just having the instance method is preferred as in Sebastian's solution) is, take advantage of the fact that functions and local variables are called in the same way in ruby and just write:
def vals
#vals ||= [...]
end
and you should be able to access it on the controllers (not the views). If you want it on your views as well you can call at the beginning of the controller
helper_method :vals
If you want to be able to modify vals using vals="some value"
def vals= vals_value
#vals = vals_value
end
Take into account that probably using the intance variable as in Sebastian's solution is preferred, but if you, for whatever reason, are settled on being able to call "vals" instead of "#vals" on the view (for example if you are using send or try), then this should be able to do it for you.
Define in corresponding model
Eg :
class User < ActiveRecord::Base
TYPES = %w{ type1 type2 type3 }
end
and use in ur form like
User::TYPES
=> ["type1", "type2", "type3"]
You can reuse this anywhere in the application.
I use active_model_serializers (0.9.2). I've been studying documention, stack and source code and still cant find some way to pass some parameter to serializer. The only one workaround is using default scope
def default_serializer_options
{
scope: some_param
}
end
#options, options orserialization_options seems to be not working for me.
This is the link which will help you with it link
First create a serializer in the serializer folder.
class AttachmentSerializer < ActiveModel::Serializer
attributes :id, :attachment_url
def attachment_url
object.attachment_url
end
end
Then in your controller you can do something like this
params.require(:model-name).permit( :attachment)
I'm coming from the .NET world and I'm trying to figure out what the 'Rails Way' to pass an object across tiers in a multi-tier application.
I'm writing a multi carrier pricing API. Basically in my price controller I have access to the following parameters params[:carrier], params[:address_from], params[:address_to], params[:container_type], etc. I have a validation library, a compliance library and a price-finder library that each deal with a subset of the params.
In .NET the params would be encapuslated in data transfer objects (DTOs) or contracts. Before calling any of the libraries, they would be converted to domain objects (DOs) and each library would work on the DOs, thus avoiding a tight coupling on the DTOs. Ruby programming recommands the use of 'duck typing', so my libraries could work directly on params (even though you would access symbols and not objects/properties). Or should I marshall my params into a PriceRequest object and have my libraries work on the PriceRequest type?
Option 1:
class PricesController < ApplicationController
def get
CarrierValidator.validate(params)
...
end
end
class CarrierValidator
def self.validate(params)
raise CarrierError if !Carrier.find_by_name(params[:carrier_name]).exists?
end
end
Option 2:
class PricesController < ApplicationController
def get
pricesRequest = PricesRequest.new(carrier_name: params[:carrier_name], ...)
pricesRequest.validate
...
end
end
class PriceRequest
attr_accessor : ...
def initalize
...
end
def validate
CarrierValidator.validate(self.carrier_name)
end
end
class CarrierValidator
def self.validate(carrier_name)
raise CarrierError if !Carrier.find_by_name(carrier_name).exists?
end
end
TIA,
J
You should create a type. I would use ActiveModel to encapsulate the data (attributes) & business logic (validations & maybe some layer-specific methods for processing the data).
Basically, you want to be able to do Rails-y things in the controller like:
def get
price_request = PriceRequest.new(params[:price_request])
if price_request.valid?
# do something like redirect or render
else
# do something else
end
end
so you want to declare:
class PriceRequest
include ActiveModel::Model
attr_accessor :carrier, :address_from, :address_to, :container_type
validates :carrier, presence: true
validate :validate_address_from
def validate_address_from
# do something with errors.add
end
# and so on
This is a good place to start: http://edgeguides.rubyonrails.org/active_model_basics.html
More details in the API: http://api.rubyonrails.org/classes/ActiveModel/Model.html
Hope that points you in the right direction...
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)