Versioning model serialization - activerecord

I've a model and 2 controllers as follow :
class MyModel < ActiveRecord::Base
def serializable_hash(options={})
super(only: [:id, :foo])
end
end
module V1
class MyController < ApplicationController
def show
render json: {my_model: #my_model}
end
end
end
module V2
class MyController < ApplicationController
def show
render json: {my_model: #my_model}
end
end
end
I want to be able to return a different json depending on the controller :
class MyModel < ActiveRecord::Base
def serializable_hash(options={})
# If V1
super(only: [:id, :foo])
# ElsIf V2
super(only: [:id, :bar])
# End
end
end
I would like to find a generic solution, so I don't have to send the version manually in parameters.

It would be better to decouple the serialization from your model i.e. don't put the serialization code in the model. You have a few options: Using a json builder in your views directory, or using ActiveModelSerializers. Both approaches will make it easy to version your serialization code:
JSON builders:
# app/views/v1/my_controller/my_model.json.builder
json.my_model do
json.id #my_model.id
json.foo #my_model.foo
end
With the above set up you can imagine easily adding a v2 directory with a different serializer.
In your controller you don't even need to specify a render call since rails will notice you have a builder in your controller's view directory. More info about jbuilder in this Railscast
ActiveModelSerializers:
Same goes with serializers, they are just files in app/serializers. You can put your files in app/serializers/v1/my_model_serializer.rb.
The concept with these two approaches is to use Rails modular paths to version your files. A controller in app/controller/v1 will load files from other directories with the same v1: app/serializers/v1.
Here's the railscast for active model serializers. And the asciicast.
Also, here's a gem that helps with api versioning: https://github.com/EDMC/api-versions I use it and find it nice to work with.

Related

CanCanCan alias method for a controller

In my application I have permissions set up like so for an admin:
can :read, Journey
can :destroy, Journey
can :update, Journey
And I have controllers like so:
class JourneyController < ApplicationController
authorize_resource class: :journey
def index; end
def show; end
end
module Journeys
class VoidJourneyController < ApplicationController
authorize_resource class: :journey
def show; end
def destroy; end
end
end
This is based on how DHH does his controllers: http://jeromedalbert.com/how-dhh-organizes-his-rails-controllers/
Now the issue I have is that because I have a show method inside the VoidJourney controller (this is to show the user some additional information as we talk to an API) it means a user who doesn't have permission to destroy a journey can access it because show is aliased to read and only the destroy is protected in that controller.
CanCanCan has the alias_action method, but that only allows aliasing a method to another for all controllers, not just one.
The only way I could think to handle this was to do:
def show
authorize! :destroy, :journey
end
So that it checks that method against a different permission. But I'd like to avoid having to do that if possible.
Is it possible to alias a method in only one controller to another? And not alias for all controllers. Looking at the docs I can't see this.

How to map routes to controllers in Sinatra?

I'd like to create a simple experimental MVC framework using Sinatra.
I'd like to define resources by name "pages" for example should resolve to:
/pages (index)
/pages/new
/pages/:id/show (show)
as WELL as map to app/controllers/PagesController.rb with corresponding get('/') to be responsible for the index, post('/pages/create') be responsible for creation, etc.
Trouble is even after reading the official documentation I'm terribly confused. I imagine I need to use non-classic Sinatra model for this, but could anyone point me in the right direction?
Thank you
If you want what I think you're wanting, I do this all the time. Initially for this scheme I used the travis-api source as a reference, but essentially what you want to do is extend Sinatra::Base in a "controller" class and then mount up your individual Sinatra "controllers" in rack, something like this:
module Endpoint
def self.included(base)
base.class_eval do
set(:prefix) { "/" << name[/[^:]+$/].downcase }
end
end
end
class Users < Sinatra::Base
include Endpoint
get '/' do
#logic here
end
get '/:id' do
#logic here
end
post '/' do
#logic here
end
patch '/:id' do
#logic here
end
end
class Posts < Sinatra::Base
include Endpoint
post '/' do
#logic here
end
end
and then something like this:
class App
require "lib/endpoints/users"
require "lib/endpoints/posts"
attr_reader :app
def initialize
#app = Rack::Builder.app do
[Users, Posts].each do |e|
map(e.prefix) { run(e.new) }
end
end
end
def call(env)
app.call(env)
end
end
You can adjust this to whatever you need, but the idea is the same, you separate your app into composable Sinatra applications that each have a prefix that they are mounted under using Rack. This particular example will give you routes for:
get '/users'
get '/users/:id'
post '/users'
patch '/users/:id'
get '/posts'
I'll give you a very simple example here:
Create a file controller.rb
get '/pages' do
#pages = Pages.all
erb :pages
end
Next create a views directory in the same folder as teh controller, and create a file named pages.html.erb
This is the corresponding view to your previously created controller action.
Here, you can type something like:
<% #pages.each do |p| %>
<%= p.title %>
<% end %>
Restart your server, visit localhost:PORT/pages and you will see a list of all your page titles.
You can check out this link for a simple sinatra tutorial - http://code.tutsplus.com/tutorials/singing-with-sinatra--net-18965
You can make this as complicated or as simple as you need. For example:
Rails makes a lot of magic happen under the hood, whereas Sinatra is more flexible at the cost of requiring you to implement some of this stuff yourself.
controller_map = {
'pages' => PagesController
}
post '/:controller/new' do
c = params[:controller]
module = controller_map[c]
module.create_new()
...
end
get '/:controller/:id/show' do
c = params[:controller]
id = params[:id]
module = controller_map[c]
module.get(id)
...
end

What are callback classes in ActiveRecord::Base in Rails

Can some one explain 16 Callback classes in this guide http://guides.rubyonrails.org/active_record_validations_callbacks.html
Ok i think i understand your problem :
The after_destroy method in PictureFileCallbacks will be auto-magically called by rails :
When rails destroys your PictureFile object, it will instantiate a PictureFileCallbacks object and try to run an after_destroy method in it.
Everything works by convention, if you follow the naming properly everything will work out of the box.
Try it on a dummy project, and if you have some trouble making this work come back with some code to show.
everything works by convention, you can try the following example:
#generate PictrueFile model with name attribute and generate seed
rails g model PictureFile name:string
#seeds.rb
3.times do |i|
PictureFile.create(name: "name#{i}")
end
#create picture_file.rb and picture_file_callbacks.rb in model directory
#picture_file_callbacks.rb
class PictureFileCallbacks
def after_destroy(picture_file)
PictureFile.create(name: "demo")
end
end
#picture_file_callbacks.rb
class PictureFile < ApplicationRecord
after_destroy PictureFileCallbacks.new
end
execute the command in rails c
PictureFile.first.destroy
PictrueFile.pluck(:name) #=>["name1", "name2", "demo"]

Rails routing: Giving default values for path helpers

Is there some way to provide a default value to the url/path helpers?
I have an optional scope wrapping around all of my routes:
#config/routes.rb
Foo::Application.routes.draw do
scope "(:current_brand)", :constraints => { :current_brand => /(foo)|(bar)/ } do
# ... all other routes go here
end
end
I want users to be able to access the site using these URLs:
/foo/some-place
/bar/some-place
/some-place
For convenience, I'm setting up a #current_brand in my ApplicationController:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_filter :set_brand
def set_brand
if params.has_key?(:current_brand)
#current_brand = Brand.find_by_slug(params[:current_brand])
else
#current_brand = Brand.find_by_slug('blah')
end
end
end
So far so good, but now I must modify all *_path and *_url calls to include the :current_brand parameter, even though it is optional. This is really ugly, IMO.
Is there some way I can make the path helpers automagically pick up on #current_brand?
Or perhaps a better way to define the scope in routes.rb?
I think you will want to do something like this:
class ApplicationController < ActionController::Base
def url_options
{ :current_brand => #current_brand }.merge(super)
end
end
This method is called automatically every time url is constructed and it's result is merged into the parameters.
For more info on this, look at: default_url_options and rails 3
In addition to CMW's answer, to get it to work with rspec, I added this hack in spec/support/default_url_options.rb
ActionDispatch::Routing::RouteSet.class_eval do
undef_method :default_url_options
def default_url_options(options={})
{ :current_brand => default_brand }
end
end

Implementing an ActiveRecord before_find

I am building a search with the keywords cached in a table. Before a user-inputted keyword is looked up in the table, it is normalized. For example, some punctuation like '-' is removed and the casing is standardized. The normalized keyword is then used to find fetch the search results.
I am currently handling the normalization in the controller with a before_filter. I was wondering if there was a way to do this in the model instead. Something conceptually like a "before_find" callback would work although that wouldn't make sense on for an instance level.
You should be using named scopes:
class Whatever < ActiveRecord::Base
named_scope :search, lambda {|*keywords|
{:conditions => {:keyword => normalize_keywords(keywords)}}}
def self.normalize_keywords(keywords)
# Work your magic here
end
end
Using named scopes will allow you to chain with other scopes, and is really the way to go using Rails 3.
You probably don't want to implement this by overriding find. Overriding something like find will probably be a headache down the line.
You could create a class method that does what you need however, something like:
class MyTable < ActiveRecord::Base
def self.find_using_dirty_keywords(*args)
#Cleanup input
#Call to actual find
end
end
If you really want to overload find you can do it this way:
As an example:
class MyTable < ActiveRecord::Base
def self.find(*args)
#work your magic here
super(args,you,want,to,pass)
end
end
For more info on subclassing checkout this link: Ruby Tips
much like the above, you can also use an alias_method_chain.
class YourModel < ActiveRecord::Base
class << self
def find_with_condition_cleansing(*args)
#modify your args
find_without_condition_cleansing(*args)
end
alias_method_chain :find, :condition_cleansing
end
end

Resources