How to access request parameters in Rails - ruby-on-rails-3

I am new to Rails, and I cannot figure out how to handle the :sort by portion of the code, from a haml view, below:
%th{:class=> title_header}= link_to 'Movie Title', movies_path( :sort_by => 'title' )
Do I catch this as some sort of parameter in my def index in the controller? Or does this require a complete new view?
When I click on the header, it goes to
http://0.0.0.0:3000/movies?sort_by=title, which seems to be the same view as before. Somehow I need to wire it up to a method that sorts that column, but for the life of me, I can't figure out where this should happen.

The parameter you seek is in the params object
def index
sort = params[:sort_by]
#movies = Movie.find(...) # TODO: get your movies
if sort # if sorting is specified
# TODO: sort #movies here
end
end

Related

multiple attributes in a single view phoenix

I want to have a single view but want to render different attributes depending upon the json-api that calls it. For example: the index page does not require all the attributes, so there is no need to call all the attributes for it, while show page needs all the attributes, so it should get all the attributes.
I basically want something like:
attributes [:id, :name, :email, :website, :inserted_at, :updated_at]
for show and
attributes [:id, :name, :email]
for index.
I am using ja-serializer for this, so would like a solution in that only.
PS: I can have two different views with corresponding attributes but that seems like a workaround also doesn't make much sense.
Thanks in advance.
It would help if you post some code. But you can do this within the view. You can define separate render function heads for each controller action.
For example:
def render("index.json", %{store: store}) do
%{data: %{store: %{id: store.sap_id, name: store.name}}}
end
def render("show.json", %{store: store}) do
%{data: %{store: %{id: store.sap_id, name: store.name, other: store.other}}}
end
Above, you have a render function head for each action: index and show. The function body then builds the correct JSON response. You can use this approach to customise the attributes you want to return.
There is an optional parameter in render method call. We can specify the fields in "fields" and keep the view as it is.
def index(conn, params) do
render("index.json-api", data: data, opts: [fields: %{"table_name"=> "fields,in,csv,style"}])
end
For more, refer to Conditional attribute inclusion in
https://hexdocs.pm/ja_serializer/JaSerializer.DSL.html

Refactoring Ruby on Rails link_to with sorting

I'm very new to Rails, Databases, and web development in general, right now my feature is working but I'm 100% sure it's not the cleanest or most Ruby-esq way of doing it. Basically I want to use two separate links that sort the same table by different columns, without using two controller actions and two views. I'd rather just user the one index action that takes a parameter indicating how to sort the returned data.
Current Controller:
Controller
def index
#casinos = Casino.order('name ASC')
end
def casinos_by_region
#casinos = Casino.order('location ASC')
end
And links in the view
%h3 Sort by:
= link_to 'Name', casinos_path
%br
= link_to 'Location', casinos_by_region_path
I read the docs but I didn't see an obvious way on passing an argument from the view to controller using a link_to path? I know I could do it other ways, but I refuse to believe I can't do it this way. Sorry for the dumb question!
Thumbs up to CDub. Just to enhance it, you might want to add a little safety to the sorting by assuring the params[:sort_param] contains an expected value in case someone decides to key in the url. The code below not only assures you've got an acceptable sorting key but also provides a default value for the first visit to the url.
def index
params[:sort_param] = %w{name location}.include?(params[:sort_param]) ? params[:sort_param] : 'name'
#casinos = Casino.order "#{params[:sort_param]} ASC"
end
How about this:
def index
#casinos = Casino.order("#{params[:sort_param]} ASC")
end
%h3 Sort by:
= link_to 'Name', casinos_path(:sort_param => "name")
%br
= link_to 'Location', casinos_path(:sort_param => "location")
The path in link_to can take a hash which are parameters on the request. You can set a parameter (in this case sort_param) with what value you want to sort by, then use that in your order on the index method of the controller.

Why doesn't CanCan respect my rule?

I need to restrict users to only access their shipping list items by 'supplier_id '. I know I can use 'accessible_by' to add a 'where' clause. But if I forget to add the accessible_by scope to my query, in the following controller, in action index, the user can read any list.
class SupplierShippingListsController < ApplicationController
load_and_authorize_resource
def index
#shipping_lists = SupplierShippingList.where(:supplier_id => params[:supplier_id])
authorize! :read, SupplierShippingList
end
end
I defined this rule first, hopping the instance would be checked against my hash but it doesn't, as if :supplier_id was pointless.
can :read, SupplierShippingList, :supplier_id => user.company_id
So I tried a block with a random value to make it fail the authorization, but the user can still access the action.
can :read, SupplierShippingList do |list|
list.supplier_id == 17
end
Is there some hidden thing to set?
EDIT
I also tried
authorize! :read, #shipping_lists
with no avail.
Index actions will ignore the block, so your second example will not work. Your best bet for relatively simple abilities like this is to use a hash of conditions like your first example. But load_and_authorize_resource will automatically load #supplier_shipping_lists for you. Your index action as shown will overwrite the variable and make the 'load' portion of 'load and authorize resource' pointless. You should remove your index action and let CanCan do the loading for you. If you don't override the instance variable, you should see what you expect as long as you use the hash of conditions.
Also, you don't need to manually call authorize! either. That is the 'authorize' part of load_and_authorize_resource. You need to just let CanCan do the work. When you enter your index action, the instance variable will either already be initialized, but will be empty if you don't have the authority to view any lists.
Since you're using load_and_authorize_resource you don't have to do anything. In your index action it should find all of the supplier_shipping_lists that correspond to the user company if you're using the can :read, SupplierShippingList, :supplier_id => user.company_id. If that isn't working, do you have any other rules being applied to the SupplierShippingList?

