Rails Controller action: Customize key in params hash - ruby-on-rails

I'm working on a Rails 3.1.x app and I have the following set of models:
class Widget
include Mongoid::Document
field :name
embeds_many :comments
end
class ShinyWidget < Widget; end
class DullWidget < Widget; end
class Comment
include Mongoid::Document
field :message
embedded_in :widget
end
So basically I need to allow comments to be associated with different types of widgets. Using the standard resources in my routes such as:
resources: widgets do
resources :comments
end
That exposes urls such as GET /widgets, GET /widgets/:widget_id/comments, etc. However, I'd like to expose an API for adding comments to different types of widgets. I'd like those API URL's to look something like:
GET /shinywidgets/:widget_id/comments
POST /shinywidgets/:widget_id/comments
However, I'm ok with having a ShinyWidgetsController and a DullWidgetsController, but I'd like to only create a single CommentsController. Since I haven't thought of a nice way of having a single CommentsController to handle comments for different types of widgets, I tried this:
resources :widgets do
get 'comments', to: 'widgets#comments_index'
post 'comments', to: 'widgets#comments_create'
end
When doing a POST to /widgets/:widget_id/comments the params hash stores the comment data that's being posted in a key named widget instead of what I was hoping for comment.
I know if used resources :comments Rails would change that key in the params hash to be comment, but can I tell Rails what to name that key given my current setup?
Currently I have to create a comment doing something like this:
def comments_create
widget = Widget.find(params.delete :widget_id)
comment = widget.comments.create(params[:widget])
end
I'd really like to have:
comment = widget.comments.create(params[:comment])
Any thoughts?

This is currently very wrong.
In order to make this work as it should, you should create a route like this
resources :widgets do
get 'comments' => 'comments#index'
post 'comments' => 'comments#create'
end
and when posting to this CommentsController, you pass in the comment info correctly in the prams[:comment].
Your controller would have actions like this
def create
widget = Widget.find(params.delete :widget_id)
comment = widget.comments.create(params[:comment])
end

Related

ActiveAdmin how to Decorate associated links

In ActiveAdmin, I know I can use decorators, like Draper, to feed display_name and name, but how do I use the decorator for simple association links (i.e. auto_link(resource))?
Given I have a Post & a Comment:
# Post.rb
class Post
has_many :comments
end
# Comment.rb
class Comment
belongs_to :post
end
# decorators/post_decorators.rb
class PostDecorator
def name
"Custom Post Name ##{object.id}"
end
end
# admin/post.rb
ActiveAdmin.register Post do
delegate_with PostDecorator
end
# admin/comments.rb
ActiveAdmin.register Comment do
index do
# ...
column :post
# ...
end
show do
default_main_content
end
end
When viewing the Comment ActiveAdmin area, the show's default_main_content and the index's column :post both link automatically to the Post object, but never use the decorator.
I will see: Post #4 instead of Custom Post Name #4 in those sections.
When I visit the Post admin area, it will use the decorated name perfectly fine.
How do I get automatic links to use Draper throughout the entire admin area?
I currently have a def name on the object itself, but that is a display property and want to move it to a Decorator.
If you're using Draper, you can use decorates_association to tell one decorator to decorate its associations. This requires that you have a CommentDecorator.
class CommentDecorator < Draper::Decorator
delegate_all
decorates_association :post
end
ActiveAdmin.register Comment do
decorate_with CommentDecorator
...
end
#nitsujri you can write a draper concern that you can include on all of your decorators that functionally handles all associations for you. Like you I was also tired of having to manage the associations myself - it meant keeping track of associations in another place.
Here is the simple concern that relies on using activerecord reflection to pull all of the names off of the existing object and throwing them to drapers decorates_associations method.
module AutoDecorateAssociations
extend ActiveSupport::Concern
included do
delegate :class, to: :object, prefix: true
decorates_associations *(object_class.reflect_on_all_associations.map(&:name) - [:versions])
end
end
and then just include AutoDecorateAssociations on the decorators you don't want to have to keep track of (unfortunately you can't toss it in your application_decorator)

