Ruby on Rails NoMethodError: undefined method `' for #<User:0x50987b8> - ruby-on-rails

This is a really simple problem I believe.
users_controller
def testcall
end
routes
resources :users
In console I type
a = User.new
a.testcall
I've tried making it
self.testcall
and then calling
User.testcall
and its the same issue

Rails uses the model-view-controller pattern, meaning the model, in this case User, and its internal behaviors are separated from the controller, in this case UsersController. The convention is to have "fat" models and "skinny" controllers, meaning most of the code defining behaviors is in the model.
So, if you want the behavior to be for an instance of User, that behavior should be inside the User class (which should be in app/models/user.rb).
For instance:
class User < ActiveRecord::Base
def testcall
puts "This is a test"
end
end
Then you could call this from the rails console like you were doing. This is because you are bypassing the controller and calling methods directly on the model by calling:
a = User.new
a.testcall
in the console. If you actually want this to be performed when someone visits a webpage, submits a form, or whatever, then you must assign the appropriate request action in the controller to call the method on a designated instance of User.
For example,
class UsersController < ApplicationController
def create
#user = User.new
#user.testcall
end
end
Obviously, the routes must be properly set up for that to work.

Related

Rails: Where do I put my API methods?

I'm very new to Rails, and I'm a little overwhelmed where I do simple things like create an API call. I've set up a route at /reports which has this controller:
class ReportsController < ApplicationController
#client = # Api-accessing gem
#all_reports = []
def self.request_report
begin
puts "Step 1:"
step1 = #client.request_report(opts = {"max_count" => 1})
step1_result = step1.parse
puts "Done!"
puts step1_result
rescue Excon::Errors::ServiceUnavailable => e
puts "Didn't work"
logger.warn e.response.message
retry
end
end # End request_report
request_report
end
This correctly calls the external API when I first load the /reports route, but when I refresh the page the code isn't re-run.
Perhaps I'm misunderstanding what controllers are used for? Am I meant to be putting this code somewhere else? Or is there a caching issue?
The only public API of controller are the actions which respond to a HTTP request. In your case get "/reports" => "reports#request_report" is a route which corresponds to the action request_report.
However actions are instance methods, not class methods:
class ReportsController
def request_report # self.request_report would make this a class method!
# #todo get reports from somewhere and
# return some sort of response.
end
# any method call here happens when the class is evaluated.
end
You are declaring the action as a class method and then calling it when the ReportsController class is evaluated. Sorry to say but just about everything about your controller is wrong.
The Rails convention would be to call the action index.
Controllers in Rails should only be instantiated by the router (or your test framework). So they are definatly the wrong place to put resuable bits and bobs. If you ever see someone doing ReportsController.new.foo or ReportsController.foo - fire them on the spot.
So where do you put external API calls?
If its a pretty trivial one-off you can place it in private method in your controller.
Some place API calls on the model layer - however that is debatable since ActiveRecord models already are supercharged to the gills with powers and responsibilities.
One solution that has worked well for me is Service Objects. They are easy to test and have a clear single responsibility.
class RequestReportService
def initalize(client)
#client = client
end
def call(opts = {})
begin
return #client.request_report(opts.merge("max_count" => 1))
rescue Excon::Errors::ServiceUnavailable => e
nil
end
end
end
class ReportsController
def index
#reports = RequestReportService.new(#client).call
end
end
To add to #max's excellent answer, you need to appreciate that Rails is based on a stateless protocol (HTTP)...
each request message can [only] be understood in isolation.
This means that if you want to create a set of controller actions, you have to appreciate that each call is going to create a new instance of your classes etc. This, coupled with the idea of a RESTful set of actions, should give you a basis from which to build your functionality.
--
#config/routes
scope constraints: { subdomain: "api" } do
resources :reports #-> http://api.url.com/reports
end
#app/controllers/reports_controller.rb
class ReportsController < ApplicationController
respond_to :json #-> requires "responders" gem
def index #-> instance method
#reports = Report.all
respond_with #reports #-> all reports
end
def show
#report = Report.find params[:id]
respond_with #report
end
end
I'll leave the service object stuff as I have no experience with it.
--
If you're pulling from an external API, you have several considerations:
Calls ideally need to be asynchronous (unless you use multi-threading)
Calls need to be made in the instance method
Your current pattern calls the API on the class, which is why you can't refresh it:
class ReportsController < ApplicationController
#client = # Api-accessing gem
#client is only invoked (I don't know why it works, as it should be a class variable) with the class.
So if you send a new request (which creates an instance of ReportsController), #client is going to be declared that one time.
To get it working correctly, #client needs to be defined with each instance method:
class ReportsController < ApplicationController
def index
#client = # Api-accessing gem
This way, each time you invoke ReportsController#index, a new API call will be made. Might seem trivial, but the data scope is massive.
Finally, you need to read up about MVC (Model View Controller):
This will show you how controllers are meant to be used in Rails applications etc.
Well I actually never seen anyone code like this in a rails controller. Rails is a mvp framework. Controller are use to negotiate between your model and the views. First of all, if you routed correctly to your controller like
get "/reports" => "request_report#reports"
your controller should have a method like the following
def request_report
#client = Client.find(params[:id])
end
And then the controller will render and display the view in your app/views/reports/request_report.html.erb with access to the #client variable you just search from your database.
I am not sure why you are calling the block request_report at the bottom of the page, it just doesn't make sense in a controller. And you certainly don't really need to write self in front of a controller method.
def self.request_report
your code
end
As for where to put your api controller, usually for an api controller, we can create new folders under controllers, so the structure will be like
app/controllers/api/v1/your_api_controller.rb
Then in your_api_controller.rb you will need to add namespace infront of your controller like this.
class Api::V1::ReportsController < ActionController::Base
end
It is the same with your routes, you will add namespace in your route.rb
namespace :api do
namespace :v1 do
get "/reports" => "request_report#reports"
end
end

Helper method called upon in model file, not working

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.

accessing devise current_user within model

hi i am trying to access current_user within a model for the purpose of creating an element on the fly with find_or_create_by.
the following is the method within my model
def opponent_name=(name)
self.opponent = Opponent.find_or_create_by_name_and_team_id(name,current_user.team_id) if name.present?
end
but the error i am getting is
NameError in EventsController#create
undefined local variable or method `current_user' for #<Event:0x007fb575e92000>
current_user is not accessible from within model files in Rails, only controllers, views and helpers.
What you should do is to pass the current_user.team_id to the opponent_name method like this:
def opponent_name=(name, current_user_team_id)
self.opponent = Opponent.find_or_create_by_name_and_team_id(name,current_user.team_id) if name.present?
end
Access current_user in Model File:
# code in Applcation Controller:
class ApplicationController < ActionController::Base
before_filter :global_user
def global_user
Comment.user = current_user
end
end
#Code in your Model File :
class Comment < ActiveRecord::Base
cattr_accessor :user # it's accessible outside Comment
attr_accessible :commenter
def assign_user
self.commenter = self.user.name
end
end
Pardon me, if It violates any MVC Architecture Rules.
Its not a good way to access the current_user in a model, this logic belongs to the controller. But if you realy cant find a workaround you should put it into a thread. But keep in mind this is not the way how it should be build.
https://rails-bestpractices.com/posts/2010/08/23/fetch-current-user-in-models/
Rails 5.2 introduced current attributes:
https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html
but as always... you must have in mind that using global states like this might let to some unpredictable behaviour 🤷‍♀️ :
https://ryanbigg.com/2017/06/current-considered-harmful

