Ruby on Rails - Use date to trigger action - ruby-on-rails

I have an object that has an end date which is created using the callback after_create.
I'm wondering where the best place is to include the logic which would trigger an action when the object expires. (Time.now.in_time_zone > Object.end_date)
I'd like to create a method that checks whether an object has the attribute repeat as true or false. If it's true and the current date is passed the end date of the object, it should add 7 days to the end date of the object.
I have a method which checks whether the object is still valid but it's a boolean and I use it multiple times in the view so if I include it there, it gets executed multiple times before the view is even updated and I end up adding too many days to the end date.
Is it possible to have an action in your view file which is called automatically when the page loads if it falls under a certain condition? I'm guessing this is bad practice because I've read a few articles about avoiding too much logic in your view files.
I'm sure there are many ways of doing this so could you please let me know what methods you've used to overcome this?
Let me know if you need any more information.

You could consider using a Controller before_action, which would be called as you mentioned before the page loads.
Adapting the example in the Rails docs at http://guides.rubyonrails.org/action_controller_overview.html#filters
class MyObjectsController < ActionController::Base
before_action :object_expire, only: [:show]
private
def object_expire
# perform some logic here
if #my_object.expired?
#expired_result = #my_object.do_repeat
end
end
end
EDIT - limit before_action to show actions only

Related

Updating attribute on get request?

I have a situation where I would like to update an attribute when a certain third party fetches data from my api's endpoint.
Currently, I've set this up as follows
module Api
module V1
class ListingsController < ApplicationController
http_basic_authenticate_with name: "third_party_user", password: "secret", except: :index
before_action :update_status, only: [:publishable_listings]
def publishable_listings
#listings = Listings.where(to_publish: true)
end
private
def update_status
listings = Listings.where(to_publish: true).update_all(published: true)
end
end
end
end
and this is just the route
...
get 'publishable_listings' => "listings#publishable_listings"
...
Is this considered bad practice or could there be an alternative way to accomplish this?
Basically, this assumes that the only GET requests coming to publishable_listings would be from third_party_user and if anyone else would be able to make a GET this would be problematic since it would update the record without actually being published.
I think this question would fit better into https://codereview.stackexchange.com/tags/ruby.
Is this considered bad practice or could there be an alternative way to accomplish this?
Basically, this assumes that the only GET requests coming to publishable_listings would be from third_party_user and if anyone else would be able to make a GET this would be problematic since it would update the record without actually being published.
With your current architecture using basic auth, I don't see a different way of implementing this. Assuming that only your third party will ever know the password, this might be fine.
However, if you would introduce a concept of user, you would be able to only mark publishing for a user as published / read. You could implement this with a many to many relationship.
Another way of implementing this could be to just use curser based pagination and store the latest cursor in your client. This way, your client could go back and it's easier to debug and reason about.
https://slack.engineering/evolving-api-pagination-at-slack/
A few more suggestions
To keep your controller simple, you should only have the basic REST methods in your controller (index, show, new, create, edit, update, delete). In your case, you could have a PublishableListingsController with a show method instead of ListingsController with a publishable_listings.
See this great article for more details http://jeromedalbert.com/how-dhh-organizes-his-rails-controllers/.
Also the assignment to listings here is not really used and I would recommend to do this not an a before action, because, if your second query fails, you will end up with listing which are already marked published but where never actually received.
def update_status
listings = Listings.where(to_publish: true).update_all(published: true)
end
Ideally you want to do this in one operation or transaction.
def show
#listings = Listings.where(to_publish: true)
#listing.update_all(published: true)
end

Rails - Show changes after PATCH