Self nesting rails categories

I have an store application, where I need to make custom routing system where URL stores categories for products. For example, http://example.com/languages/ruby/rails will display category#show named 'rails', that has parent named 'ruby', that has parent named 'languages' and and URL of http://example.com/languages/ruby/rails/store will display product in this category.
Currently I have:
category.rb
belongs_to :parent, class_name: 'Category'
has_many :categories, foreign_key: :parent_id
has_many :products
routes.rb
resources :categories, :path => '', :only => [:index, :show] do
resources :products, :path => '', :only => [:show]
end
root :to => 'products#index'
but it still stacks up to 2, e.g. URL http://example.com and http://example.com/languages shows list of categories/subcategories, but http://example.com/languages/ruby have params: {"action"=>"show", "controller"=>"products", "category_id"=>"language", "id"=>"ruby"}
Removing products from routes does not help at all - then it just says that No route matches [GET] "/language/ruby", although I assume It might cause need for extra check if current URL point on category or product later on.
Also I tried get '*categories/:id', to: 'category#show' variations
+ I am using friendly_id gem so that path do not look like http://example.com/2/54/111/6
I just want to find out what is the best ruby on rails solution for this kind of situations, when you need search engine optimizations + endless (e.g. no way to define how deep such recursion can go) nested resources that nest themselves (including fact that category/language/category/ruby/category/rails just looks ugly).
Note: most information I used is taken from Stack Overflow and railscasts.com (including pro/revised episodes), so mentioning a good source with information like this will be great too.
I solved this myself recently with a CMS I built on Rails recently. I basically construct the routes dynamically at runtime from the database records. I wrote this blog post on the strategy:
http://codeconnoisseur.org/ramblings/creating-dynamic-routes-at-runtime-in-rails-4
The core of the solution (adapting the blog post above) is simply iterate over the database records and construct the routes needed for each category. This is the main class for doing that:
class DynamicRouter
def self.load
Website::Application.routes.draw do
Category.all.each do |cat|
get cat.route,
to: "categories#show",
defaults: { id: cat.id },
as: "#{cat.routeable_name}_#{cat.name}"
end
end
end
def self.reload
Website::Application.routes_reloader.reload!
end
end
For the above, the Category model should implement a "routeable_name" method which simply gives an underscored version of the category name that uniquely names that category's route (its not strictly necessary, but helps when doing "rake routes" to see what you have). and the #route method constructs the full route to the category. Notice the defaults which sets the ID param for the category. This makes the controller action a very simple lookup on the category's ID field like so:
class CategoryController < ApplicationController
def show
#category = Category.find(params[:id])
end
end

Rails Param Population

When you have a rails resource defined rails seems to automatically create a params entry of attributes for that resource. e.g. if my model Lesson has a subject attribute and I post subject=Maths it automatically creates the param[lesson] = { subject: 'Hello' }. The problem I am having is getting nested attributes to appear within this created lesson array.
I'm using mongoid as my backend and have an association on Lesson called activities. The code looks like this:
class Lesson
include Mongoid::Document
field :subject, type: String
embeds_many :activities, class_name: 'LessonActivity' do
def ordered
#target.sort { |x, y| x.display_order <=> y.display_order }
end
def reorder!
#target.each_with_index { |val, index| val.display_order = index }
end
end
accepts_nested_attributes_for :activities
However I can't work out how I access this activities from within params.require(:lesson).permit :activities
I can access it via params.permit(:activities) but that feels a bit messy
I've done some digging and found out what's going on with this.
It all comes from a rails feature, the Param wrapper, details and api. Which configured for json will automatically pass the attributes of the model into a param of the model name (in this case Lesson).
The attributes of the model that will be populated based on how the model responds to the method attribute_names so this gives two routes to achieve the aims of the question.
1 - Instruct my controller to include activities as part of Lesson parameters, e.g. using this method:
class Api::LessonsController < Api::ApiController
wrap_parameters Lesson, include: Lesson.attribute_names << :activities
2 - Update the attiribute_names method for the model to include :activities
I'm still left with a couple of things to resolve, namely the reason associations aren't part of attribute_names on Mongoid and if overriding it to include attribute names is a bad idea.
Basing on the params you provided for your JSON POST request, you will need the following code to whitelist the params you need:
def activities_params
params.require(:activities).permit(:title, :display_order, :content, :time)
end
The params forwarded by your JSON POST request did not have the :activities hash as a value to the :lesson key so whitelisting the params you need is simple like above.
I think you may have answered you question here:
"how I can make it part of lessons key or why I can't. I'm not passing a lesson parameter "
If I read that correctly, you are not passing the lesson param, just a hash of Activities?
That would explain why you can access
params.permit(:activities)
but not
params.require(:lesson).permit :activities

