Obfuscating ids in Rails app - ruby-on-rails

I'm trying to obfuscate all the ids that leave the server, i.e., ids appearing in URLs and in the HTML output.
I've written a simple Base62 lib that has the methods encode and decode. Defining—or better—overwriting the id method of an ActiveRecord to return the encoded version of the id and adjusting the controller to load the resource with the decoded params[:id] gives me the desired result. The ids now are base62 encoded in the urls and the response displays the correct resource.
Now I started to notice that subresources defined through has_many relationships aren't loading. e.g. I have a record called User that has_many Posts. Now User.find(1).posts is empty although there are posts with user_id = 1. My explanation is that ActiveRecord must be comparing the user_id of Post with the method id of User—which I've overwritten—instead of comparing with self[:id]. So basically this renders my approach useless.
What I would like to have is something like defining obfuscates_id in the model and that the rest would be taken care of, i.e., doing all the encoding/decoding at the appropriate locations and preventing ids to be returned by the server.
Is there any gem available or does somebody have a hint how to accomplish this? I bet I'm not the first trying this.

What you are describing sounds like a specialized application of a URL slug. Take a look at plugins like acts_as_sluggable or friendly_id. Also look at overriding the to_param method on your User model.
Maybe start here: Best Permalinking for Rails

Related

How can I disguise the ID part of my URL in rails when working without active record?

I have a URL that looks like this:
http://localhost:3000/activities/5NcsdzWvXbv
I'd like to make it look something like this:
http://localhost:3000/activities/river-rafting
I have a column in my non-activerecord database that stores the names of activities that I'd like to use instead of the id.
I've taken a look at friendly_ID gem but it looks like it won't work for me because of the way my model is set up.
class Leads
include ActiveAttr::Model
end
Is there an easy solution to this?
I checked out an old rails question that recommended doing this:
def to_param
self.name
end
This wouldn't work for me as my model file isn't connected to activerecord but instead an external nosql database.
Can I just make some modifications to my routing file or is there some other way to modify my URL?
Thanks for your time.
I don't quite understand your problem.
If your routes.rb is set up properly, then it will translate to activities/:id. So, if you're preparing data in controller, then it's up to you how you will fetch the data, passing params[:id] to your non-activerecord ORM.
The :id is just a placeholder. If you defined your route as activities/:slug, then you would access the id part using params[:slug]
Can you please post the part of the code which is not working for you?
Edit:
You still haven't posted any code which is not working for you. Anyways, I feel that you need to check some docs on working with multiple databases in Rails. Here is a sketch for starters:
Class YourClass
establish_connection "your_external_database"
end
Then you use that connection to perform queries to the db, using your slugs.
Edit2:
Ahhh... you need to obfuscate ids. It just boils down to exposing encoded ids and then decoding them at your (backend) side :)
Here is an example approach:
How do I obfuscate the ids of my records in rails?

How to use externally provided IDs in Rails REST API?

What are the best practices to working with external IDs in REST API in general, and specifically in Rails?
Some Background:
I created an API for my application, which uses standard resource based JSON REST API.
I have a Flight model, by which users to create model objects by POSTing to /api/flights. This assigns a unique id to the new Flight objects the way ActiveRecord always does.
So far pretty standard.
However, my customer wants me to provide an option to assign an id by himself as part of the creation of the Flight object in the API, and later use the id he assigned in order to show/update/delete (etc.) the object by API.
I figure I can simply add an external_id parameter and add routes to update/delete/index by external_id. However, this seems to clutter the elegant resource based rails approach.
So, what's the best practice?
I think this is fine. Another similar case is creating a "slug" or "friendly url" for a URL which replaces the less friendly ID ("augments" is a better word than replaces). As long as the external_id is immutable, unique within its scope, URL-safe and so on it's equivalent to the ID. Because it's an external value, however, you need to confirm with your client that it is immutable; while possible to have it change, there's a lot of work needed to handle changing the value (especially if it's part of a URL).
If you want to replace Rails' find method to use the external_id instead, use method_missing to redefine id -- there are examples of this around by googling "rails 3 slug method_missing" and similar.
I'm not sure if this can help but you can check the gem friendly id you can specify the external_id to this gem then it'll handle requests
http://example.com/states/4323454
to be like
http://example.com/states/washington

