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.
Related
I am, for the first time, trying to use a HABTM relationship in my Rails application. The following image shows the models and their relations to each other:
Currently I am displaying all members within a region with the following:
<% #region.members.each do |member| %>
<%= link_to member.name, member %>
<% end %>
I am trying to sort/order the list of members by their respective level. I then would like to have the members ordered in descending alphabetical order.
So, the members_controller code is currently the default:
#members = Member.all
I can order the results into alphabetical order:
#members = Member.order("name DESC").all
but I can't figure out how to use a related model's data to order the member records. There are likely going to be three levels; Charity, Basic and Subscription. I am planning on having a block at the top of the page which only shows subscription members. The subscription members should not then be shown again on the page.
#subscribed_members = Member.where(:level == 1).order("name DESC").all
but, as you can see, I have no idea how to filter that block by level.
Can anyone point me in the right direction, or maybe to a tutorial for this? I've read through the Rails guide for HABTM relationships but it only seems to cover the models.
EDIT
After trying the suggestion, I now have the following:
#members = Member.all
#subscribedmembers = Member.include(:levels)
.where("levels.name == 'subscriber'")
How do I use that method in the regions view?
<% #region.subscribedmembers.each do |member| %>
<%= member.name %>
<% end %>
That won't work because it's looking for a related model called subscrivedmembers which doesn't exist.
For this kind of task, i recommend you to take a look at joining models with ActiveRecord. The methods are include and joins. Lemme demonstrate with a piece of code:
#subscribed_members = Member.include(:levels)
.where("members.url == 'test_url'")
.order("levels.name DESC")
If you check your console after this query is run, you can see the include makes a SQL join with the two tables. Here i am assuming member :has_many :levels, so you include the :levels table and use a prefixed column name on your order clause.
It is not that hard once you get the idea, so i encourage you to try these two methods on the console and check the results.
EDIT
Create a scope with you query code, then use it on any place. Example:
# On your model
scope :ultimate_level, include(:levels).where("name = 'test'").order("levels.name DESC")
# On your controller
#subscribed = Member.ultimate_level
# On your view
<% #subscribed.each do |s| %>
You can create different scopes too, each one that makes a single operation, and then reuse on our controllers, etc.
Obs.: Check the syntax, i didn't test the code myself.
I have a Rails app that pulls in music from Soundcloud. This data contains a title, which I save as mix.sc_title but it's not always properly formatted. I have added an additional attribute on my Mix model which I call mix.override_title
For display on my site, I want to use the override title if available, and the sc_title in all other cases.
I have a Mix model method to do this for me
def display_title
override_title.blank? sc_title : override_title
end
Mixes#index grabs #mixes = Mix.where(:active => true) and mixes/index.html.erb looks like this:
<ul>
<% #mixes.each do |mix| %>
<li><%= link_to mix.display_title, mix %></li>
<% end %>
</ul>
As you can see, I'm not directly using any mix attributes, and so I take a huge hit when I go to the DB, and I don't actually benefit from it.
Is there a leaner way to get just the information I need? (mix.display_title)
I have tried Mix.select("display_title").where(:active => true) but it fails because display_title is not a real DB column
You can do Mix.select("sc_title, override_title").where(:active => true) and it will work, since those are the actual fields that the method uses. I don't really think getting the additional attributes gives you that much of a DB hit but sometimes selecting only what you need can be beneficial.
As you start chaining on more Arel commands, consider putting the select into a model method:
def select_active_titles
select("sc_title, override_title").where(:active => true)
end
Edit: Your link_to helper also secretly calls mix.id to link to the right mix, so make sure it's working and if not add id to the list of selected attributes.
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
I search for a working solution for a rather simple problem, but could not find a good explanation.
What I currently have (working) is an index view which contains:
a form to enter a new element and
a paginated list of existing elements (using will_paginate).
For the list I am interested in only part of the data, thus I am trying to add a form with filter options and I would like to store the forms content in a cookie (which should be replaced with an per user object stored in the database, but i do not have users yet). What I cannot figure out is how to get the values from the form stored in a cookie (and vice versa) and how to use it together with will_paginated.
What I currently tried to do as a first step is to create an #filter object in my controller and adding the filter form for this object, setting the form options to use the index controller again. This leads to selected filter parameters passed in the params hash to the index controller (visible in the url). But this solution has some drawbacks. first the filters are gone as soon as I change the view (e.g. by creating a new element) and second the #filter object should be the cookie instead.
Here is the code I have so far:
View-partial for filter:
<%= form_for(#filter, :url => {:action => "index"}, :html => {:method => :get}) do |f| %>
<div class="field">
<%= f.label :german %><br />
<%= f.check_box :german %>
</div>
<div class="actions">
<%= f.submit "Filter" %>
</div>
<% end %>
Controller:
def index
#word = Word.new
#filter = Word.new(params[:word])
#words = Word.paginate(:page => params[:page]).order('word')
# ....
Can anybody help me? How is such a functionality (filtering results) done in other applications?
So the answer to the question is, use a where clause to include only the matching records in your result.
#words = Word
.where("german = ?", params[:word][:german] != 0")
.order('word')
.paginate(:page => params[:page])
This is a new Rails syntax called Active Relation (AREL for short), which should generally replace the older find and find_by methods. It has several benefits that can improve performance, notably that the SQL it executes (which you can see in your logs) only occurs when it is referenced, not when it is declared. This give you neat ways of defining partial relations (even as named scopes) that you can build up to create simpler statements that combine together.
The order of the various clauses doesn't matter -- AREL will generate the same SQL, but generally I like to follow the order of the underlying SQL,
where
joins
group
order
limit
offset
(limit and offset are handled in your case by the pagination tool).
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)