Rails: Canned/RESTful way for accessing data returned by a method of a model

In my app I have a User model which defines a history method that returns a list of Activity objects, showing the last N actions the user has carried out. The UserController#history method wires this with a view.
The code looks as follows:
class UserController < ApplicationController
def history
user = User.find(params[:id])
#history = user.history(20)
end
end
class User < ActiveRecord::Base
has_many :activities
def history(limit)
...
end
end
Naturally, I also added this line to my routes.rb file:
match '/user/:id/:action', :controller => 'user'
so now when I go to localhost:3000/user/8/history I see the history of user 8. Everything works fine.
Being a Rails NOOB I was wondering whether there is some canned solution for this situation which can simplify the code. I mean, if /user/8 is the RESTful way for accessing the page of User 8, is it possible to tell Rails that /user/8/history should show the data returned by invoking history() on User 8?
First of all the convention to name controllers is in the plural form unless it is only for a single resource, for example a session.
About the routes I believe you used the resources "helper" in your routes, what you can do is specify that the resource routes to users also has a member action to get the history like this
resources :users do
member do
get :history
end
end
I think there is no cleaner way to do this
You can check it here http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
As far as the rails standards are concerned, it is the correct way to show the history in your case. In rails controllers are suppose to be middle-ware of views and model, so defining an action history seems good to me.
And you can specify the routes in better way as:
resources :user do
get 'history', :on => :member #it will generate users/:id/history as url.
end

Use variable other than :id in rails 3 routes

I'm trying to get my rails 3 app to use a route that looks something like:
exampleapp.com/patients/123456
rather than
exampleapp.com/patients/1
where "123456" would be associated with the patient's medical record number (:mrn), which is already in the :patients table and is a unique integer. I want to use the :mrn in place of the usual :id. How would I go about this?
Sorry if this has already been asked - I couldn't find the terminology as to what I'm trying to do is called. Thanks!
You could do this,
class Patient < ActiveRecord::Base
self.primary_key = "mrn"
end
However, this will change a bunch of other things. The to_params will use mrn. The controller will still use params["id"], but the value will be the mrn field. The Patient.find method will work on the mrn field, but not the id field. (You can user Patient.find_by_mrn and Patient.find_by_id which will work on their specified fields.) Also, all foreign keys will be to the mrn value.
You can edit the mrn field, and you will still have an id field (unless you turn it off), however, editing could be a pain because all the foreign keys will have to be corrected.
Alternatively, if you just want to change the URL, then in your config/routes.rb file instead of
resources :patient
use
match "/patients/:mrn" => "patients#show"
match "/patients/:mrn" => "patients#update", :via => :put
You could just add this to your Patients model
def class Patient < ActiveRecord::Base
self.primary_key = "mrn"
end
You can get per-resource identifier customization by redefining the member_scope and nested_scope methods on the Resource instance.
resources :patients do
#scope[:scope_level_resource].tap do |u|
def u.member_scope
"#{path}/:mrn"
end
def u.nested_scope
"#{path}/:#{singular}_mrn"
end
end
end

Resources