I am using devise for two different types of user in my app. They are called user and professional.
I currently have a simple resource based controller called MessagesController which pulls out messages for the current professional like this
class MessagesController < ApplicationController
def index
#messages = Message.find_all_by_profession_id(current_professional.id)
end
end
I want to find the best way of keeping this controller but changing the query based on the type of user that is logged in. I want the same to happen for all actions in the resource (index, new, create, update etc)
I know I can do this
if current_user
#messages = Message.find_all_by_user_id(current_user.id)
else
#messages = Message.find_all_by_profession_id(current_professional.id)
end
but this would be bulky and messy across all actions. I'm sure there must be a better way. What is the most rails like way of doing this? Should I be creating a completely new controller do handle user based messages?
I can think of two ways:
You can put your code inside the initialize method of your controller:
def initialize
if current_user
#messages = Message.find_all_by_user_id(current_user.id)
else
#messages = Message.find_all_by_profession_id(current_professional.id)
end
super
end
Or you can create a before_filter :
class MessagesController < ApplicationController
before_filter :get_messages
private
def get_messages
if current_user
#messages = Message.find_all_by_user_id(current_user.id)
else
#messages = Message.find_all_by_profession_id(current_professional.id)
end
end
end
IMHO, i think you can move this chunk of code to the model, so the controller only make a call passing the user parameter and gets all messages from the model.
# messsages_controller.rb
#messages = Message.find_all_messages(current_user, current_professional)
# message.rb
def self.find_all_messages(user, professional)
if user
self.find_all_by_user_id(user.id)
else
self.find_all_by_profession_id(professional.id)
end
end
I think it is better for this kind of code to be on your model. Of course you can improve the if/else code, but i am out of ideas now.
Related
I have trouble with trailblazer when setting up a simple show all Things view.
operation
class Thing < ApplicationRecord
class ShowAll < Trailblazer::Operation
include Model
model Thing, :all #why :all is not working here?
def process
end
end
end
controller
class PageController < ApplicationController
def index
run Word::ShowAll
end
end
why is :all not working for getting all Things from the db but :find works to get one via its id?
The best place to ask TRB questions is actually on Github channel.
I'm not sure where you found that example, as it is not supposed to work AFAIK, :find is a shortcut I believe, I've never actually used it.
All your logic should be defined inside the process method. http://trailblazer.to/gems/operation/1.1/api.html#process
Having said that, trying to get all records without some kind of pagination is a really bad idea, unless you are 100% sure that your table won't grow beyond several dozen records. Unless you know you don't have a large load. So to define that kind of shortcut is dangerous.
Calling Trailblazer::Model#model as you're doing there is just a shortcut for overriding the TrailBlazer::Operaration#model! method. So what you seem to want to do is:
class Thing < ApplicationRecord
class ShowAll < Trailblazer::Operation
def model!(params)
Thing.all # add any filtering or pagination here
end
end
end
And in your controller call present instead of run so that it sets up the model but doesn't call the process method of the operation.
class PageController < ApplicationController
def index
present Word::ShowAll
end
end
Guys I've a helper method in ApplicationController as follows
class ApplicationController < ActionController::Base
helper_method :current_user
end
And I want to call it in my model (say Project) as follows:
class Project < ActiveRecord::Base
after_save :update_other_tables
private
def update_other_tables
# if project saves, Create a new insteance into User Projects
#userproject=UserProject.new(
:user_id=>User.current_user.id,
:project_id=>self.id
)
#userproject.save
end
Here, I'm getting error like undefined method `current_user'
So, How to call the this method in model? please help me
I would do it in the following way:
class ApplicationController < ActionController::Base
# Assignment method
def current_user=(user)
#current_user = user
end
# Returns the current user, nil otherwise
def current_user
#current_user
end
end
Now when a user logs in you can set:
current_user = the_user
And from the point onwards you can do:
current_user.id
Hope this helps.
not a ruby guy but I am guessing the User here isnt referring to the User that is present in your controller namespace and that is why there is no method current_user on the object.. You probably need to pass the User object to your helper method or just pass the id..
The above theory is from my experience dealing with something similar in .Net MVC so please correct me if I am wrong and I will delete the answer..
P.S: was too long of a comment
I am trying to achieve a subsequent form submission. To clarify things -
I submit a form for #post
then once that #post is created I would immediately (under the hood) like to submit the form for #associations.
The catch is, this second form submission would require the post_id field from the newly created #post.
What would be the best way to achieve this? Would nested forms help me pull the newly created #post.id? Kindly help me with this.
If this is something that should happen whenever you create a Post, then you should use active callbacks to achieve that :
class Post < ActiveRecord::Base
after_create do |post|
# create your association using post.id
end
end
or, you can write it like that also :
class Post < ActiveRecord::Base
after_create :after_create_post
def after_create_post
# create your association using self.id
end
end
Otherwise, if this is something specific to a controller action, you should simple do something like this :
class PostsController < ApplicationController
def create
#post = current_user.posts.build(params[:post])
# then use the #post.id to build your association. something like
#post.associations.build(:prop1 => 'value1', :prop2 => 'value2')
end
end
Hope this helps!
Just start to develop with devise for my app authentication, however running into trouble with accessing current_user from a model. I have no problem accessing this from a controller.
For my scenario I need to get the current user id to keep track who has edit the blog post, which I save the user id with before_save filter.
Appreciate your help!
Thanks
You can only pass it down in a parameter.
class SomeModel
def my_nifty_method(user)
end
end
in controller:
class SomeModelsController < ApplicationController
def some_method
sm = SomeModel.new
sm.my_nifty_method(current_user)
end
end
You should be able to do this in your model. 'c' here being the model object and setting it's user_id attribute before create, or save in your case.
before_create {|c| c.user_id = Authorization.current_user.id}
I'm somewhat new to RoR so be warned.
Why are you trying to access to that method in the model?
You can access to the ID attributes via self.id (within the model)
I have a model (say Car) in which a method needs access to the current_user to determine if the user is allowed to perform the things that the method does.
For example, a method might want to do these things:
Check that current_user owns this object
Check that the object status == 1 (Active)
Check that a related object exists and it's X field is not NULL
I need this business logic to be in the model, not in the controller, so that it's the one place where my business logic will be. The method might get called from places other than a controller.
I know that there are gems like cancan, declarative_authorization etc. but they seem to be overkill for what I need to do. And also, accessing current_user in a model is not considered the "right way".
Then, how do I make that check in the model but still feel "clean"?
I have experienced a situation where "current_user" needs be tightly connected to a model, but I handled it all in the Controller and it works pretty well. Here are some examples:
My model is "Photos". Photos are owned by users, and how people interact with photos is obviously tightly related to whether or not they own the photo.
In the show action I need to load either the existing rating a user has given to a photo (so they can edit it) or allow them to create a new one:
def show
#photo = Photo.find(params[:id])
if user_signed_in?
if #rating = current_user.ratings.find_by_photo_id(params[:id])
#rating
#current_user_rating = #rating.value
else
#rating = current_user.ratings.new
#current_user_rating = "n/a"
end
end
end
When people create photos I want them to be automatically assigned to the current user.
def new
#photo = Photo.new
end
def create
#photo = current_user.photos.create(params[:photo])
if #photo.save
redirect_to user_path(current_user), :notice => t('photo.notice.created')
else
render 'new'
end
end
Only the owners of a photo can change them:
def edit
#photo = Photo.find(params[:id])
if #photo.user == current_user
render 'edit'
else
redirect_to user_path(current_user), :alert => t('application.error.unauthorized')
end
end
def update
#photo = current_user.photos.find_by_id(params[:id])
#photo.update_attributes(params[:photo])
if #photo.save
redirect_to user_path(current_user), :notice => t('photo.notice.updated')
else
render 'edit'
end
end
This approach is based on the constraints that a "current_user" object is tied to the session, which only the controller knows about. So, in short, I have yet to find a good way to integrate "current_user" into a model, but I've been able to find (I think) pretty clean ways to tie the model and controller together so that this can be provided by the controller.
One fairly simple solution to most problems, if your controller is starting to get messy, would be to take a chunk of logic and define as a method in the model, but require one argument = a user object. Then you can just feed "current_user" to that method from your controller and the model handles the rest.
Good luck! Also, if anyone else has any better ideas for this, I'd love to hear them!
Handle auth'ing in a Controller.
Example: Putting auth logic in parent ApplicationController.
class ApplicationController < ActionController::Base
protect_from_forgery
protected
# Returns the currently logged in user or nil if there isn't one
def current_user
return unless session[:user_id]
#current_user ||= User.find_by_id(session[:user_id])
end
# Make current_user available in templates as a helper
helper_method :current_user
# Filter method to enforce a login requirement
# Apply as a before_filter on any controller you want to protect
def authenticate
logged_in? ? true : access_denied
end
# Predicate method to test for a logged in user
def logged_in?
current_user.is_a? User
end
# Make logged_in? available in templates as a helper
helper_method :logged_in?
def access_denied
redirect_to login_path, :notice => "Please log in to continue" and return false
end
end
Now that current_user is an accessor to the logged in user and you can access it in any controller, you can do your authorization logic in the appropriate controller before you do anything with the model.
Your right, though. Models don't care about authorization or who is accessing them.