How to use externally provided IDs in Rails REST API? - ruby-on-rails

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

Related

Generate random human understandable ID for users in rails

I want to generate a unique (human recognisable) id for each user that changes when they comment on different posts in my app. (so doesn't need to be stored in db as is constantly changing). The ID only needs to be used within the scope of a thread.
What I mean by this is identical to what YikYak does. In YikYak, anonymity is essential, so in each thread, users are assigned a randomly generated avatar which is the only way they and other people recognise which posts are posted from the same user.
I'm building an app where I too need anonymity, but I need users to be able to recognise when other people are commenting more than once.
What is the best thing to generate? A string of 2 random words like "Brocolli umbrella"? Or is there a way to randomly generate a simple avatar?
Any advice much appreciated
You might try ffaker gem and use
FFaker::Name.name
#=> "Christophe Bartell"

Rails Capitalization/Pluralization Rules

When dealing with resources (e.g. users) different parts of the rails app refer to them in one of several ways, some capitalized/singular, some lowercase/plural etc. At times this seems logical (e.g. a method for several resources vs. just one) but at other times it seems arbitrary...
Is there any easy way of remembering how to access them from different parts of the app?
Most of the time, you will need to access different models across the app. And you would always access them with singular name with first letter uppercase'd like User, Tweet. Regarding controllers, I don't think so you would ever to access a controller from some other controller.
Remember, if are using raw SQL, and you want to access the table of a model, that would always be in plural and all lower case, like users for User, and tweets for Tweet.
Regarding routes, they are always accessed through lowercase words, and deciding whether singular or plural -- it depends upon the context.
If you are accessing all tweets, the route method will be tweets_path, and if want one tweet, then tweet_path(1) or edit_tweet_path(1) where 1 being the id of the tweet that you want to show or edit.
And for classes: everywhere in Rails, and generally speaking in Ruby, they would always be singular, and uppercase'd.

Best practices regarding per-user settings and predefining options

I want to save settings for my users and some of them would be one out of a predefined list! Using https://github.com/ledermann/rails-settings ATM.
The setting for f.e. weight_unit would be out of [:kg, :lb].
I don't really want to hardcode that stuff into controller or view code.
It's kind of a common functionality, so I was wondering: Did anyone come up with some way of abstracting that business into class constants or the database in a DRY fashion?
Usually, when I have to store some not important information which I don't care to query individually, I store them on a serialized column.
In your case you could create a new column in your users table (for example call it "settings").
After that you add to user model
serialize :settings, Hash
from this moment you can put whatever you like into settings, for example
user.settings = {:weight_unit => :kg, :other_setting1 => 'foo', :other_setting2 => 'bar'}
and saving with user.save you will get, in settings column, the serialized data.
Rails does also de-serialize it so after fetching a user's record, calling user.settings, you will get all saved settings for the user.
To get more information on serialize() refer to docs: http://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize
UPDATE1
To ensure that settings are in the predefined list you can use validations on your user model.
UPDATE2
Usually, if there are some pre-defined values it's a good habit to store them in a constant inside the related model, in this way you have access to them from model (inside and outside). Acceptable values does not change by instance so it makes sense to share them between all. An example which is more valuable than any word. Defining in your User model:
ALLOWED_SETTINGS = {:weight_unit => [:kg, :lb],
:eyes_color => [:green, :blue, :brows, :black],
:hair_length => [:short, :long]}
you can use it BOTH
outside the model itself, doing
User::ALLOWED_SETTINGS
inside your model (in validations, instance methods or wherever you want) using:
ALLOWED_SETTINGS
Based on your question, it sounds like these are more configuration options that a particular user will choose from that may be quite static, rather than dynamic in nature in that the options can change over time. For example, I doubt you'll be adding various other weight_units other than :kg and :lb, but it's possible I'm misreading your question.
If I am reading this correctly, I would recommend (and have used) a yml file in the config/ directory for values such as this. The yml file is accessible app wide and all your "settings" could live in one file. These could then be loaded into your models as constants, and serialized as #SDp suggests. However, I tend to err on the side of caution, especially when thinking that perhaps these "common values" may want to be queried some day, so I would prefer to have each of these as a column on a table rather than a single serialized value. The overhead isn't that much more, and you would gain a lot of additional built-in benefits from Rails having them be individual columns.
That said, I have personally used hstore with Postgres with great success, doing just what you are describing. However, the reason I chose to use an hstore over individual columns was because I was storing multiple different demographics, in which all of the demographics could change over time (e.g. some keys could be added, and more importantly, some keys could be removed.) It sounds like in your case it's highly unlikely you'll be removing keys as these are basic traits, but again, I could be wrong.
TL;DR - I feel that unless you have a compelling reason (such as regularly adding and/or removing keys/settings), these should be individual columns on a database table. If you strongly feel these should be stored in the database serialized, and you're using Postgres, check out hstore.
If you are using PostgreSQL, I think you can watch to HStore with Rails 4 + this gem https://github.com/devmynd/hstore_accessor

What command can I use to create this Ruby on Rails model?

I'm creating a model in Ruby on Rails that will act like a file system. You'll have assets (like files) that could either be folders or files themselves. How can I create a command for this?
Asset
id (unique auto-incrementing number)
name
is_directory (bool)
user_id (id of the owner)
parent_asset_id (id of parent directory, or null if under the root)
access_token (randomly generated token, used to send shareable links)
contents
I'm thinking something like:
rails generate model Asset
name:string
is_directory:boolean
user_id:integer
parent_asset_id:integer
access_token:string contents:??
Some questions I have:
What's the difference betweeen blob vs longblob vs mediumblob vs longtext vs etc, and which would I want to use? (The assets are essentially text... not sure what the max size will be yet)
Is the parent_asset_id a good naming convention, or is there something else that would make Rails give me some secret sauce, similar to why I picked the name user_id (to match the User model)?
Is there a way to declare a default random string value for the access_token? (The access token will be used for a shareable link to the asset)
Anything else I'm overlooking?
This is a detailed question, so I hope it serves as a case study for anyone looking to implement something like a file system in RoR.
Obviously if you really wanted to implement a file system you'd use an actual file system or Amazon S3... but if you want a light-weight file-like system in RoR, this seems like the best approach.
First off, I strongly recommend you consider an existing gem like Paperclip, which will handle a lot of these details for you.
Answers in order:
You would use the binary field type to store general data, but if you use Paperclip there are some specific fields you would need to use instead, which are explained in Paperclip's docs.
If by parent_asset_id you really mean the asset can 'belong' to many other models, then look into setting up a polymorphic relationship, with an id and type field. If instead you mean storing the path to the stored file, then Paperclip handles this for you. See #3 for details...
You can access a stored file on Paperclip by calling something as simple as asset.url in your view. If you wish to go manual and insert a random code, you can insert a callback into your Asset.rb model that does something like:
before_create :generate_key
def generate_key
self.key = ActiveSupport::SecureRandom.hex
end
S3 is not a complex system to set up on Rails, and it is far more flexible and scaleable than storing the files elsewhere - however, if you want to, then use the 'assets' path.

Obfuscating ids in Rails app

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

Resources