How to run class method via URL in Rails 4 - ruby-on-rails

I'm real beginner in Rails.
I created app/services/xclass.rb class with some_method inside.
I need to execute some_method using url.
For example, I want run this method when I execute in my browser url - http://application.com/notifications/send
I think it could be done through controller (notifications_controller) but how to do it?
I created only controller, with no model, just for launching some_method.

first, create a route:
get "notifications/send" => "notifications#some_action", :as => "send_notification"
Then create a controller action in your controller (ie. NotificationsController):
def some_action
Xclass.some_method # run the method you want
redirect_to root_path # redirect or whatever you want here
end
Now you can either visit the path http://your_app.com/notifications/send, or link to is using 'send_notifications_path' url helper in rails.
That should do it

Since you're a beginner, let me give you some ideas
MVC
Firstly, you need to appreciate that Rails is an MVC (model view controller) framework:
In short, this means that every time you send a "request" to Rails, it will be "routed" to the specific controller action which corresponds with that route.
This means that when you ask about how to fire a "class method", you're going to have to work within the confines of the MVC programming pattern. Here's how:
#config/routes.rb
resources :notifications do
get :send, on: :collection #=> domain.com/notifications/send
end
#app/controllers/notifications_controller.rb
class NotificationsController < ApplicationController
def send
#call your class method here
YourModel.class_method
end
end
#app/lib/your_model.rb
class YourModel
def self.class_method
#do something here
end
end
--
Rails
This is further supported by the fact that Rails is just a framework - in fact it's a gem (a great one) which runs on top of Ruby.
This means that even though some of the ways in which Rails works might seem somewhat alien to begin with, you have to remember that it basically just captures "requests" fed to it by a web sever, processing them with connectivity to the database etc.
The issue here is that as you're sending the request over HTTP, you have to work within the constraints of this protocol (specifically that it's stateless), and with Rails. As mentioned, Rails is MVC-based, which means that every request will be routed to your controller, which is why you have to create the corresponding route & controller action to handle it
If you use the code above (tweaked to your app), it should work for you

Related

Dynamic Routes Rails 4, taken from db

Frustrating, I can't find an eligible solution for my problem.
In my Rails 4 app, I want to give my users the possibility to add their own custom post types to their sites. Like:
www.example.com/houses/address-1
www.example2.com/sports/baseball
Both would work, but only for the linked sites. Sports and houses would be the (RESTful) post types, taken from the db, added by users.
I have been struggling to find a elegant solution to accomplish this. I found http://codeconnoisseur.org/ramblings/creating-dynamic-routes-at-runtime-in-rails-4 but that feels kinda hacky and I'm not sure if reloading the routes works in production, I'm getting signals that it won't.
I'd say I have to use routes constraints http://guides.rubyonrails.org/routing.html#advanced-constraints but I don't have a clue how to approach this.
To be clear, I have no problem with the site setting stuff, the multi tenancy part of my app is fully functional (set in Middleware, so the current site is callable in the routes.rb file). My issue is with the (relative) routes, and how they could be dynamically set with db records.
Any pointers much appreciated.
I think route constraints don't work for you because your domain is a variable here. Instead, you should be examining the request object.
In your ApplicationController, you could define a method that would be called before any action, like so:
class ApplicationController < ActionController::Base
before_action :identify_site
def identify_site
#site = Site.where(:domain => request.host).first
end
end
As you scale, you could use Redis for your domains so you're not making an expensive SQL call on each request.
Then you can just add the #site as a parameter to whatever call you're making. I'm assuming you're doing some sort of "Post" thing, so I'll write some boilerplate code:
class PostController < ApplicationController
def show
#post = Post.where(:site => #site, :type => params[:type], :id => params[:id])
end
end
Just write your routes like any other regular resource.

Accessing a variable of one method in another in ruby on rails

