How to split things up in a grape api app? - ruby-on-rails-3

In every examples I see, people only implement one giant api.rb file. Ex:
intridea/grape
bloudraak/grape-sample-blog-api
djones/grape-goliath-example
While this approach works fine as is, it can quickly become crowded and difficult to maintain so I would like to split things up on my app.
For instance, I would like to split my entities from my resources, and then split up my resources between different files. For examples:
app
- api
api.rb
- entities
- weblog.rb
- post.rb
- comment.rb
- resources
- weblog.rb
- post.rb
- comment.rb
Now, api.rb would be something like:
require 'grape'
module Blog
class API < Grape::API
prefix "api"
end
end
app/api/entities/post.rb would be something like:
module Blog
module Entities
class Post < Grape::Entity
root 'posts', 'posts'
expose :id
expose :content
end
end
end
app/api/resources/post.rb would be something like:
module Blog
class API < Grape::API
resource :posts do
get do
present Post.all, with: Blog::Entities::Post
end
desc "returns the payment method corresponding to a certain id"
params do
requires :id, :type => Integer, :desc => "Post id."
end
get ':id' do
present Post.find(params[:id]), with: Blog::Entities::Post
end
end
end
end
When we do this, we encounter the following message:
Expected /blog-app/api/resources/post.rb to define Post
SOLUTION (thanks to dB. and my co-workers)
You have to change the structure to something like:
app
- api
api.rb
- resources
- post_api.rb
Then, in the post_api.rb
module Blog
class Resources::PostAPI < Grape::API
resource :posts do
get do
present Post.all
end
end
end
end
Finally, the api.rb becomes:
require 'grape'
module Blog
class API < Grape::API
prefix 'api'
version 'v1', :using => :path
format :json
mount Blog::Resources::PostAPI => '/'
end
end
Now /api/v1/posts should work :)

The class in post.rb should be Post, not API. Then you can mount the Post API inside class API.
class API < Grape::API
mount Blog::Post => '/'
end
To avoid confusion I would put Post in a Resources namespace, too or rename it to PostAPI.

I found it not working for path prefix:
mount Blog::Post => '/blog'
doesn't work if you want have prefix the path.
use
namespace :blog do
mount Blog::Post
end
Hope it helps!

Related

how can I make a simple route on rails and can I use it for an ajax form?

Ive been trying to create a simple route on rails, following this instructions
http://guides.rubyonrails.org/routing.html
my problem is that when I want to enter to my method I get a weird error.
I have a controler user and on my routes I wrote something like this
resources :users do
match "/custom/" => "user#custom"
end
So, at my controller I add this code
def custom
#user = User.find(params[:user_id])
end
but when I try to enter doing localhost:3000/users/1/custom I get an error like
uninitialized constant UserController
doing rake routes I can see
user_custom /users/:user_id/custom(.:format) user#custom
Any idea how to solve this problem?
I want this route to submit a form... is it possible to use this route (if i make it run) for use ajax? I want to submit a form.
Thanks
Change your route to:
resources :users do
match "/custom/" => "users#custom"
end
You should avoid the use of match though, since it will be deprecated in Rails 4. Try this instead
resources :users do
get :custom, on: :member
end
get is the verb, :custom the route and on: :member means that you are looking for a /users/:id/custom route instead of a /users/custom one. If you are looking for the latter, do this:
resources :users do
get :custom, on: :collection
end
Another way to do it is like this, which I prefer:
resources :users do
get 'custom', on: :collection
end
That gives you a route of /users/custom. If you were do use on: :member, then it would give you a route of /users/:id/custom.
You can also use a block for defining multiple custom actions for collections or members.
For example:
resources :users do
collection do
get 'custom'
post 'some_other_method'
end
member do
get 'some_action'
end
end

How do I create routes for a controller that does nothing with models in Rails?

Still pretty new to Rails, so if I'm taking the completely wrong approach, please feel free to straiten me out.
How do I make routes.rb aware that there's a root controller full of actions that don't manipulate models, while preserving the route helper methods? I'd like it to respond to requests like these:
http://localhost/download
http://localhost/share
With route helpers like
download_app_path
share_path
but without static named routes like these:
match '/download' => 'site#download', :as => :download_app
match '/share' => 'site#share', :as => :share
from a SiteController that doesn't create, show, or otherwise manipulates models from my app.
I've tried using an approach like this, but it works without generating the route helpers ( naturally )
match '/:action', :controller => 'site'
I could theoretically do without the route helpers, but I think they're a bit easier to read than passing hashes of url options to link_to or form methods.
Is there a way to accomplish a more resourceful root controller, or is what I'm trying to do unconventional for Rails?
Edit
For clarity, here's what this SiteController class looks like:
class SiteController < ApplicationController
def download
#custom_options = { .. }
end
def share
#custom_options = { .. }
end
def about
end
end
Its purpose is to allow me to collect pages that don't interact with resources ( such as Users or Friendships ) into a single controller and maintain them all in one place. I'm trying to set this controller up as the application root controller - so all paths from this controller will be directly off the app host ( myapp.com/download )
Thanks in advance!
routes and resources are not tied to models. it's just a RESTful convention. if you just want to use the index actions, in your example download and share could be done like
resouce :download, only: [:index]
resouce :share, only: [:index]
see all the examples in the guides http://guides.rubyonrails.org/routing.html
if you want to add the download and share functionality to some "resource" like, say a picture, then you would do something like:
resources :pictures do
get 'download', :on => :member
get 'share', :on => :member
end
a resource always has and endpoint /pictures for example. so if you want to have paths directly to your host, then you need to provide custom matchers in your routes like you did in your examples.

Nested Routing for Single Table Inheritance model rails 3.1

