A controller method calls upon a model method to generate a token. Inside the controller method the generated token is also saved in a cookie: cookies.permanent[:remember_token] = user.remember_token.
Would it not be better to include this last line in the model method that creates the token? It is DRYer since I have more than 1 controller with this same behaviour (using the same model method). And the risk that I would forget these cookies lines is lower. Or is it not possible to execute this command in a model method?
Update: I think it's not possible to do this in a model method because there it's not known to which user/computer to write the cookie to?
Models should have no knowledge about concepts like a user's cookie. It's really a controller thing.
If you're using the line across different controllers, you could move it into the ApplicationController:
class ApplicationController < ActionController::Base
def store_remember_token(user)
cookies.permanent[:remember_token] = user.remember_token
end
end
And then reuse it in your controllers:
class SomethingsController < ApplicationController
def show
#user = # ...
store_remember_token(#user)
end
end
Related
In a create method in a controller I have:
if logged_in_admin?
#invitation.set_ids
In the Invitation model:
def set_ids
self.person_one_id = current_user.id
end
current_user is a method in app/helpers/sessions_helper.rb and defines the currently logged in user. I use this method successfully in many controller methods. However, for the use case above I get the error message undefined local variable or method 'current_user' for #<Invitation:0x007f699086bf40>.
Why do I get this error message? Is this because this time I'm using the helper method in a model file and is this not allowed? If such is not allowed, what would be the best way to securely set person_one_id for #invitation equal to the id of the currently logged in user?
current_user not available in a model layer(it's MVC, your helpers on the CV layer and model know nothing about the current_user helper). Pass user_id from your helper as argument:
some_helper.rb
def my_helper
if logged_in_admin?
#invitation.set_ids(current_user.id)
# .....
model.rb:
def set_ids(user_id)
self.person_one_id = user_id
end
You have to add the following line to your ApplicationController:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SessionsHelper
end
Now you should be able to use the methods inside your controllers / models.
I'm in the process of creating my own simple blog application and I want to include a 'latest posts' section on the sidebar, so my posts model needs to be accessible by the entire app. I'm looking for the best way of doing so.
I'm thinking a before_filter in the application controller followed up a private method to call the scope I have:
class ApplicationController < ActionController::Base
before_filter :latest_news
private
def latest_news
#latest = News.latest.limit(5)
end
end
Is this the best way?
Instead of a before_filter, I'd recommend using a lazy-load approach that does basically the same thing.
class ApplicationController < ActionController::Base
helper_method :latest_news
def latest_news
#latest_news ||= News.latest.limit(5)
end
end
This way you can call latest_news from any controller or view (which is what the helper_method macro does for you) and then it'll load it if it's not loaded already the first time it's called and any subsequent calls will be cached. This is a pretty common pattern for getting things like the current user record, etc.
Rails form validation is designed to go in the model most easily. But I need to make sure the current user has the required privileges to submit a post and the current_user variable is only accessible in the controller and view.
I found this answer in a similar question:
You could define a :user_gold virtual attribute for Book, set it in the controller where you have access to current_user and then incorporate that into your Book validation.`
How can I set this up with my post and user controller so that the current_user variable is accessible in the model?
Solution:
This whole thing is wrong from an application design perspective as #Deefour's answer pointed out. I changed it so my view doesn't render the form unless the condition is true.
The "similar question" is saying you can do something like this
class YourModel < ActiveRecord::Base
attr_accessor :current_user
# ...
end
and then in your controller action you can do something like
#your_model = YourModel.find(params[:id])
#your_model.current_user = current_user
#your_model.assign_attributes(params[:your_model])
if #your_model.valid?
# ...
You can then use self.current_user within YourModel's validation methods.
Note I don't think this is what you should be doing though, as I don't consider this "validation" as much as "authorization". An unauthorized user shouldn't even be able to get the part of your action where such an update to a YourModel instance could be saved.
As for doing the authorization with Pundit as requested, you'd have a file in app/policies/your_model.rb
class YourModelPolicy < Struct.new(:user, :your_model)
def update?
user.some_privilege == true # change this to suit your needs, checking the "required privileges" you mention
end
end
Include Pundit in your ApplicationController
class ApplicationController < ActionController::Base
include Pundit
# ...
end
Then, in your controller action you can do simply
def update
#your_model = YourModel.find(params[:id])
authorize #your_model
# ...
The authorize method will call YourModelPolicy's update? method (it calls the method matching your action + ? by default) and if a falsy value is returned a 403 error will result.
Authorization shouldn't be done in models. Models have already many responsibilities don't you think?
That's a controller thing, and actually you can have the logic in other place using some gem like cancan and in your controller you would do something like:
authorize! :create, Post
You can define a "virtual attribute" in your model like this:
class Book < ActiveRecord::Base
attr_accessor :current_user
end
Its value can be set directly in your controller like this:
class BooksController < ApplicationController
def create
book = Book.new
book.current_user = current_user
book.save!
end
end
And inside your model's validation routine, you can access it like any other ActiveRecord field:
def validate_user_permission
errors[:current_user] = "user does not have permission" unless current_user.is_gold?
end
I can't remember if this is the case with ActiveRecord, but you might be able to set virtual attributes via the mass-assignment methods like create, update, and new in the controller:
def create
Book.create!(current_user: current_user)
end
In order to do that, you would probably have to add the following line to your model to enable mass-assignment of that virtual attribute:
attr_accessible :current_user
I agree with Ismael - this is normally done in the controller. It's not an attribute of the model, it's a permission issue and related to the controller business logic.
If you don't need all the power of a gem like CanCan, you can role your own.
class BooksController < ApplicationController
before_filter :gold_required, :only => :create
def create
book = Book.new
book.save!
end
# Can be application controller
private
def gold_required
return current_user && current_user.is_gold?
end
end
You may want to put the filter on the 'new' method as well.
I am playing with the heroku api in Rails and have come across a potential issue.
After submitting a login form i am instantiating the heroku object.
heroku = Heroku::API.new(:username => USERNAME, :password => PASSWORD)
I would then like to use the heroku object in all controllers to further querying the api. I have tried #heroku, ##heroku and $heroku, but none work. Is this possible?
The only solution i have found it to use the api to fetch the users api key, store this in a session then use it to re-instantiate the heroku object within each controller method. Is this the best / only solution?
In general, a before_filter could solve your re-instantiating problem. If you want to set an instance variable that is available to every controller method, do something like this:
class UsersController < ApplicationController
before_filter :get_user
def profile
# #user is accessible here
end
def account
# #user is accessible here
end
private
def get_user
#user = User.find(params[:id])
end
end
You can also user before_filters in the application controller to set instance variables that are accessible in all of your controllers. Read about filters here.
As for storing the API keys to the session, that works, but if you want long term access, you might want to write the API keys to the database. Combined with before filters, you could do something like this:
class ApplicationController < ActionController::Base
before_filter :setup_heroku
def setup_heroku
if current_user && current_user.heroku_api_key
#heroku = Heroku::API.new(:api_key => current_user.heroku_api_key)
end
end
end
Im working with a medium sized Rails application and I do this in every controller:
def create
#object = Model.new(params[:model].merge(editing_user: current_user))
...
end
def update
#object = Model.find(params[:id])
#object.editing_user = current_user
...
end
Setting the editing user over and over again is not DRY. I thought about cleaning this up with an observer but it would need access to the current user. Observers do not have access to the current user, neither should they (Law of Demeter).
Any suggestions how to DRY this up between controllers?
class ApplicationController < ActionController::Base
before_filter :init_request
def init_request
params[:editing_user] = current_user
end
end
I like using decent_exposure to dry up my controllers. It automatically finds or initializes a model instance, based on whether an :id was passed as a param, and it assigns the attributes from params[:model].
To finish drying up your code, you could use the new strategy support (see the end of the readme) to automatically set the editing_user attribute on your model.
You could try an after_filter for this. Perhaps something like so:
class ApplicationController < ActionController::Base
after_filter :set_editing_user
def set_editing_user
#object.update_attribute(:editing_user, current_user) if #object && current_user
end
The difficulty, of course, is that you'll be saving the object twice per call. Generally though creations and updates don't happen so frequently that two database commits is a serious problem, but if you expect to be the next Twitter -- with massive database insertion load -- it could be an issue.
You could also possibly set this in a before_filter, but then you'd have to find or set the object in a previous before_filter. Otherwise #object will always be nil and the before_filter will never fire. You can use the filter ordering methods prepend_before_filter and append_before_filter to ensure the correct sequencing of these filters.