I am using rocket pants to render my JSON API.
I'm trying to change the way it renders the JSON by overriding as_json in my model, but somehow, it seems not to change anything in the rocket pants response.
in my controller:
class Api::V1::ProjectsController < RocketPants::Base
...
def show
expose Project.find(params[:id])
end
...
end
And in my model:
class Project < ActiveRecord::Base
...
def as_json(options = {})
{"this" => "is not working!"}
end
...
end
What am I missing?
In addition, there is a first set of options that can be sent to the expose block. Depending on the source, you can pass options through to serializable_hash method. For example:
expose user, only: [:name, :email]
This will call serializable_hash on the object with name and email.
You can also specify eager loading in this set of options. For example:
expose uploads, :include => { :user => { :only => :username } }.
This will expose your uploads and eager load the belongs_to association with user.
Source: https://github.com/filtersquad/rocket_pants/issues/20#issuecomment-6347550
I've figured out how to do that. The way rocket pants work is by looking at the serializable_hash method. Overriding it results in a change in the response.
Edit:
The solution I got to:
In the model where I need to add some attributes: simply override the attributes method:
# Overriding this method is required for the attribute to appear in the API
def attributes
info = {} # add any logic that fits you
super.merge info
end
In the controller that needs to expose the API I've created a new Model class (this is only needed in order to keep different API versions), and overridden the serializable_hash method:
class Location < ::Location
def serializable_hash(options = {})
super only: [:id, :lat, :long],
include: [user: {only: ...your attributes here...}]
end
end
For nested things:
paginated #matches, include: { listing: { include: { company: { only: [:name, :email] } } } }
Related
I am working on a Ruby on Rails project with ruby-2.5.0 and Rails 5. i am working on api part, i have used jsonapi-serializers gem in my app. I want to add conditional attribute in serializer.
Controller:
class RolesController < ApplicationController
def index
roles = Role.where(application_id: #app_id)
render json: JSONAPI::Serializer.serialize(roles, is_collection: true)
end
end
Serializer:
class RoleSerializer
include JSONAPI::Serializer
TYPE = 'role'
attribute :id
attribute :name
attribute :application_id
attribute :application do
JSONAPI::Serializer.serialize(object.application)
end
end
Here application is a model which has_many roles and roles belongs to application. I want to add application details in some conditions. I also tried like:
Controller:
class RolesController < ApplicationController
def index
roles = Role.where(application_id: #app_id)
render json: JSONAPI::Serializer.serialize(roles, is_collection: true, params: params)
end
end
Serializer:
class RoleSerializer
include JSONAPI::Serializer
TYPE = 'role'
attribute :id
attribute :name
attribute :application_id
attribute :application do
JSONAPI::Serializer.serialize(object.application), if: #instance_options[:application] == true
end
end
But #instance_options is nil. Please help me how i can fix it. Thanks in advance.
In the jsonapi-serializers this is what is said about custom attributes:
'The block is evaluated within the serializer instance, so it has access to the object and context instance variables.'
So, in your controller you should use:
render json: JSONAPI::Serializer.serialize(roles, is_collection: true, context: { application: true })
And in your serializer you should use context[:application] instead of #instance_options
Given the following controller code:
def show
render json: #post, include: 'author', adapter: :json_api
end
Inside the serializer do I have access to the include directive?
class PostSerializer < ActiveModel::Serializer
attributes :title, :body, :foo
belongs_to :author
def foo
# can I gain access to the include_directive here somehow?
end
end
I have looked in #attributes, #instance_options, #object, #root #scope and #serializer_class (all the instance variables I can see with pry) with no luck.
I am using active_model_serializers (0.10.2).
The include option is passed to the Adapter. The adapter uses it essentially by passing it to serializable_hash in each serializer. So, you could redefine that method, inspect it, and call super
Otherwise I'm not sure what you're trying to do.
I have two models, restaurant and cuisine with a many to many association. And I have this in my app/admin/restaurant.rb
ActiveAdmin.register Restaurant do
scope("All"){|scope| scope.order("created_at desc")}
Cuisine.all.each do |c|
scope(c.name) { |scope| scope.joins(:cuisines).where("cuisines.id=?",c.id)}
end
end
The problem is whenever I delete or add a new cuisine the scopes do not change until I make a change to my admin/restaurant.rb file. How can I fix this issue?
I was able to fix this by adding in my admin/restaurant.rb
controller do
before_filter :update_scopes, :only => :index
def update_scopes
resource = active_admin_config
Cuisine.order("created_at ASC").each do |m|
next if resource.scopes.any? { |scope| scope.name == m.name}
resource.scopes << (ActiveAdmin::Scope.new m.name do |restaurants|
restaurants.joins(:cuisines).where("cuisines.id=?", m.id)
end)
end
resource.scopes.delete_if do |scope|
!(Cuisine.all.any? { |m| scope.name == m.name })
end
resource.scopes.unshift(ActiveAdmin::Scope.new "All" do |restaurants| restaurants end)
end
found the solution here
I'm not sure of a way to defines scopes dynamically, at least using the scope method.
The alternative to the scope method is defining a class method, which accomplishes the same thing so far as I know.
In other words,
scope("All"){|scope| scope.order("created_at desc")}
is the same as
# in a Class
class << self
def All
order("created_at desc")
end
end
You can dynamically create class methods using this method (taken from ruby-defining-class-methods:
class Object
def meta_def name, &blk
(class << self; self; end).instance_eval { define_method name.to_s, &blk }
end
end
I'll use the following to remove the generated class methods:
class Object
def meta_undef name
(class << self; self; end).class_eval { remove_method name.to_sym }
end
end
These methods can be called from the save and destroy hooks on your models, i.e.:
# in a Model
def save(*args)
self.class.meta_def(name) do
joins(:cuisines).where("cuisines.id=?",c.id)
end
super(*args)
end
def destroy(*args)
self.class.meta_undef(name)
super(*args)
end
Then whenever a record is created or removed, the scopes will be updated. There are pros and cons of this approach. Clearly it's nice to define methods on the fly, but this is vulnerable to remote code execution.
Personally I'd probably hold off from dynamically defining class methods (i.e. scopes) and just make one that accepts an argument. Example:
# This is with standard ActiveRecord, not sure about ActiveAdmin
class Restaurant < ActiveRecord::Base
def self.All
order("created_at desc")
end
end
class Cuisine < ActiveRecord::Base
def self.by_name(name)
Restaurant.all.joins(:cuisines).where("cuisines.name=?", name)
end
end
Cuisine.by_name("something")
Restaurant.all.All
Restaurant.All
edit in response to your comment:
load(file) will re-load the source. So you could try the following:
# in a model
def save(*args)
load(Rails.root.join("app", "models", "THIS_MODEL_FILE.rb")
super
end
def destroy(*args)
load(Rails.root.join("app", "models", "THIS_MODEL_FILE.rb")
super
end
Under the hood, save is called for both create and update. So overriding it and destroy covers all the CRUD operations.
The reason I didn't initially recommend this approach is that I haven't personally used it. I'd be curious to know how it works.
Given the following:
class WebsitesController < ApplicationController
# POST /websites/save
# POST /websites/save.json
def save
Website.exists?(name: params[:website][:name]) ? update : create
end
# POST /websites
# POST /websites.json
def create
#server = Server.find_or_create_by_name(params[:server_id])
#website = #server.websites.new(params[:website])
#etc... #website.save
end
# PUT /websites/1
# PUT /websites/1.json
def update
#website = Website.find_by_name(params[:website][:name])
#etc... #website.update_attributes
end
end
The client does not have any IDs of these models
The request that gets sent only has the names, but not the ids.
And the following models
class Website < ActiveRecord::Base
serialize :website_errors
attr_accessible :plugins_attributes
has_many :plugins
accepts_nested_attributes_for :plugins
end
class Plugin < ActiveRecord::Base
belongs_to :website
end
When I make a POST request to /websites/save.json, the Website gets updated correctly if it exists, but the Plugins that belong to it always get recreated causing duplicate content in the Database. Why does this happen? I redirect to the update action which calls update_attributes so how can it be that it does not update it? I take it that it's because no ID is given with the request.
Can I make the Controller listen to plugin_name instead of plugin_id?
Modify your controller to have this:
def update
#website = Website.find_by_name(params[:website][:name])
if #website.update(params)
redirect_to website_path(#website)
else
render :edit
end
end
Also, if you're using strong_parameters, you'll need this at the bottom of your controller:
params.require(:website).
permit(
:name,
...,
plugins_attributes: [
:name,
...,
]
)
end
I want to capture an entire posts params, store it in the DB in one field (text), and then later get at each individual param? Possible? Any example you can show? thanks
You can serialize the entire params hash (or any other object)
class SomeModel < ActiveRecord::Base
serialize :params
…
end
class SomeModelsController < Applicationcontroller
def some_action
SomeModel.create(:params => params)
end
end
All you need is something like the following. I didn't include all of the Sendgrid params since there are so many and you will know what I mean with a few:
class SendgridMessage < ActiveRecord::Base
serialize :attachments
...
end
class SendgridMessagesController < ApplicationController
def create
SendgridMessage.create(:to => params[:to], :from => params[:from], :attachments => params[:attachments])
end
end
Sendgrid will send a POST to /sendgrid_messages with the params, and your object will be created with all of the correct fields (you will need to add some to the example) and serialized attachments like you are looking for.
perhaps
request.raw_post
is what you're looking for?
http://api.rubyonrails.org/classes/ActionDispatch/Request.html#method-i-raw_post