Rails: eager load on fetching associations, not on find

I've got three nested models: user has many plates and plate has many fruits. I also have a current_user helper method that runs in the before filter to provide authentication. So when I get to my controller, I already have my user object. How can I load all the user's plates and fruits at once?
In other words, I'd like to do something like:
#plates = current_user.plates(include: :fruits)
How can I achieve this?
I'm using Rails 3.1.3.
You will probably want to use the provided #includes method on your relation. DO NOT USE #all unless you intend to immediately work through the records, it will immediately defeat many forms of caching.
Perhaps something like: #plates = current_user.plates.includes(:fruits)
Unfortunately, there are portions of the Rails API that are not as well documented as they should be. I would recommend checking out the following resources if you have any further questions about the Rails query interface:
Query Interface Guide
ActiveRecord::Relation Walkthrough (screencast)
The query interface is possibly the most difficult part of the Rails stack to keep up with, especially with the changes made with Rails 3.0 and 3.1.
You can do
ActiveRecord::Associations::Preloader.new([current_user], :plates => :fruit).run
To eager load associations after current_user was loased. The second argument can be anything you would normally pass to includes: a symbol, an array of symbols, a hash etc
#plates = current_user.plates.all(:include => :fruits)
should do it.

Get all has many from an activerecord hash

I have a model Campain which has many Media.
I do this:
Campain.all.medias
But get this error:
undefined method `medias' for #<Array:0x00000004bbaf40>
How can I get all medias from Campain.all?
The better approach is to use this code:
Campaign.includes(:media).map(&:media)
(Used English grammar, I hope you get the main idea). This will get all in two requests.
---EDIT---
If media - is has_many assosiation, indeed the return will be in form: [[...],[..]] so in that case use flaten to make it just simple array.
In the case when there needed all Mediums that are for all Camplaign use arrays group method to collect unique or just simple uniq. This approach to use Rails classes is preferable, as it is more general and configurable, for example it will apply any default scopes, that may be on Campaign.
Just to complete Dylans Post - I upgraded to Rails 3.1.1 today. My app has ~60 Models and one of these was called Media and it worked well eaven when it was bad english. So I upgraded and I think they patched the pluralizemethod. I wasnt able to call the medias actions and rails said the table 'media' doesnt exist which was the moment I realised my table was called media because I used rails 3.0.3 to create the model. I renamed the model, controller and views names, updated the routes from resources :medias to ressources :media as a quick fix and it worked again!
If you are looking for all Media that has an associated Campaign, you should query via the Media object, not the Campaign object. This will give you one array of Media objects, and only takes one query (assuming your Media object has a campaign_id [that is, Media :belongs_to :campaign]).
Media.where("campaign_id is not null").all
As far as Rails is concerned, the singular of Media is "medium" and the plural is "media", so just try media. However, all will still return an array, which you can't call media on anyway. So you probably want:
#campaigns = Campaign.all
#media = Medium.where(:campaign_id => #campaigns.collect(&:id))
or this (similar to another answer, but you need to flatten the results):
Campaign.includes(:media).map(&:media).flatten

Ruby on rails - nested attributes: How to do a find or create of the nested model

I have a Bill model with nested Customer model.
The Customer model has a phone number with a uniqueness validation on it.
While creating the bill I want to fetch the existing record based on the phone number or create a new one if such doesn't exist.
How should I do it in a RESTful way?
you would use the find_or_create_by method which would look something like this in your case:
fetchedRecord = Bill.find_or_create_by_phone_number(customer.phone_number)
You can look at the find_or_create or find_or_create_by methods (which are dynamically created). A little Googling should get you there the rest of the way, I think.
It doesn't seem like these answers are what you are asking.
Forget about Rails, my question would be, what's the RESTful way to create a resource that might already exist? Should you POST to the resources (list) URL, and then expect a HTTP status code of 201 if the resource was created and a 200 if it already existed?
Seems like this should be spelled out in a standard somewhere.
By the way, this is how I am handling it--with status codes.
I place mine in the the association callback before_add

Resources