Get current_user in Rails form validation by defining a virtual attribute

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.

Rails-way - where to put this kind of helper method?

I'm struggling a bit to find the right place for a helper method. The method basicly 'inspects' a User-model object and should return some information about the 'progress' of the user, eg. "You need to add pictures", "Fill out your address" or "Add your e-mail-adress". None of the conditions I'm checking for are required, it's just like a "This is your profile completeness"-functionality as seen on LinkedIn etc.
Each of these 'actions' have a URL, where the user can complete the action, eg. a URL to the page where they can upload a profile photo if that is missing.
Since I need access to my named routes helpers (eg. new_user_image_path) I'm having a hard time figuring out the Rails-way of structuring the code.
I'd like to return an object with a DSL like this:
class UserCompleteness
def initialize(user)
end
def actions
# Returns an array of actions to be completed
end
def percent
# Returns a 'profile completeness' percentage
end
end
And user it with something like: #completeness = user_completeness(current_user)
However, if I'm adding this to my application_helper I don't have access to my named routes helpers. Same goes if I add it to my User-model.
Where should I place this kind of helper method?
This is a similar problem to that of Mailers. They are models, and should not cross the MVC boundaries, but need to generate views. Try this:
class UserCompleteness
include ActionController::UrlWriter
def initialize(user)
end
def actions
# Returns an array of actions to be completed
new_user_image_path(user)
end
def percent
# Returns a 'profile completeness' percentage
end
end
But be aware you are breaking MVC encapsulation, which might make testing more difficult. If you can get away with some methods in the users helper instead of a class that might be better.
From the little i got your question i think you want a method which you can used in Controller as well as Views.
To Accomplish this simple add method in application_controller.rb and named it hepler_method
Example:-
class ApplicationController < ActionController::Base
helper_method :current_user
def current_user
#current_user ||= User.find_by_id(session[:user])
end
end
you can use method current_user in both Controller as well as views

Resources