Is there any way to remove sensitive fields from the result set produced by the default ActiveRecord 'all', 'where', 'find', etc?
In a small project that I'm using to learn ruby I've a reference to User in every object, but for security reasons I don't want to expose the user's id. When I'm using a simple HTML response it is easy to remove the user_id simply by not using it. But for some task I'd like to return a json using something like:
def index
#my_objects = MyObject.all
respond_to do |format|
...
format.json { render json: #my_objects, ...}
...
end
end
How do I prevent user_id to be listed? Is there any way to create a helper that removes sensitive fields?
You can use the as_json to restrict the attributes serialized in the JSON response.
format.json { render json: #my_objects.as_json(only: [:id, :name]), ...}
If you want to make it the default, then simply override the method in the model itself
class MyObject
def serializable_hash(options = nil)
super((options || {}).merge(only: [:id, :name]))
end
end
Despite this approach is quick and effective, it rapidly becomes unmaintainable as soon as your app will become large enough to have several models and possibly different serialization for the same type of object.
That's why the best approach is to delegate the serialization to a serializer object. It's quite easy, but it will require some extra work to create the class.
The serializer is simply an object that returns an instance of a model, and returns a JSON-ready hash. There are several available libraries, or you can build your own.
Related
I start to learn ruby on rails and I want to temporary add an attribute to a model so I added the accessor to my model :
attr_accessor :dettes
And then I used the method each, here is what I have :
#depenses = Depense.where(user_id: #user.id)
#depenses.each do |d|
d.dettes = "value"
end
render json: #depenses
But this didn't work, my new attribute isn't added
I've seen others questions about this but I'm still not able to resolve this, am I missing something ?
By default, render :json only adds the permanent attributes. You can add the temporary one as well this way:
render json: #depenses, methods: :dettes
As far as I understand you want modify object within request without mutating it in the database, so you add attr_accessor.
Then you used
#depenses = Depense.where(user_id: #user.id)
Which returns instance of ActiveRecord::Relation(not explicit instances of objects which you can modify). So you when you iterates within this relation instance and change it attr_accessor the instance of modyfied object hasn't been assigned to any public variable. Basically you need to convert relation to array, then #depenses going to be an array of Depense instance objects, which you can modify and pass to render methods:
#depenses = Depense.where(user_id: #user.id).to_a
Then you need tell json that you want to use custom method for serializing, like #eiko mentioned:
render json: #depenses, methods: :dettes
(But if you have a loot of data in Depense datatable then it's not the best idea, you gonna use some limitations with offsets for where clause)
Keep rocking ;)
I'm using RABL right now to generate JSON responses of an API in Rails, but I'm finding that while RABL is super handy for mapping models to responses, to create a consistent API I'm having to to duplicate that mapping logic in the update and create functions of my controller.
As a simple example, if I just want to change the attribute names in the response to a POST request, I can do this in RABL:
create.rabl
object #car
attributes car_id: :id, badly_named_legacy_column_that_means_color: :color
But if I want the client to be able to use these same "cleaned up" attributes in the JSON POST/PUT request itself (i.e. be able to send { "id": 1, "color": "red" } instead of { "car_id": 1, "badly_named_legacy_column_that_means_color": "red" }), I have to manually do this mapping again in the controller:
cars_controller.rb
def create
params[:car_id] = params.delete(:id)
params[:badly_named_legacy_column_that_means_color] = params.delete(:color)
#car = Car.create(params)
end
Now there are two places that I need to map car_id to badly_named_legacy_column_that_means_color. Not very DRY.
So far I haven't come across any way to handle this using RABL. Is there one that I'm missing? I also realize this might be outside the scope of RABL, which bills itself specifically as a templating system, so maybe is there another API builder that would allow me to do this? I love the idea of mapping messy database columns to a clean API but having to specify this mapping in both the view and the controller isn't very DRY. Any thoughts appreciated.
Update
The original answer is all about Ruby/Rails => JSON, the question is JSON => Ruby/Rails. This answer about associating columns should explain an approach:
alias_attribute :new_column_name, :column_name_in_db
Then you can just reference new_column_name in the RABL and Rails will handle the association on the create/update.
You should be able to call render from the create method and render any view. You could customize a response with a create specific template or reuse the generic show template. The trick is to re-use the object rabl template (app/views/car/car.rabl in this case), for example:
# POST /cars
def create
#car = Car.new(params)
if #car.save
render action: 'show'
else
respond_with #car
end
end
Where app/views/cars/car.rabl is
attributes :id, ...
and app/views/cars/show.rabl is
object #car
extends "cars/car"
I've got some fairly complex JSON responses in my application for my Ticket model and I'd like my TicketDecorator to be the one who builds those responses.
However, I've already setup and API for my system and use RABL to build those JSON responses, so I'd like to reuse the templates that I've already created.
I'm wondering if it's possible to render a RABL template from within a method inside TicketDecorator, something like this:
Here is my RABL template tickets/show.json.rabl
object #ticket
attributes :id, :link, :reported_ago_in_words_with_reporter,
:last_updated_in_words_with_updater, :priority_label, :status,
:location, :category_list
node(:attachment_urls) { |ticket| [ asset_path(ticket.first_attachment_url), asset_path(ticket.second_attachment_url), asset_path(ticket.third_attachment_url) ] }
node(:comment_count) { |ticket| ticket.comments.count }
child :recent_comments do
extends 'comments/index'
end
and here is my TicketDecorator method:
class TicketDecorator < ApplicationDecorator
def as_json
h.render(template: "tickets/show", formats: :json)
end
end
However, this doesn't work because I can't sucessfully set #ticket, and I get an error saying: undefined method `first_attachment_url' for nil:NilClass because #ticket is nil.
Anybody have any good solutions on how I can make these two work nicely together?
My only real thought would be rendering the template to a string and using RABL manually, but I'm not sure how I'd be able to call render_to_string inside of Draper since its not a view helper.
Any thoughts about that?
If it is a matter of accessing objects in Rabl, maybe this can be of help to you.
Also on your custom nodes I would access the object directly, instead of relying on the block variable. The latter is imo meant for handling collections, like this:
collection #tickets
attribute :name
node(:some_complex_stuff) {|ticket| ticket.vendor.heavy_lifting}
You might also look into the fact, that you can call any object method like an attribute since it still is Ruby :)
class Ticket
def we_got_what
"the funk!"
end
end
In order to get funky in your Rabl just do:
object #ticket
attribute :we_got_what
There's now a rabl wiki entry describing a situation like this. For others arriving, check it out here
The solution is to send the decorator to rabl rather than render rabl from the decorator. Flipping the responsibility makes things a bit easier.
My rails app produces XML when I load /reports/generate_report.
On a separate page, I want to read this XML into a variable and save it to the database.
How can I do this? Can I somehow stream the response from the /reports/generate_report.xml URI into a variable? Or is there a better way to do it since the XML is produced by the same web app?
Here is my generate_report action:
class ReportsController < ApplicationController
def generate_report
respond_to do |format|
#products = Product.all
format.xml { render :layout => false }
end
end
end
Here is the action I am trying to write:
class AnotherController < ApplicationController
def archive_current
#output = # get XML output produced by /reports/generate_report
# save #output to the database
respond_to do |format|
format.html # inform the user of success or failure
end
end
end
Solved: My solution (thanks to Mladen Jablanović):
#output = render_to_string(:file => 'reports/generate_report.xml.builder')
I used the following code in a model class to accomplish the same task since render_to_string is (idiotically) a protected method of ActionController::Base:
av = ActionView::Base.new(Rails::Configuration.new.view_path)
#output = av.render(:file => "reports/generate_report.xml.builder")
Perhaps you could extract your XML rendering logic to a separate method within the same controller (probably a private one), which would render the XML to a string using render_to_string, and call it both from generate_report and archive_current actions.
What I typically do in this type of situation is to create a separate module/class/model to generate the report (it could even potentially be right in the Product model). This separate component could be in app/models or it could be in lib. In any case, once you have it extracted you can use it anywhere you need it. The controller can call it directly. You can generate it from the console. You can have a cron job generate it. This is not only more flexible, but it also can help smooth out your request response times if the report becomes slow to generate.
Since you are using a template it's understandable that the controller route is convenient, but even if you have to include some kind of ruby templating system in your auxiliary lib, it's still probably going to be less hassle and more flexible then trying to go through the controller.
#output = Product.all.to_xml
I'm sorry, is you question about Xml or about sessions? I mean is the fact that your action generates Xml material to the question? Or do you just want to save the output of the action for latter use?
You said on a "separate" page - you mean on another request? (like after user approved it?)
Why do you want to save the output? Because it should be saved exactly as rendered? (for example user can get frustrated if he clicked to save one report and you saved another)
Or is this thing expensive to generate?
Or may be, I got it wrong and it's about refactoring?
Is there an easy way to return data to web service clients in JSON using Rails?
Rails resource gives a RESTful interface for your model. Let's see.
Model
class Contact < ActiveRecord::Base
...
end
Routes
map.resources :contacts
Controller
class ContactsController < ApplicationController
...
def show
#contact = Contact.find(params[:id]
respond_to do |format|
format.html
format.xml {render :xml => #contact}
format.js {render :json => #contact.json}
end
end
...
end
So this gives you an API interfaces without the need to define special methods to get the type of respond required
Eg.
/contacts/1 # Responds with regular html page
/contacts/1.xml # Responds with xml output of Contact.find(1) and its attributes
/contacts/1.js # Responds with json output of Contact.find(1) and its attributes
http://wiki.rubyonrails.org/rails/pages/HowtoGenerateJSON
Rails monkeypatches most things you'd care about to have a #to_json method.
Off the top of my head, you can do it for hashes, arrays, and ActiveRecord objects, which should cover about 95% of the use cases you might want. If you have your own custom objects, it's trivial to write your own to_json method for them, which can just jam data into a hash and then return the jsonized hash.
There is a plugin that does just this,
http://blog.labnotes.org/2007/12/11/json_request-handling-json-request-in-rails-20/
And from what I understand this functionality is already in Rails. But go see that blog post, there are code examples and explanations.
ActiveRecord also provides methods to interact with JSON. To create JSON out of an AR object, just call object.to_json. TO create an AR object out of JSON you should be able to create a new AR object and then call object.from_json.. as far as I understood, but this did not work for me.