I created a Single table inheritance model in my model file and am having difficulty with the routing. When I use :as in my resource, it renames my named path.
Model file:
class Account < ActiveRecord::Base
belongs_to :user
end
class AdvertiserAccount < Account
end
class PublisherAccount < Account
end
Routes.rb
resources :advertiser_accounts, :as => "accounts" do
resources :campaigns
end
I used :as in my routes because it is a single table inheritance and I want to pass the account_id and not the advertiser_account_id. My link is http://127.0.0.1:3000/advertiser_accounts/1/campaigns
/advertiser_accounts/:account_id/campaigns/:id(.:format)
However, using :as renames my named path from advertiser_account_campaigns to account_campaigns. My route looks like
account_campaigns GET /advertiser_accounts/:account_id/campaigns(.:format) campaigns#index
So when I create a new item using form_for, I would get "undefined method `advertiser_account_campaigns_path'"
Edited: current hacked solution
A hack around way that I am using is to duplicate the code in the routes file. Anyone have suggestions?
resources :advertiser_accounts, :as => "accounts" do
resources :campaigns
end
resources :advertiser_accounts do
resources :campaigns
end
If you run "rake routes" with your setup you'll see this:
account_campaigns GET /advertiser_accounts/:account_id/campaigns(.:format) campaigns#index
POST /advertiser_accounts/:account_id/campaigns(.:format) campaigns#create
new_account_campaign GET /advertiser_accounts/:account_id/campaigns/new(.:format) campaigns#new
edit_account_campaign GET /advertiser_accounts/:account_id/campaigns/:id/edit(.:format) campaigns#edit
account_campaign GET /advertiser_accounts/:account_id/campaigns/:id(.:format) campaigns#show
PUT /advertiser_accounts/:account_id/campaigns/:id(.:format) campaigns#update
DELETE /advertiser_accounts/:account_id/campaigns/:id(.:format) campaigns#destroy
accounts GET /advertiser_accounts(.:format) advertiser_accounts#index
POST /advertiser_accounts(.:format) advertiser_accounts#create
new_account GET /advertiser_accounts/new(.:format) advertiser_accounts#new
edit_account GET /advertiser_accounts/:id/edit(.:format) advertiser_accounts#edit
account GET /advertiser_accounts/:id(.:format) advertiser_accounts#show
PUT /advertiser_accounts/:id(.:format) advertiser_accounts#update
DELETE /advertiser_accounts/:id(.:format) advertiser_accounts#destroy
So you should use "account_campaingns_path" in this setup, the ":as" actually changes the calls in the code not the paths in the url. If you want to change the paths you should use ":path =>" rather than ":as =>".
The Rails guide on routing also shows some examples with ":as" and ":path" and the resulting paths and helpers, you'll need to search a bit because think they only use in in examples explaining other cases.
Edit: rereading your question, I think you may also want to look at member routes, I'm not sure if that's what you want to mean with it being a single inheritance and not wanting to pass the advertiser_account's ':account_id'?

Trouble in Rails 3 Adding Restful Routes to Namespaced Resources

I am trying to add a route to a controller that has been set as a resource in the 'admin' namespace like this:
namespace :admin do
resources :books do
collection do
post :process_new
end
end
end
I have added an action int the Admin::BooksController for process_new, but whenever I try to access this action using the url: .../admin/books/process_new I get the following error:
Couldn't find Book with ID=process_new
It looks like it's routing to the show action and trying to use process_new as the id. Can someone shed some light on what I may be doing wrong?
**Edit:
I changed my redirects to use the helper functions and it seems to be working.
Add get :process_new to your resources :books routes:
namespace :admin do
resources :books do
collection do
get :process_new
post :process_new
end
end
end

Dynamic define_method throwing error in RSpec

I am pretty sure I am missing a basic mistake here, so I am hoping another set of eyes might help. I am using Rails 3, Ruby 1.9.2 and Rspec 2.
I would like to define dynamic class methods on a model so that I can return base roles for an assignable object (such as account) as they are added to the system. For example:
BaseRole.creator_for_account
Everything works fine via the console:
ruby-1.9.2-p180 :003 > BaseRole.respond_to?(:creator_for_account)
=> true
but when I run my specs for any of class methods, I get a NoMethodError wherever I call the method in the spec. I am assuming that something about how I am dynamically declaring the methods is not jiving with RSpec but I cannot seem to figure out why.
The lib dir is autoloaded path and the methods return true for respond_to?.
# /lib/assignable_base_role.rb
module AssignableBaseRole
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
BaseRole.all.each do |base_role|
role_type = RoleType.find(base_role.role_type_id)
assignable_name = base_role.assignable_type.downcase
method = "#{role_type.name}_for_#{assignable_name}"
define_method(method) do
self.where(:role_type_id => role_type.id,
:assignable_type => assignable_name).first
end
end
end
end
Then include the Module in BaseRole
# /models/base_role.rb
class BaseRole < ActiveRecord::Base
include AssignableBaseRole
belongs_to :role
belongs_to :role_type
......
......
end
Then in my spec:
it "adds correct authority for creator role" do
create_assignment
base_role = BaseRole.creator_for_account # <== NoMethodError here
user1 = Factory.create(:user)
account.users << user1
user1.roles_for_assignable(account).should include(base_role.role)
end
Did you have another class in your project or specs with the same name, but doesn't have the dynamic methods added? I had the exact same problem as you, and renaming one of the classes fixed it.
My guess is the other class is getting loaded first
It appears you are defining these methods based on values in the database:
BaseRole.all.each do |base_role|
.....
Could it be that "creator" doesn't exist in the test database as a role type, or "account" doesn't exist as assignable_type?
Presumably you are testing this in the console for development, not test, so the data could be mismatched. Might need to set up the data in a before hook.