how does devise current_user work? - ruby-on-rails

I want to understand how the devise current_user method works because I want to generalize it to other models which would allow code such as current_forum or current_forum_thread.
To be more specific I am trying to implement a chat forum in Rails. I have a page showing all posts (currently none) for a specific discussion thread. That same page has the new post form embedded. The debug(params) shows:
action: show
controller: discussions
forum_id: '1'
id: '1'
discussion: !ruby/object:Discussion
attributes:
id: 1
title: first discussion (thread)
forum_id: 1
So the create method in the posts controller should know what the discussion id is. Yet this code in the controller does not work.
1. #discussion = Discussion.find(params[:id])
2. #post = #discussion.posts.new(params[:post])
3. if #post.save
4. flash[:success] = "Discussion post created!"
5. redirect_to '#'
6. else
7. render '#'
8. end
Line 1. raises the error:
Couldn't find Discussion without an ID
Also, on inspection it turns out that the #discussion variable is always NIL.

I think it's more of a helper function, devise's way is to get the ID through the session, but you could do the same through the params hash,
i.e
module ApplicationHelper
def current_forum_thread
Thread.find(params[:id])
end
end
does that work for you?

I'm putting before_filter :authenticate_user! on top of every controller, and then do something like this:
current_user.posts.new(params)
This also requires relation User has_many :posts
Seems to work (not sure if that's the best way though).
Also, your error seems to mean that your prarms[:id] is nil, so check if it's passing properly. You should be able to see that in the logs.
# Discussions controller - show action
#discussion = Discussion.find(params[:id])
render ...
# Discussion show view
%ul
- #discussion.posts.each do |post|
%li= post.content # to output list of posts
= form_for #discussion.posts.new do |f|
= f.input :content
= f.submit
# form to create new post related to this discussion
# Post controller - create method
#post = Post.new(params[:id])
#post.save!
render ...

Using the current_id with threads seems overly complex for this implementation, as it looks like a pretty simple nested resource.
The post isn't saving, because it couldn't find the discussion. Since you're on the Post controller and not Discussion, you need to be looking for the discussion with
#discussion = Discussion.find(params[:discussion_id])
The :id your searching for it with is from the post's parameters. It didn't find anything because you probably have significantly more posts that discussions. If it did find something it would be finding the wrong thing.
Another things on the check list to make a nested route work, is to get the routes right. Check them with 'rake routes' but it should look like this:
resources #discussions do
resources #posts
end
This will add the routes so your form, which should look something like <%= form_for [#discussion, #post] do |f| %> can post/put to the discussion_posts_path.
Using the current_id like you got into is really scoping, and it's kind of messy, Ryan Bate's has an awesome video on Multi-tenancy with scopes http://railscasts.com/episodes/388-multitenancy-with-scopes

Related

Creating multiple forms without flooding the controller with new objects

How do I create multiple forms without having to create the object in the controller/load them by ajax?
Say you have a Forum model that has many posts, and each post has many tags. You are looking at a list of posts at forms_controller#index.
Say you want to allow the user to tag posts from the same template. One way is to load the form via a remote link when needed.
# forums/index.html
= link_to "Add Tag", new_post_tag_path, remote: true
# tags_controller.rb
def new
#tag = #post.tags.build
respond_to do |format|
format.js
end
end
# tags/new.js.erb
<%= j render("form") %>
But that requires going to the server and back again to render the form.
Another way is to create the object when looping over the posts.
- #posts.each do |post|
= form_for #post.tags.build do
That doesn't work very well if you want to create several tags at the same time.
Is there another way to do this, perhaps with JS, without having to go to the server/or create the object as in the second approach?
Ryan Bates has done a great screencast on this subject. http://railscasts.com/episodes/197-nested-model-form-part-2 Hopefully this should provide you with what you need.

link_to custom action but wrong method?

all, I'm trying to get a custom action to work with a put method: in the
in _post.html.erb i have a link_to statement:
<%= link_to 'End now', post, :method => :put, :action => endnow %>
routes.rb contains:
resources :posts do
member do
put :endnow
end
and posts_controller.rb looks like:
class PostsController < ApplicationController
helper_method :endnow
[.. code for create, edit, destroy, etc ..]
def endnow
puts params
end
end
rake routes's relevant line looks like:
endnow_post PUT /posts/:id/endnow(.:format) posts#endnow
However, the action endnow helper doesn't run when clicking on this link.
Strangely, it does run with an index action (which i can tell from the puts command.
Of course, eventually the code for endnow will update #post, but for now, it just doesn't run properly.
Maybe i'm going about this the wrong way - all I'm trying to achieve is to update #post upon clicking the link to that post, and before showing it.
Any ideas / Alternatives?
Why not use the route helper method provided to you? Change your link to
<%= link_to 'End now', endnow_post_path(#post), method: :put %>
Things you're doing wrong:
If you want to specify the :action, use the Symbol for the action (you're missing a colon). :action => endnow should be action: :endnow
I will assume you have a #post instance variable you're passing from your controller to your action. You should be using that instead of post (unless you do in fact have a local post variable you're omitting from your code)
You are using endnow as an action; you should remove the helper_method :endnow line in your controller because it's not something you want to/should be accessing from your view.
This can all be avoided by using the route helper (for endnow_post you'd append _path to get the local route path: endnow_post_path), and pass in your #post as an argument.
Because you're trying to do a PUT request, you must make sure you have something like jquery-ujs included in your asset pipeline to convert these links to form submissions behind the scenes; browsers don't support PUT via the click of a link on their own.
As for why you're getting the template error when you get your link_to working, Rails is telling you that you need to create a app/views/posts/endnow.html.erb file. Your action has only puts params which does not terminate execution, leaving Rails to assume you still are trying to render some endnow.html.erb template.
Are there other ways to do what you're trying to do (change a single attribute of a specific model)? Sure. Are there better ways? That's pretty subjective; it may not be the most RESTful way, but it's arguably easier to deal with (if for example there are very specific authorization rules to check before updating the attribute you are modifying in endnow. Does the way you've started fleshing out work? Absolutely.
Finally, as a bump in the right direction, after you fix your link_to and remove the the helper_method as I have described above, your endnow action might look like this:
def endnow
post = Post.find!(params[:id])
post.some_attribute_here = some_new_value_here
post.save
redirect_to :root and return # <- this line sets a redirect back to your homepage and terminates execution, telling rails to do the redirect and **not** to render some endnow.html.erb file
end

Create a link to a defined method?

As my first Rails app, I'm trying to put together a simple blog application where users can vote on posts. I generated the Blogpost scaffold with a integer column (entitled "upvote") for keeping track of the vote count.
In the Blogpost model, I created a function:
def self.voteup
blogpost.upvote += 1
end
On the Blogpost index view, I'd like to create a link that does something like:
link_to "Vote up" self.voteup
But this doesn't seem to work. Is it possible to create a link to a method? If not, can you point me in the right direction to accomplish this?
What you are trying to do goes against the MVC design principles. You should do the upvoting inside a controller action. You should probably create a controller action called upvote. And pass in the post id to it. Inside the controller action you can retrive the post with the passed in ID and upvote it.
if you need serious voting in your rails app you can take a look at these gems
I assume that you need to increment upvote column in blogspots table. Redirection to a method is controllers job and we can give links to controller methods only. You can create a method in Blogposts controller like this:
def upvote_blog
blogpost = Blogpost.find(params[:id])
blogpost.upvote += 1
blogpost.save
redirect_to blogpost_path
end
In your index page,
<% #blogposts.each do |blogpost| %>
...
<%= link_to "Vote up", :action => upvote_blog, :id => blogpost.id %>
...
<% end %>
You can not map Model method to link_to in view. you can create an action in controller to access the Model method and map it using link_to, also if the action is other than CRUD, then you should define a route for the same in route.rb

Newbie rails database relation question

I have two models Station and Broadcast. Broadcast belongs_to Station and has a station_id column.
I don't know how to make new method in BroadcastController to expect the station_id value and how to create a new Broadcast with right station_id in it.
I'd recommend against using a link_to here. Using a 'get' action to change/add data on the server is generally frowned upon. Also, I wouldn't use the 'new' action, as it's used by convention in a Rails Restful route to present a form, not actually persist data. All that said, the simple answer to your question is to pass a value through the link_to helper like so:
link_to 'Add broadcast', new_broadcast_path(:station_id => station.id)
...and your 'new' method on BroadcastsController would do:
def new
#broadcast = BroadCast.new(:station_id => params[:station_id])
if #broadcast.save
flash[:notice] = "New broadcast created!"
redirect :back # or whatever
else
# etc.
end
end
But, again, this is not the right way to do this. What you probably want to do is stay within the Rails (and web) conventions and use a form to create the new broadcast record by way of the 'create' action on the controller. You might place this form next to your stations on the index view which presents a button that points to the correct 'create' action of BroadcastsController, and uses a hidden_field to set the station_id. Something like (EDIT: better use of hidden_field):
<% form_for :broadcast do |f| %>
<%= f.hidden_field :station_id, :value=> station.id %>
<%= submit_tag 'Add broadcast' %>
<% end %>
Assuming you've set a restful route in routes.rb for broadcast like:
map.resources :broadcasts
...then your form will automatically point to a 'create' action on BroadcastsController, which you should write to look something like:
def create
#broadcast = BroadCast.new(params[:broadcast])
if #broadcast.save
# etc., etc.
end
The Rails Guides are a good place to get more comfortable with the Rails controller actions. I'd also spend some time looking over the RESTful actions and how they're used in Rails as well.
You actually do specify a station_id for your Broadcast model, such as
script/generate scaffold broadcast name:string station_id:integer ...
so when you add a broadcast record, it will ask you for a station_id.

Rails - link_to, routes and nested resources

As my understanding on nested resources, on edge Rails, should not
link_to 'User posts', #user.posts
point to
/users/:id/posts
?
The routes.rb file contains
map.resources :users, :has_many => :posts
If this is not the default behavior, can it be accomplished doing something else?
Along the same lines as Rishav:
link_to "User Posts", [#user, :posts]
Here's an explanation from my blog.
Really early on in Rails, you would write routes like this:
redirect_to :controller => "posts", :action => "show", :id => #post.id
What this would do is dutifully redirect to the show action inside the PostsController and pass along the id parameter with a
value of whatever #post.id returns. Typical 302 response.
Then Rails 1.2 came along and allowed you to use routing helpers, like this:
redirect_to post_path(#post)
And the people rejoiced.
This would do effectively the same thing. post_path here would build a route using the #post object that would look something
like /posts/1 and then redirect_to would send back a 302 response to that route and the browser would follow it.
Then later versions (I can't remember which one), allowed syntax like this:
redirect_to #post
And the people rejoiced a second time.
Magic, but not really
Any sufficiently advanced technology is indistinguishable from magic.
While this seems like magic, it's not. What this is doing is actually very, very neat. The redirect_to method, much like its cousins link_to and form_for all use a common method to build URLs, called url_for. The url_for method takes many different
varieties of objects, such as strings, hashes or even instances of models, like in the example above.
What it does with these objects then, is quite neat. In the case of the redirect_to #post call above, it inspects the #post
object, sees that it is an object of the Post class (we assume, anyway) and checks to see if that object has been persisted in a
database somewhere by calling persisted? on it.
By "persisted", I mean that a Ruby object has a matching record in the database somewhere. The persisted? method in Active Record is implemented like this:
def persisted?
!(new_record? || destroyed?)
end
If the object wasn't created through a call such as Model.new then it won't be a new record, and if it hasn't had the destroy method called on it won't be
destroyed either. If both of these cases are true, then that makes the object has most likely been persisted to the database in the form of a record.
If it has been persisted, then url_for knows that this object can be found
somewhere, and that the place it can be found is most likely under a method called post_path. So it calls this method, and passes
in the to_param value of this object which is usually the id.
In short, it's effectively doing this:
#{#post.class.downcase}_path(#post.to_param)
Which comes out to being this:
post_path(1)
And when that method is called you would get this little string:
"/posts/1"
Lovely!
This is called polymorphic routing. You can pass an object to methods like redirect_to, link_to and form_for and it will
attempt to work out the correct URL of what to use.
The form of form_for
Now, when you're coding Rails you may have used form_for like this a very long time ago:
<% form_for #post, :url => { :controller => "posts", :action => "create" } do |f| %>
Of course, with advancements in Rails you could simplify it to this:
<% form_for #post, :url => posts_path do |f| %>
Because the form is going to default to having a POST HTTP method and therefore a request to posts_path is going to go to the
create action of PostsController, rather than the index action, which is what would result if it were a GET request.
But why stop there? Why not just write this?
<%= form_for #post do |f| %>
Personally, I see no reason not to... if it's something as simple as this. The form_for method uses url_for underneath, just like
redirect_to to work out where the form should go. It knows that the #post object is of the Post class (again, we assume) and it
checks to see if the object is persisted. If it is, then it will use post_path(#post). If it's not, then posts_path.
The form_for method itself checks to see if the object passed in is persisted also, and if it is then it'll default to a PUT HTTP
method, otherwise a POST.
So this is how form_for can be flexible enough to have an identical syntax on both a new and edit view. It's becoming more and
more common these days for people to even put their whole form_for tags into a single partial and include it in both the new and
edit pages.
A more complex form
So form_for is fairly simple for when you pass a normal object, but what happens if you pass an array of objects? Like this, for
instance:
<%= form_for [#post, #comment] do |f| %>
Well, both url_for and form_for have you covered there too.
The url_for method detects that this is an array and separates out each part and inspects them individually. First, what is this
#post thing? Well, in this case let's assume it's a Post instance that is persisted and has the id of 1. Second, what is this
#comment object? It's a Comment instance that has not yet been persisted to the database.
What url_for will do here is build up the URL helper method piece by piece by placing each part in an array, joining it into a routing method and then calling that routing method with the necessary arguments.
First, it knows that the #post object is of the Post class and is persisted, therefore the URL helper will begin with post. Second, it knows that the #comment object is of the Comment class and is not persisted, and therefore comments will follow post in the URL helper build. The parts that url_for now knows about are [:post, :comments].
The url_for method combines these individual parts with an underscore, so that it becomes post_comments and then appends _path
to the end of that, resulting in post_comments_path. Then it passes in just the persisted objects to the call to that method, resulting in a call like this:
post_comments_path(#post)
Calling that method results in this:
"/posts/1/comments"
Best part? form_for will still know to use POST if the #comment object is not a persisted object, and PUT if it is. A good
thing to remember is that the form_for is always for the last object specified in the array. The objects prior to it are just its
nesting, nothing more.
The more objects that are added, the more times url_for will do the hard yards and build the path out... although I recommend that
you keep it to just two parts.
A symbolic form
Now that we've covered using an array containing objects for form_for, let's take a look at another common use. An array containing
at least one Symbol object, like this:
<%= form_for [:admin, #post, #comment] do |f| %>
What the url_for method does here is very simple. It sees that there's a Symbol and takes it as it is. The first part of the
url will simply be the same as the symbol: admin. The URL that url_for knows of at this point is just [:admin].
Then url_for goes through the remaining parts of the array. In this case, let's assume both #post and #comment are persisted
and that they have the ids of 1 and 2 respectively. Same classes as before. url_for then adds post to the URL that it's building,
and comment too, resulting in [:admin, :post, :comment].
Then the joining happens, resulting in a method of admin_post_comment_path, and because both #post and #comment are persisted here,
they're passed in, resulting in this method call:
admin_post_comment_path(#post, #comment)
Which (usually) turns into this path:
/admin/posts/1/comments/2
You can use the array form of polymorphic routing with the redirect_to, link_to and form_for methods. There's probably other
methods that I'm not remembering right now that can do it too... it's generally anything in Rails that would normally take a URL.
There's no need to build your URLs in any Rails version greater-than 2 using hashes; that's pretty old school.
Instead, experiment with your new knowledge of polymorphic routing and use it to the best of your advantage.
This should work:
link_to "User Posts", user_posts_path(#user)
for more details visit:
http://guides.rubyonrails.org/routing.html
link_to uses url_for which uses polymorphic_url.
polymorphic_url:
builds the helper method, using the class name of active record objects
calls the helper with the active record objects as arguments
Therefore, as others said, you should use:
link_to 'User Posts', [#user, :posts]
for which the path is:
user_posts_path(#user)
^^^^ ^^^^^ ^^^^^
1 2 3
class of #user because it is an active record
convert to string because symbol
add as call argument because active record
That builds the good helper method.
This is how to link to a nested resource in the latest Rails:
link_to 'Destroy Comment', post_comment_path(comment.post, comment)
Note: This is in a partial so there isn't a #.

Resources