Nested resources, list siblings on the same page as 'create' form

I have a nested resource, not dis-similar from the 'post' / 'comments' relationship you'd find on a blog.
On my #show event for the post, I display a form for a new comment and below it are all the previous comments for that post.
My #show method within the controller looks something like this:
def show
#post = Post.find(params[:id])
#comment = #post.comments.build
Now, the problem with this is that I now have an empty, unsaved entity inside my #page.comments collection. As a result within my view when I'm listing the comments and I do something like.
<%= comment.created_at.strftime("%a, %d %b %Y %H:%M:%S") %>
I get an exception thrown:
undefined method `strftime' for nil:NilClass
I have tried to rectify this by switching my #show controller so that it doesn't scope the new comment through the post object but I still run back into the same problem if I'm redirected back to the form with a validation error.
Can anyone offer some advice?
I have had this same problem. There doesn't seem to be a legit way to build the child object through the collection without actually adding it to the collection.
So instead of:
#post = Post.find(params[:id])
#comment = #post.comments.build
I do:
#post = Post.find(params[:id])
#comment = Comment.new(:post => #post)
It builds the child object in the same way, but doesn't add it to the parent collection so your list builds without error. It's not how I would like to do it, but it appears to be the best way to accomplish the same thing with the minimum amount of change to the workflow.

REST path for "new from copy"

For certain models, I wish to provide functionality that allows a user to create a new record with default attributes based on copy of an existing record.
I'm wondering what would be the correct restful route for this.
My initial thinking is that it could be a parameter to the new action. I.e. to borrow from the the Rails Guides examples, instead of just:
GET : /photos/new
Also allow:
GET : /photos/new/:id
...where :id is the id of the record to use as a template. The response would be a new/edit form, same as with a plain old new but the values would be pre-filled with data from the existing record. The parameter (or absense of it) could be easily handled by the new controller method.
The alternative seems to be to create a new controller method, for example copy which would also accept an id of an existing record and response with the new form as above. This seems a little 'incorrect' to me, as the record is not actually being copied until the user saves the new record (after probably editig it somewhat).
TIA...
UPDATE: my question is not "how do I do this in rails?", it's "is it RESTful?"
my question is not "how do I do this in rails?", it's "is it RESTful?"
No, it isn't. For that matter, neither is GET /photos/new. Rails seems to be hopelessly mired in the past, where it was considered haute programme for a GET on a URI to return an HTML form which would then POST x-www-form-urlencoded data back to that same URI. The opacity of that POST forces them to invent new verbs-as-URI's like /photos/new, when you could be using PUT instead, or at least POST with the same media type.
The simplest way to make a copy of an HTTP resource RESTfully is:
GET /photos/{id}/ -> [representation of a photo resource]
...make modifications to that representation as desired...
POST /photos/ <- [modified representation]
If you're implementing this for browsers, you should be able to perform those actions via Ajax quite easily, using an HTML page sitting perhaps at /photos/manager.html/ to drive the interaction with the user.
You can try to use nested resources. I'm not exactly sure about structure of you application, but in general using nested photos will look somehow like this:
routes.rb
resources :photos do
resources :photos
end
photos_controller.rb
before_filter :find_parent_photo, :only => [:new, :create]
def create
#photo = Photo.new params[:photo]
if #parent_photo.present?
# fill some #photo fields from #parent_photo
end
#photo.save
respond_with #photo
end
def find_parent_photo
#parent_photo = Photo.find(params[:photo_id]) if params[:photo_id].present?
end
new.html.haml
= form_for [#parent_photo, #photo] do |f|
-# your form code
previously when you wanted to add a link to photo creation you wrote something like that
= link_to "new photo", [:new, :photo]
now if you want to add a link to photo creation based on foto #photo1
= link_to "new photo based on other one", [:new, #photo1, :photo]
You should be able to match a route like so:
match 'photos/new/:photo_id' => 'photos#new
or you could just pass a :photo_id parameter in the url and handle it in the controller:
'/photos/new?photo_id=17'
Example using helper method: new_photo_path(:photo_id => 17)
Edit: I don't know if this conforms to REST
It may be over the top, but you could do something like this:
class PhotoCopiesController < ApplicationController
def new
#photo = Photo.find(params[:photo_id]).dup
end
def create
end
end
and
resources :photo_copies, :only => [:new, :create]
and
= link_to 'Copy', photo_copy_path(:photo_id => #photo.id)