I would like to show the users which fields have been modified following his PUT/PATCH request
For example, I have a big "project" form, with several fields, but my user decided to only update the deadline and the project name. After he clicks the "save" button, I would like to show some message saying "You have successfully updated : name, deadline"
If it's possible, I would like some generic code that would detect the update action and infer the variable name. By generic I mean, I want to implement this in my ApplicationController, so I don't have to add code in every controller#update action
Let's look at this sample code from controllers/entreprise_controller.rb
def update
if #entreprise.update_attributes(entreprise_params)
redirect_to #entreprise, notice: "Entreprise éditée"
else
render 'edit'
end
end
Here's an idea of steps to reach my goal. Could you help me with each of these ? Or suggest a better approach ?
Detect that we are doing a CRUD update action, for example from the action name in the code, that should always be update (how can I read, from the code, the name of the action being executed ?)
Guess the variable name : here #entreprise, it can be inferred from the file for example (or maybe calling self.class and doing some regex ?)
Save the list of variables that are going to be updated (maybe some tricks involving before_action and after_action and dirty_tracking ? See my edit.)
Provide this list as a GET argument for the redirect to #entreprise (should be pretty straightforward)
Show this list to the user (this part is OK for me)
EDIT concerning Dirty Tracking
Mongoid already implements this. However the main problem is getting the intermediate variable before it is saved. Each controller instanciates the variable like #entreprise during a before_action callback. And if I add a before_action in my ApplicationController, it will fire before, so no variable is available yet. And as regards a possible after_action in ApplicationController, the doc says "Any persistence operation clears the changes." so it's dead already. I probably cannot get away without rewriting every controller ?
Dirty Tracking & controller in a nutshell :
prepend_before_action
before_action of ApplicationController
before_action of EntrepriseController (which includes set_entreprise, where the variable #entreprise is defined so as to proceed with update)
If we can get a callback to HERE, it would let us inspect the object for dirty tracking information, as the object exist in memory, we can use #entreprise.attributes=entreprise_params and look at the dirty info (where entreprise_params is the strong parameters for #entreprise)
action : on success, it will store the info in the DB, and we lose dirty tracking info
after_action

Setting per request Time.zone based on a model instance and keeping DRY

I have the following code:
around_filter :event_time_zone
def event_time_zone(&block)
#event.present? ? Time.use_zone(#event.venue.time_zone_name, &block) : yield
end
I placed it in my ApplicationController.
The problem is that the instance variable #event is never set when this around_filter is called. It is however available before the action in every resource controller ( by use of CanCan), however I do not want to sprinkle a bunch of around_filter calls in all those controllers.
Is there a simple way that I can use this above code but not repeat myself everywhere?
I could do a user based time_zone, but what if a user is managing events in different time zones? The only relevant date to display would only ever be in the events time zone.
I think your issue has to do with the ordering that the filters are firing, i.e. the around_filter is running before your filter that sets the #event variable
I personally would do this per controller as needed, your application will eventually have many controller/actions that have nothing to do with events.
Setup user based timezone setting/handling which is handled in the application controller
Set the timezone based on #event as needed to override user setting
You may be interested in this blog post I did on rails timezones (see also the example code on github) - http://jessehouse.com/blog/2013/11/15/working-with-timezones-and-ruby-on-rails/

Package instance variables in rails controllers?

I'm overwhelmed by managing instance variables in controllers so am thinking if there's a better way to manage them.
My situation is, I'm having a PagesController that handles the front page rendering. In the front page, I have multiple small forms that originally belong to different controllers (For example, make a new post form, and there's a PostsController dedicated for it but for convenience you can make an easy post just at the front page.) and they all need their corresponding instance variable to hold the form (e.g. new post form needs a #post object).
It turns out to me, that I have to manually add these instance variables into my PagesController#index in order to make the forms work, so many lines become just
#post = Post.new # similar for other objects
#some_other_var = OtherController.new # another one
#one_more = AnotherController.new # again
# even more #variables here when the website is big
If this doesn't seem bad enough, think about when create or edit action fails (e.g. does not pass validation) and we need to render the previous page. We need to add these lines AGAIN. Actually we need to include ALL THESE VARIABLES whenever there's a render.
It seems very cumbersome to manually type such code to every action that needs them and it's so easy to just miss one or two of them when the website gets complicated.
So I'm wondering if there's a better way to manage such variables so that we only need to include them once instead of writing the same code every time.
You can create a before_filter something like:
class ApplicationController < ActionController::Base
...
...
protected
def instance_variables_for_form
#post = Post.new # similar for other objects
#some_other_var = OtherController.new # another one
#one_more = AnotherController.new # again
# even more #variables here when the website is big
end
end
and use it like:
class PagesController < ApplicationController
before_filter :instance_variables_for_form, only: [:action]
...
...
end
and then you can call it explicitly too from any action whenever needed.
If those variables can be logically grouped, you should consider putting them into Presenter objects.
Here is a good blog post explaining the idea: http://blog.jayfields.com/2007/03/rails-presenter-pattern.html

geocode_ip_address except specific controllers

I have added geocode_ip_address in ApplicationController so I can get the user location info from their session.
I have some controllers that I don't want them to be checked against Geokit. It really slows application and there's no need for geo check there.
Since its called like geocode_ip_address and not as a before_filter I'm not able to use skip_before_filter
Any idea?
it actually uses store_ip_location filter so you can put
skip_before_filter :store_ip_location
That being said it stores the result of the geo code inside a cookie that is being checked before making a service call so the subsequent calls should not impact performance that much
You can selectively NOT INVOKE the code based on the controller you're in like so:
unless ["controller1", "controller2", "etc"].member?(params[:controller])
geocode_ip_address
end
Put the names of the controllers you don't want the code to run for in the list and it won't be invoked in those controllers.
You could also create a constant that's a list of controllers like this:
CONTROLLERS_TO_NOT_FILTER_BY_GEOCODE = ["controller1", "controller2", "etc"]
unless CONTROLLERS_TO_NOT_FILTER_BY_GEOCODE.member?(params[:controller])
geocode_ip_address
end

Resources