I am trying to call 'checking' action from post model.
class Post < ApplicationRecord
after_commit :testing
def testing
#id = "#{self.id}"
puts #id
checking # call action to controller
end
end
posts_controller.rb
def checking
puts "not Working"
end
I am trying to achieve the above, whereas it is not getting called.
You can do this by instantiating the controller.
SomeController.new.some_action
However it is Not Recommended as your model logic should be independent of your controller logic. Your model should follow Single-responsibility Principle.
A class should have only a single responsibility.
You might need some common logic for controller and models or some service!!!!
As it was said: the models should not know about controller.
Depending on the reason,
you can define checking in model and from Controller just call this method for current Post.
if something should be set from Controller and checked in Model , you can use the approach defined here
As many (or all) other said, you shouldn't call a Controller action from a Model. If you are saving/destroying and object from a Controller and you want to check is commit was called, you can do it in the controller, after saving or destroying.
posts_controller.rb
def create
#post = current_user.posts.build(post_params)
if #post.save
checking
end
end
def checking
puts "Is is working"
end
If this is not what you want, because you are specifically interested in the commit callback, you can change the code to this:
posts_controller.rb
def create
#post = current_user.posts.build(post_params)
#post.save
if #post.commmited?
checking
end
end
def checking
puts "Is is working"
end
And add some logic to your Model:
class Post < ApplicationRecord
attr_accessor :commit_performed
#unset the commit_performed attribute on first callbacks
before_destroy :unset_commit
before_validation :unset_commit
#set the commit_performed attribute after commit
after_commit :set_commit
def unset_commit
#commit_performed = false
end
def set_commit
#commit_performed = true
end
def commited?
#commit_performed
end
end
You should not be calling a controller action from a model. That's not how even controller methods are called. If you want to trigger an action, it should be either written as a target of a view form or input method, or as a redirect through another controller method itself. If you really want this action to be triggered (if you want the message after a record is saved to a listener url), I'd suggest using a lib such as HTTP::Net or a gem like HTTParty to trigger a call to the action, with its url or rails url_helper. That too, is not suggested, and/or is not the way to operate things in rails.
Related
Right now I have a model Trip that when saved, runs some callbacks. I want to isolate this behavior so that it happens only when it runs on the controller (create/update/destroy), so I want to remove the callbacks.
Assuming I have a service object DoSomething#call which accepts trip and does everything I need, what are my options to run it in create/update/destroy?
I have a few ideas but they involve horror things like:
def create
super() do |success, failure|
#action_successful = failure.instance_of?(
InheritedResources::BlankSlate
) || failure.class.nil?
if #action_successful
DoSomething.call(Trip.find(params[:id]))
end
end
end
Which comes with a lot of downsides:
The horrible way to detect if an action is successful
No way to get the in-memory reference to the object being acted on (reload from db)
Since I have no reference to the in memory object, it's quite problematic to run something during on destroy (no reference, can't reload)
Additional code as requested
class Trip
end
The custom service (I've multiples)
class SaveLastChangedTrip
def call(user, trip)
return if user.nil?
user.update_attributes!(last_trip: trip)
end
end
and the activeadmin file
ActiveAdmin.register Trip do
controller do
def update
if super() # This is pseudocode, I want to run this branch only if save is successful
SaveLastChangedTrip.call(current_user, resource)
end
end
end
end
I think you are looking for something like this:
def create
create! do |success, failure|
success.html do
DoSomething.call(resource)
end
end
end
See comments.rb for similar example.
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
I'm using the callback before_update to call a function on model which set the checkbox value on my variable.
The problem is the checkbox value which is on params[:mail_checker_issue] isn't accessible on the model layer.
The question is: How to access this params using the callback before_update ? Below my code:
module IssueSetChecketIssuePatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
before_save :before_mail_checker
end
end
end
module InstanceMethods
require_dependency 'issue'
def before_mail_checker
self.set_mail_checker_issue(params[:mail_checker_issue])
end
def set_mail_checker_issue(mail)
#mail_checker = mail
end
def get_mail_checker_issue
#mail_checker
end
end
Rails.configuration.to_prepare do
Issue.send(:include, IssueSetChecketIssuePatch)
end
params are a controller concern and are wholly separate from models. Consider what should happen if you tried to save that model from a console, for example.
You need to pass the param to the model after you instantiate it from your controller, then check the value set on the model in your before_save callback.
It's also worth noting that your code is somewhat un-Rubyish (and really, looks a lot like Java!) - you could get the same effect by just defining an attr on the model.
Rails.configuration.to_prepare do
require_dependency 'issue'
class Issue
attr_accessor :mail_checker_issue
end
end
Then, once you have an issue:
# Controller code
#issue = Issue.find(params[:id])
#issue.mail_checker_issue = params[:mail_checker_issue]
You don't, models don't know about controllers or params hash.
You should include this logic at your controller instead of forcing it in a callback.
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 need to call the create action in controller A, from controller B.
The reason is that I need to redirect differently when I'm calling from controller B.
Can it be done in Rails?
To use one controller from another, do this:
def action_that_calls_one_from_another_controller
controller_you_want = ControllerYouWant.new
controller_you_want.request = request
controller_you_want.response = response
controller_you_want.action_you_want
end
You can use a redirect to that action :
redirect_to your_controller_action_url
More on : Rails Guide
To just render the new action :
redirect_to your_controller_action_url and return
The logic you present is not MVC, then not Rails, compatible.
A controller renders a view or redirect
A method executes code
From these considerations, I advise you to create methods in your controller and call them from your action.
Example:
def index
get_variable
end
private
def get_variable
#var = Var.all
end
That said you can do exactly the same through different controllers and summon a method from controller A while you are in controller B.
Vocabulary is extremely important that's why I insist much.
You can use url_for to get the URL for a controller and action and then use redirect_to to go to that URL.
redirect_to url_for(:controller => :controller_name, :action => :action_name)
This is bad practice to call another controller action.
You should
duplicate this action in your controller B, or
wrap it as a model method, that will be shared to all controllers, or
you can extend this action in controller A.
My opinion:
First approach is not DRY but it is still better than calling for another action.
Second approach is good and flexible.
Third approach is what I used to do often. So I'll show little example.
def create
#my_obj = MyModel.new(params[:my_model])
if #my_obj.save
redirect_to params[:redirect_to] || some_default_path
end
end
So you can send to this action redirect_to param, which can be any path you want.
Perhaps the logic could be extracted into a helper? helpers are available to all classes and don't transfer control. You could check within it, perhaps for controller name, to see how it was called.
Composition to the rescue!
Given the reason, rather than invoking actions across controllers one should design controllers to seperate shared and custom parts of the code. This will help to avoid both - code duplication and breaking MVC pattern.
Although that can be done in a number of ways, using concerns (composition) is a good practice.
# controllers/a_controller.rb
class AController < ApplicationController
include Createable
private def redirect_url
'one/url'
end
end
# controllers/b_controller.rb
class BController < ApplicationController
include Createable
private def redirect_url
'another/url'
end
end
# controllers/concerns/createable.rb
module Createable
def create
do_usefull_things
redirect_to redirect_url
end
end
Hope that helps.
You can call another action inside a action as follows:
redirect_to action: 'action_name'
class MyController < ApplicationController
def action1
redirect_to action: 'action2'
end
def action2
end
end
Separate these functions from controllers and put them into model file. Then include the model file in your controller.