Rails: Nested paths view convention - ruby-on-rails

I'm going through the Rails "Getting Started" guide and noticed an interesting differentiation in the code for determining a link path.
For the parent model, article, we have:
<%= link_to 'Destroy', article_path(article),
method: :delete, data: { confirm: 'Are you sure?' } %>
And for the child model, comment, we have:
<%= link_to 'Destroy Comment', [comment.article, comment],
method: :delete, data: { confirm: 'Are you sure?' } %>
These seem to be very different formats for a link to a model in similar circumstances. Is there an explanation - technical or methodology-related? I'm especially curious why an array of 2 items is needed for comment.
Lastly, my inheritance instincts tell me the code should be article.comment instead of comment.article. Any reasoning behind this ordering?

Adding to #hashrocket answer, It all comes down to how Rails creates routes for nested resources. If run rake routes you would see this
article DELETE /articles/:id(.:format) articles#destroy
article_comment DELETE /articles/:article_id/comments/:id(.:format) comments#destroy
That means, a delete request for an article just takes :id as key/parameter, whereas the delete request for comment needs two, article_id(the :id of the article that particular comment belongs to) and the :id of the comment itself.
In simple terms, to delete an article, you just need its :id so it is article_path(article), whereas to delete a comment you need the :id(which serves for :article_id) of the article it belongs to and the :id of the comment,so it is [comment.article, comment]. You can also write it as article_comment_path(comment.article, comment)
I suggest you to read about nested resources to understand better

A little earlier in the guide, you can see they setup up some associations between an article and a comment. An article has many comments and a comment belongs to an article.
The reason there is an array for the deletion of the comment is because you have to know the article the comment belongs to and the comment itself because of those associations. Since comments belong to articles, we need the article id to find the comment we want to delete. That's why it's comment.article. We're finding the article the comment belongs to.
If you write article.comment, you're getting a comment of the article, not an article of the comment.

Related

Generating proper paths in namespaced Rails scaffolds

When you use rails generate scaffold admin/user --model-name=User or rails generate scaffold_controller --model-name=User it generates almost everything in a namespaced fashion. You get app/controllers/admin/users_controller.rb with your controller and app/views/admin/users/ filled with your views.
The one thing it doesn't get right is your paths. You have to manually go and replace references to user_path with admin_user_path and the like. This is pretty tedious.
Is there a way to tell Rails to generate the paths to point to your new namespace, rather than the namespace that the model is in?
Using Rails 4.
With rails build-in generators you can't.
See the generator source code to understand why:
<td><%%= link_to 'Show', <%= singular_table_name %> %></td>
<td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td>
<td><%%= link_to 'Destroy', <%= singular_table_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
As you can see, it goes with edit_<%= singular_table_name %>_path to generate the edit path, without considering name-spacing. (And haml-rails does the same)
The best thing to do, if you have time and patience for it, would be to fix this on the codebase and propose a PR. That's the main point of open-source after all.
If you go this direction, have a look first at open issues, I haven't dive deep into but it seems that different conversations are going on about that matter. Like https://github.com/rails/rails/pull/13927 or https://github.com/rails/rails/issues/21652
Or you can use existing gems like Beautiful-Scaffold that seem to be supporting namepacing

Method for getting polymorphic relationship table name in Rails. Meta-programming

I want to test if a Polymorphic child belongs to parent.
#event.eventable_id == #current_user.id and #event.eventable_type == current_user.class.name
Now this is ugly and shouldn't be in the view like this. But I'd like to do this check for every polymorphic object. So I need a meta-programming way to figure out the naming scheme of the polymorphic relationship table.
phone to phonable
social to sociable
etc.
The method would look great like this.
if #event.belongs_to(current_user) #<NoMethodError: undefined method `belongs_to' for #<Event:
So I need a helper method to check the polymorphic relationship. If I could get the name scheme of any polymorphic child like this
#event.polymorphic_relationship_table_name
I could then incorporate it in a meta-programming helper. If there's already a solution built for this please let me know.
I got it! This is the answer for getting the polymorphic relationship name scheme.
#event.class.reflect_on_all_associations(:belongs_to).first.name
=> :eventable # this is the ouput
And I can put any object instance in front of .class.reflect_on_all_associations(:belongs_to).first.name
The document that helped me the most on this was Identifying Foreign Key Dependencies from ActiveRecord::Base Classes by Ryan Stenberg.
:-)
Example Usage
First a helper method.
app/helpers/application_helper.rb
module ApplicationHelper
def child_and_parent?(obj, user)
obj_id = Integer(eval "obj.#{obj.class.reflect_on_all_associations(:belongs_to).first.name}_id")
obj_type = String(eval "obj.#{obj.class.reflect_on_all_associations(:belongs_to).first.name}_type")
(user.id == obj_id) and (user.class.name == obj_type)
end
end
And then use it in the view to remove features that don't belong to the current user.
app/views/events/index.html.erb and app/views/events/show.html.erb
<% if child_and_parent? #event, current_user %>
<li><%= link_to 'Edit', edit_event_path(#event) %></li>
<li><%= link_to 'Delete', #event, method: :delete, data: { confirm: 'Are you sure?' } %></li>
<% end %>
You should be able to access all that you want and need from the instance itself:
#event.eventable.class.table_name
#event.eventable.class.to_s
And so on ... when I tested this in a local app that has a few polymorphic models, I was able to access the parent data from those methods with ease, which you could integrate into your app in a much cleaner way than what I wrote :). Hope it helps!

