Should my URL's in Rails look like:
http://foobar.com/articles?category=recent
- OR -
http://foobar.com/articles/recent
I find the former to be more RESTFUL, but the latter to be more cleaner (code-wise). For example, the code can look like:
In Article controller:
def index
if params[:category] == 'recent'
#articles = Article.by_recent
elsif params[:category] == 'expired'
#articles = Article.by_expired
end
end
In Article, index view:
<% unless #articles.blank? %>
<ul>
<% #articles.each do |article| %>
<li><%= article.title %></li>
<li><%= article.content %></li>
<% end %>
</ul>
<% end %>
With http://foobar.com/articles/recent, I would have to customize my routes. Something like:
match 'articles/:category' => 'articles#index'
Above will access the Article controller, index action. Or even:
resources :articles do
collection do
get 'recent'
end
end
Which will allow for urls like http://foobar.com/articles/recent, but needs a 'recent' action in the Article controller.
Both seem pretty useful, in the end of the day. But which is the general consensus? Using the query string approach (http://foobar.com/articles?category=recent) or the other way (http://foobar.com/articles/recent).
Looking forward to your suggestions.
Obviously like you said you could go either way, but since something like "recent" is a condition of or query against articles I would go with the former. If it were a nested attribute and its own model the second approach is arguably better, but since it isnt you really lose a lot of flexibility to change and refactor the approach down the road.
For instance if you wanted to do "old" as opposed to "recent" you would not be able to simply pass in an "old" parameter, but would have to hardcode that with its own action and views in the same controller as "recent" which is of course a lot more work for nothing.
In my opinion articles/recent is much cleaner as it avoids filling your controller action with conditionals and also looks nicer from a user perspective.
Related
this seems like a simple question but I can't find the answer. I have books in my Rails app, and a scope for featured books. I want an endpoint that lists all books, and an endpoint that lists just the featured books.
I have one API Books Controller with one index action, so site.com/api/books will return all books. How do i make the second endpoint that will return just featured books? is there another way to query, like /books?featured=true ?
thanks.
You have all sorts of options here, but perhaps the best would be to define a route like this:
get '/books(/:featured)' => 'controller#action'
Then, in your controller, something like:
#books = params[:featured].present? ? Book.featured : Book.all
That's just quick-and-dirty, but certainly does the trick.
untested but should work.
routes.rb
get "/books(/:type)" => "books#index", as: :books
books_controller.rb
// kinda of flexi situation, you just throw in a "filter" you want to look for. you throw in "featured" and it will look for "featured=>true". if you throw in "published" it looks for "published=>true"
def index
if params[:type].present?
#books = Book.where(params[:type].to_sym => true)
else
#books = Book.all
end
end
index.html.erb
<% =link_to("all books", books_path) %>
<% =link_to("feature_books", books_path(:featured)) %>
<% =link_to("other scoped books", books_path(:other_scope)) %>
In my Rails app, I have the following association:
Video belongs to Genre (Video does not HAVE to have a genre)
Genre has many Videos (Genre can have no videos)
In the Video model, I have the following method.
# models/video.rb
def genre_name
genre.present? ? genre.name : ''
end
This is to avoid something like this in the view (which just seems messy):
# views/videos/show.html.erb
<% if #video.genre.present? %>
<%= #video.genre.name %>
<% else %>
No Genre Present
<% end %>
Instead, I can just do this (which looks much tidier)
# views/videos/show.html.erb
<%= #video.genre_name %>
However, it doesn't feel right asking for information about the genre in the Video model. What's the best way to organise this code? Should I be using helpers instead?
If you find yourself doing this kind of thing a lot, it may be worth looking into a decorator pattern, which can house view logic like this. (I quite enjoy using Draper for this purpose, but it's not very difficult to roll your own naive implementation.)
Then, your decorator logic can look like this:
class VideoDecorator
def genre_name
object.genre.try(:name).presence || "Fallback"
end
end
You can wrap up the model in a decorator as you render in the controller:
#video = Video.find(params[:id])
respond_with #video.decorate
And your view 'logic' (or lack thereof) can look like this application-wide:
<%= #video.genre_name %>
Thoughtbot has an excellent explanation of the decorator pattern here
You could write in your view
<%= #video.genre.try(:name) || 'No Genre Present' %>
If you don't need the fallback text, just
<%= #video.genre.try(:name) %>
Read more about Object#try here.
If you want the fallback also when name is an empty string (not just nil) you can use Object#presence
<%= #video.genre.try(:name).presence || 'No Genre Present' %>
I have a view that is getting complicated, and I'm wondering I should be doing this different? Picture (or code) is worth a 1000 words, so heres the view...
<% #orientation_by_date[date].each do |orientation| %>
<% if current_user %>
<% if orientation.active? %>
<li><%= link_to orientation.class_time, new_orientation_registration_path(orientation) %>
(<%= orientation.current_number_seats %>/<%= orientation.seats %>)</li>
<% else %>
<li><%= orientation.class_time %>(Class full)</li>
<% end %>
<%= link_to "VIEW", orientation_registrations_path(orientation) %></li>
<% else %>
<% if orientation.active? %>
<li><%= link_to orientation.class_time, new_orientation_registration_path(orientation) %>
(<%= orientation.current_number_seats %>/<%= orientation.seats %>)</li>
<% elsif orientation.class_date.before Date.today %>
<li><%= orientation.class_time %>(Class Closed)</li>
<% end %>
<% else %>
<li><%= orientation.class_time %>(Class full)</li>
<% end %>
<% end %>
<% end %>
What you are looking at is a the front end calendar view of a scheduling application. Based on differnt states, you see different information in each day on the calendar, ie, the number of seats remaining, vs. 'Class Full' vs. something else for Admins. Should I be pulling this logic into my model or controller somehow?
There are lots of ways to skin the cat. Which is 'right' is as much about personal preferences as anything else. That said, here are a few ideas that you might want to consider.
Use partials for each type of user
This may or may not be your driving concern but the outermost layer of decision making is based on user type so it may make sense to build a partial for each type of user. In this case you might have 'active_user_orientation_view' and 'guest_orientation_view'. Doing that reduces (this section) of your view down to a single if-then-else statement with pretty clear indication of your intent -- registered users see one thing and guests see something else.
Wrap-up repeating code into helper methods
Two of the list items are generated using the exact same code. Make it DRY! As an example, I'd probably drop down into the OrientationsHelper (app/helpers/orientations_helper.rb) and add a #orientation_full_item helper like this
def orientation_full_item(orientation)
content_tag(:li) do
"#{orientation.class_time} (Class full)"
end
end
With that helper in place, the two lines rendering the "Class full" message could be reduced to <%= orientation_full_item(orientation) %>. You could do the same for the list item that provides a link to the registration form. For consistency, you might do it for all of the list items. That would give you a view that very clearly declares its intentions.
Consider using a Presenter
Rather than litter your model (business logic) with view-oriented convenience methods, a better choice would be to create a new class that accepts an instance of the class and provides the same convenience methods. This is what the Presenter pattern is all about. The advantage of it is that you very clearly organize your code along the lines of it's intention -- biz logic stays together and stays untangled from view logic. In this case you might provide an ActiveUserOrientationPresenter and a GuestOrientationPresenter class, each of which provides a #list_item convenience method capable of rendering out the list item with its appropriate contents.
The PragProg guys have a title written by Bruce Williams with some great suggestions on how to build robust view code that is probably worth the money and time invested. One of the available code snippets deals specifically with presenters. You can read it http://media.pragprog.com/titles/warv/present.pdf.
Write unit tests that nail down the contents of all those <li> items with XPath.
Grab Nokogiri, and use Nokogiri::HTML::Builder to write all that in Ruby:
builder = Nokogiri::HTML::Builder.new do |doc|
doc.ul {
doc.li('data 1')
doc.li('data 2') if oodles_of_poodles?
doc.li('data 3')
}
end
puts builder.to_html
Now that it's all in one language, you can refactor it freely without constantly tripping over the escape tokens needed to mix two languages together.
I'm trying to build a condition based on wether or not a "user" is a "member". Basically I need a way of checking if the current_user.id matches any of the user_id of any members. The non-working code I have right now is:
<% if current_user = #page.members %>
you can view this content.
<% end %>
I'm looking for something along the lines of: "If current_user.id exists in the "user_id" of any members."
Something like this, based on the field names in your question:
<% if #page.members.map(&:user_id).include? current_user.id %>
You can view this content
<% end %>
Assuming your #page.members variable contains an array, you can use the include? method:
<% if #page.members.include? current_user %>
you can view this content.
<% end %>
If you're using an array of ids, you will of course need to change the test slightly to look for the current user's id:
<% if #page.members.include? current_user.id %>
you can view this content.
<% end %>
#member_ids = #page.members.map{|m| m.id()}
then check for the condition as below
#memeber_ids.include? current_user.id()
Has said before include? should do the thing.
I'm just answering to tell you about a gem called CanCan, that gives you easy access for authorization "helpers".
Why you should use CanCan instead of doing what you are actually doing?
Don't reinventing the weel most of the times it's a goob practice.
You are placing business logic on the view (bad practice).
CanCan most likely has been developed thinking on security, and all the best practices in mind.
You save some developing hours.
Sorry if I repeated myself.
I have a piece of code which displays a complicated 'listing' of resources. by complicated i mean that it has name, date, time posted, number of comments, picture, description, and tons more things that you obviously only want to write that block of code one time.
I'm trying to think of how this would go into the rails architecture and I'm at a loss
i need to display the resource listing using a different variable in different controllers. currently its in a partial called _resource_view.html.erb
This is fine for the main listing, but its not usable for the persons profile listing, which takes the same format but shows different resources
_resources_expanded.html.erb
<ul class="res-list">
<% #resources.each do |resource| %>
... list stuff
<% end %>
</ul>
It uses #resources on index, but on the profile page it uses the same listing code except its #user.resources.each do |resource|
I was thinking maybe something like this but this seems redundant .. and im not even sure if it will work
<ul class="res-list">
<% #resources.each do |resource| %>
<%= render 'layouts/resources_expanded' %>
<% end %>
</ul>
Don't use instance variables, use local variables in the partial - that lets you pass in a single each time through the #resources.each loop.
<%= render 'layouts/resources_expanded', :resource => resource %>
and then in _resource_view.html.erb change #resource to resource