I am facing an issue with accessing a particular variable of a method say A , in another method say B in the controller.. The size of the object(variable) is too big since it contains the results of a service call made.. My usecase is like on selecting an option from a drop down box, it redirects to a method B in controller and the same object(variable) should be parsed. How can I access the variable in the other method?
I tried storing in a cookie and since the size is too big I am getting Cookie Overflow exception. I am not using a DB. So I guess using memcache won't work. Also tried storing it as hidden field in view and passed its value as a data through ajax call. But I am getting it as a string. Tried to specify datatype as json and several other ways.. but of no use..Using ##var also din work..Not sure why..
Code:
On change of the drop down:
$(document).ready(function(){
$('#filter_service').change(function() {
$.ajax({type: "GET",
url: "/device_troubleshootings/query_operation",
data: { filter_service: $('# filter_service').val()},
});
});
});
Service call:
def log_results
//Service call
#get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
#dsn_result = #get_log_results.logs_result_obj_list
end
Here, I am trying to access #dsn_result object in "/device_troubleshootings/query_operation” method.
Please suggest me ways to access the variable.
MVC
I think you're getting confused with how Rails should work
Remember, Rails (which is just a framework for Ruby) is built on the "MVC" programming pattern. This means each time you send a request to your Rails application, it has to be handled by a single controller#action which you will then allow you to pull the relevant data from your models
The problem you have is you're trying to load multiple controller methods, and pass the same data to both. This might work in Ruby, but not Rails (Rails is stateless):
--
Model
The correct way to handle this type of setup is by creating another request for your application, which will load another controller#action, allowing you to access the data you need
As demonstrated by the MVC diagram above, each time you send a request to Rails, it's basically a new request. This means that unless you've persisted your data in the likes of a cookie, you'll need to load the data from the model.
The problem you have is you're trying to store an entire data-set in the front-end of your system. This issue is very bad, as not only is it inefficient, but it goes against the MVC pattern completely.
You'll be much better storing the bare-minimum data set you need in the front-end (ids or similar), which you will then be able send to your controller via ajax; building a new data-set from
--
Class Variables
You mentioned you tried to declare some ##class variables to no avail. The problem with this is that the class vars will only be available for an instance of a class.
As mentioned, since Rails is stateless, the class variables won't persist between requests (how can they?). I think you know this already, considering you've been trying to use cookies to store your data
The way to resolve this is to rebuild the data each time from the model (as detailed above)
Solution
The solution for you is to "go stateless"
Here's how:
Treat Method A and Method B as completely separate "ACTIONS"
When using these actions, you need to consider the smallest piece of data to pass between the two
To load Method B, you need to send a new request from your browser (as if you've never loaded Method A before)
Your method_a can be handled in the "standard" way:
#config/routes.rb
resources :your_controller do
collection do
get :method_a
get :method_b
end
end
This will mean that you can load method_a relatively simply:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
def method_a
#get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
#dsn_result = #get_log_results.logs_result_obj_list
end
end
As you know, the #dsn_result will not persist through to the next request.
There are two ways to resolve this (set a CONSTANT -- if you're pulling from an API, this will give you a single call -- or use a before_action to set the variable for as many actions as you need). I'll detail both for you:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
before_action :set_log_data
def method_a
end
def method_b
end
private
def set_log_data
#get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
#dsn_result = #get_log_results.logs_result_obj_list
end
end
This will work if you pull data from your own data-set (using the models), however, the better way to do this in your case will likely be to set a constant (considering, of course, that you don't want the data to change):
#config/initializers/dsn_result.rb
get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
DSN_RESULT = get_log_results.logs_result_obj_list
In my case I solved with global variable $my_global_var
So my files look like this
routes.rb
Rails.application.routes.draw do
resources :pages
root 'pages#index'
post 'pages/test'
end
pages_controller.rb
class PagesController < ApplicationController
def firstaction
$my_global_var = "My global var"
puts $my_global_var
end
def secondaction
puts $my_global_var
end
end
index.html.erb
<%= button_to 'Test', pages_test_path, method: :post %>

Prevent controller action from direct execution by user in Rails

I have a situation where i need to prevent users from explicitly calling say /town/addBuilding. Town is my controller and addBuilding is the action that is executed.
Now, the thing is that this action should only be executed in my program's code and not by a user requesting to execute it. Moreover, this action is executed like a callback. In my application_controller, when some condition is met, the controller action is triggered and there is a redirection. In php, a simple guard like defining a guard and checking against it would be enough. Is there an equivalent thing in rails and if so, what is the best way to implement it ?
Thanx for reading and i appreciate your help :)
EDIT: I'm pasting some code to make it clearer, note that /town/addBuilding was an example, the controller names and actions below are differently named.
Now, that is the actual application controller code, it is part of a browser game that i'm coding.
def checkQuest
if TavernQuest.hasQuest(current_user)
quest = TavernQuest.getQuest(current_user)
if quest.end_time < Time.now # get quest info and check if the quest has been completed
TavernQuest.deleteQuest(current_user)
redirect_to :controller => 'tavern', :action => 'monsterAttack'
end
end
end
The tavern controller action is just the plain code that i want to execute, but only if the redirection happens inside the application controller.
It seems that you are trying to put logic into a controller which actually should belong in a model or a library.
Why do i say this: aside from the current_user and the redirect, all the code is more related to your model (where the knowledge should be) and not your controller. Your model knows when a user's quest is expired.
Example implementation:
class TavernQuest
def self.user_quest_is_expired?(user)
quest = getQuest(current_user)
if quest && quest.end_time < Time.now
TavernQuest.deleteQuest(current_user)
true
else
false
end
end
end
and in your controller you just need to write
redirect_to :controller => 'tavern', :action => 'monsterAttack' if TavernQuest.user_quest_is_expired?(current_user)
Put the addBuilding method under a line that starts with protected, as follows
protected
def addBuilding
#your code
end
Enjoy!
EDIT: In addition to this you might also wanna use the before_filter in your controllers... I'll post the exact syntax soon.
before_filter :addBuilding, :only => :method_name
method_name is the method from which :addBuilding can be accessed, no other method can access this method after adding in this line..
EDIT: Ok, so based on the info you provided, protected wont work since if we put your secret action under protected only the tavern controller will have access to it.
EDIT: Please consider using Sessions to check if the users have a valid session when they try to to execute the monsterAttack action..

Associate a URL with a resource within my application

When I visit http://my-application.com/posts/1 in my browser, Rails knows I'm looking for the Post with id = 1. How can I get my application to do this internally? I.e., I'd like a function (call it associate_with_resource) that takes a string containing a URL as its input and outputs the associated resource. For example:
>> associate_with_resource('http://my-application.com/posts/1')
=> #<Post id: 1, ... >
(I'd like to be able to use associate_with_resource throughout my application though -- not only in the console)
I think I'm looking for the ActionController::Routing::Routes.recognize_path method
You are right about ActionController::Routing::Routes.recognize_path and I would do it like this:
create a file lib/associate_with_resource.rb
module AssociateWithResource
def associate_with_resource(path)
url_hash = ActionController::Routing::Routes.recognize_path path
url_hash[:controller].classify.constantize.find(url_hash[:id])
end
end
class ActionController::Base
include AssociateWithResource
helper_method :associate_with_resource
end
class ActiveRecord::Base
include AssociateWithResource
end
Now you can call the associate_with_resource(path) from almost everywhere to get the resource belonging to a given path
When I visit http://my-application.com/posts/1 in my browser, Rails knows I'm looking for the Post with id = 1.
This is not correct.
In Rails 3, when you put this into routes.rb:
resources :posts
Then Rails will know that you have a controller named PostsController in the file app/controllers/posts_controller.rb. Rails will also know that in your PostsController class, you have seven methods that are intended to be action methods: index, new, create, show, edit, update, delete.
What you do in these action methods is entirely up to you. You may wish to retrieve and display a Post object, or not.

What's the correct way to run one controller action from another controller action without an HTTP redirect?

I'd like to be able to dispatch from one controller action to another conditionally, based on a combination of query parameters and data in the database.
What I have right now is something like:
class OldController < ApplicationController
def old_controller_action
if should_use_new_controller
new_params = params.dup
new_params[:controller] = "new_controller_action"
redirect_to new_params
return
end
# rest of old and busted
end
end
class NewController < ApplicationController
def new_controller_action
# new hotness
end
end
This works just fine, but it issues an HTTP redirect, which is slow. I'd like to be able to do this same thing, but within the same HTTP request.
Is there a clean way to do this?
Edit: The bounty will go to someone who can show me a clean way to do this that leaves the controllers and their actions relatively untouched (other than the redirect code itself).
Instead of calling code across actions, extract the code to lib/ or something, and call that code from both controllers.
# lib/foo.rb
module Foo
def self.bar
# ...
end
end
# posts_controller
def index
Foo.bar
end
# things_controller
def index
Foo.bar
end
Create an instance of the controller class:
#my_other_controller = MyOtherController.new
Then call methods on it:
#my_other_controller.some_method(params[:id])
I prefer the module idea, but this should do the trick.
You can also pass parameters as a whole from another controller:
#my_other_controller.params = params
I suspect you want option 3, but lets go through the some alternatives first
Option 1 - Push the controller selection logic into a helper that inserts the right link into your view. Benifits - controllers remain clean, Cons - if decision logic depending on submitted values this approach won't work. If URL is being called by external websites then this won't work.
Option 2 - Push the logic back into your model. Pro's - keeps controller clean. Cons - doesn't work well if you've got lots of sesson, params or render / redirect_to interaction.
Option 3 - Stay within the same controller. I suspect you are trying to replace some existing functionality with some new functionality, but only in some cases. Pro's - Simple and have access to everything you need. Cons - only works if it makes sense to use the same controller i.e. you're working with the same entity such as user, place or company.
Lets look an an example for option 3. My links controller has totally diferent behavour for admins than other users ...
class LinksController < ApplicationController
#...
def new
#Check params and db values to make a choice here
admin? ? new_admin : new_user
end
#...
private
def new_admin
#All of the good stuff - can use params, flash, etc
render :action => 'new_admin'
end
def new_user
#All of the good stuff - can use params, flash, etc
render :action => 'new_user'
end
end
If two controllers are trying to do the same thing, there's a very good chance this should be in a model. Take a good look at your design and -- I'm sorry I don't know your experience level with MVC -- read up on thin controller techniques:
http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model
http://www.robbyonrails.com/articles/2007/06/19/put-your-controllers-on-a-diet-already
http://andrzejonsoftware.blogspot.com/2008/07/mvc-how-to-write-controllers.html
If the problem is that you need the other controller to do the render, then maybe the route should have pointed there to begin with, and still the skinny controller technique should save the day.
If extracting the common code between controllers into a module doesn't work for you, I would use Rack middleware. I haven't seen code that uses ActiveRecord within middleware but I don't know of any reason why it shouldn't be possible since people have used Redis and the like.
Otherwise I think your only option would be to restart processing of the request with something like (untested, pseudo example):
env['REQUEST_URI'] = new_controller_uri_with_your_params
call(env)
This is similar to how integration tests are implemented. But I don't know if everything from call until you hit a controller is idempotent and safe to rerun like this. You could trace through the source and see. But even if it's ok now, it might break in any future version of rails or rack.
Using middleware would avoid this by letting you intercept the request before it's been run. You should still be able to share code with your rails application by extracting it out into common modules included in both places.
Honestly I think just doing the simple thing of factoring the common controller code is likely cleaner, but it's hard to know without the details of your situation so I thought I'd go ahead and suggest this.
Do this:
class OldController < ApplicationController
def old_controller_action
if should_use_new_controller
new_controller_action
end
# rest of old and busted
end
end
and the new controller
class NewController < OldController
def new_controller_action
# new hotness
end
end

Resources