How to make confirm message i18n compatible

I'm relatively new to Rails, and have been programming a few months.
I'm trying to use the t() method for internationalization, but it doesn't seem to work when I ask for confirmation in a link_to.
For example, when I write
<%= link_to t( ".delete_student_info"),
#student,
method: :delete,
confirm: "child_deletion_confirmation"
%>
...I predictably get a link_to that works and asks the confirmation question
However, when I write
<%= link_to t( ".delete_student_info"),
#student,
method: :delete,
confirm: t( ".child_deletion_confirmation")
%>
...I get the following output
Child Deletion Confirmation" data-method="delete" href="/en/student_profiles/41" rel="nofollow">Delete Student Info
Is there something conceptual that I am missing? I've looked in the Rails Guides Rails i18n API, but it doesn't address this issue. I'm thinking that maybe the confirm: is something different, but I don't know how to look it up. Any ideas?
I tried this out on my Rails 4 console and it worked fine:
helper.link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: I18n.t("date.formats.default") }
# => "<a data-confirm=\"%Y-%m-%d\" href=\"http://www.rubyonrails.org/\">Visit Other Site</a>"`.
Now, note the behavior when using nil for the :confirm is like what you're seeing:
helper.link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: nil }
# => "Visit Other Site"
So this makes me think that somehow your translation is evaluating to nil. However, I can't seem to figure out how to duplicate that issue...
I'll expand this answer to try to help more if you can show what the translations file looks like?

Abstract the delete link for a polymorphic model used in a nested controller

Question
In a polymorphic model, thats used in nested controllers, how can I abstract my delete link's path so I'm not hardcoding upload_permitted_user_path(#permissible, permitted_user)?
Details
I have a polymorphic model called permitted users. Basically theres a bunch of objects in my application where we need to control who can see it. So a post, photo, etc can have permitted users.
I want to be able to delete permitted users on the post#edit, photo#edit, etc pages.
I have this line:
# Used in "posts#edit"
<%= link_to 'Delete',
post_permitted_user_path(#permissible, permitted_user), # This should not be hardcoded.
method: :delete,
data: { confirm: 'Are you sure?' } %>
# Used in "photos#edit"
<%= link_to 'Delete',
photo_permitted_user_path(#permissible, permitted_user), # This should not be hardcoded.
method: :delete,
data: { confirm: 'Are you sure?' } %>
How can I abstract the path so I'm not hardcoding <MY_TOP_LEVEL_CLASS>_permitted_user_path(#permissible, permitted_user)?
Found the answer (feel free to repost and I'll accept :P)
Creating polymorphic links is easy using "polymorphic routes".
You can easily generate the proper link using polymorphic_url([#top_resource, #next_level_resource]) in any view.
For example:
polymorphic_url([:admin, #article, #comment]) becomes admin_article_comment_url(#article, #comment).
Another example without the leading :admin:
polymorphic_url([#article, #comment]) becomes article_comment_url(#article, #comment).

How to prevent a person from like a post more than once?

Please bear with me as I'm utterly confused, so my question may seem rather disorganized.
Ok, I have a blog that's created from rails guides: http://edgeguides.rubyonrails.org/getting_started.html
Amongst other tweaks, I added a column for likes for each post on index page. The likes was implemented from this tutorial: http://docs.railsbridge.org/intro-to-rails/hooking_up_votes_and_topics?back=voting_on_topics
So, right now I have four models: User, Article, Comment, and Like.
Right now, the way my likes column works is that when you like it, it increments its number.
However, I'm trying to prevent that happening past one like.
This is the method in ArticlesController
def like_vote
#article = Article.find(params[:id])
#article.likes.create
redirect_to(article_path)
end
I'm really confused on how to prevent a person from liking a post more than once.
As it is, right now the only columns in Like model are: "id", "article_id", "created_at", updated_at", user_id"
In case anyone wants to know, the column in Article model are: "id", "title", "text", "created_at", "updated_at", "user_id"
This is how the likes are displayed in Articles view.html.erb
<td><%= pluralize(article.likes.count, "like") %></td>
<td><%= button_to '+1', "/articles/#{article.id}/user/#{current_user.id}/like_vote", method: :post %></td>
And this is the route in the route's file
resources :articles do
member do
post '/user/:user_id/like_vote' => 'articles#like_vote'
end
end
Does this question make sense?
You may want to check this gem so you dont have to reinvent the wheel
https://github.com/ryanto/acts_as_votable
then you can just do
#user.likes #article
and to check for the vote
#user.voted_up_on? #article # => true
Even if you don't use the gem, its worth taking a look at its source as its comprehensive
You would have to create a table to track what users have liked what posts. Then if the user has a like entry for that post you would not allow it. By incrementing the count you don't have record of what user actually liked the post.
You can use a method like this
def likes_by_user(current_user_id)
Like.where('article_id = ? and user_id = ?', self.id, current_user_id).count
end
and then use a helper view like this:
<% if article.likes_by_user(current_user.id) > 0 %>
<td><%= button_to '+1', "/articles/#{article.id}/user/#{current_user.id}/like_vote", method: :post, disabled: true %></td>
<% else %>
<td><%= button_to '+1', "/articles/#{article.id}/user/#{current_user.id}/like_vote", method: :post %></td>
<%end%>
You should also prevent saving in the article controller create method to check the number of likes made by the current user on the particular article.

Resources