I found time travelling gems like Timecop, to test time dependent features. Is it possible and/or sensible to use this in development as well?
If not: is there another time traveller gem, suitable for development? I couldn't find one.
Yes, sure you can. Here's some example code from an app I've built, where you can set a date via the admin area, and then browse the site as if it's that date, for the duration of your session:
in app/controllers/concerns/time_travel_filters.rb:
# This allows us to set a different date
# in the admin area, and use TimeCop to process each
# request as being on that different date - useful for
# testing different phases of the challenge.
module TimeTravelFilters
extend ActiveSupport::Concern
included do
if Rails.env.development? || Rails.env.staging?
around_filter :time_travel_for_request
end
end
def time_travel_for_request
time_travel
yield
time_travel_return
end
def time_travel
logger.info 'TIME TRAVEL START'
if session[:timecop_date]
Timecop.travel(session[:timecop_date])
else
Timecop.return
end
end
def time_travel_return
logger.info 'TIME TRAVEL RETURN'
Timecop.return
end
end
and then you just need to include TimeTravelFilters in your controllers that you want to use it.
You'll need to set session[:timecop_date] in order for it to take effect - I do this through a form on a page in my admin area, but you can do it however you want. Returning to the current time is as simple as deleting that session key.
Related
I have a Rails 3.2 application, and I want to use one database for many clients and one application. So for every model I have created a field called account_id, now I want to add a global scope for filtering the row in the base of the account_id of the logging user(account_id is a session param). So in initialize I have created a file and put these code
module ActiveRecord
# = Active Record Named \Scopes \
module Scoping
module Default
module ClassMethods
def unscoped #:nodoc:
return (block_given? ? relation.scoping { yield } : relation).where(account_id: Thread.current['account_id'].id)
end
def default_scope(scope = {})
scope = Proc.new if block_given?
if scope.kind_of?(Array) || scope.is_a?(Hash)
scope.merge!({:conditions=>{account_id:Thread.current['account_id'].id}})
end
self.default_scopes = default_scopes + [scope]
end
end
end
end
end
If I logged with user account_id=2 all is ok, but if in the same moment I logged on another browser or computer with account_id=3 ...I have many errors and on the log, I have seen that the application use account_id=2 but also account_id=3 at the same time.
Any solution? How I can rewrite default_scope(scope = {})? Other other idea?
Thread.current[] data is not unique per request. I used to have the same problem. By that time I had been using this gem https://github.com/steveklabnik/request_store. Hope it will help or at least give an idea.
Edit to make it more clear
Like the title tells how to use the awesome ActiveSupport::Testing::TimeHelpers#travel_to method inside my controller. Like in tests, I want to achieve something like this:
SomethingConroller < ApplicationController
def index
travel_to some_date do
# some stuff that it depends on the current_date
end
end
end
Tried include the module:
include ActiveSupport::Testing::TimeHelpers
but I got:
uninitialized constant ActiveSupport::Testing
Hoping that the date traveled to will be applied to the view, view_helpers, controller_action
If you must, then add require 'active_support/testing/time_helpers' at the top of the file.
Although I've no idea what you're doing with that code. Try this instead:
SomethingConroller < ApplicationController
def index
#instance = SomeModel.find_by_date(12.days.ago)
end
end
I think you're messing up your concepts here. You should use time travel to put tests in a certain time so that you can test a scenario at that new time. For example, lets say users have to renew their subscription after one year.
create the user(now).
travel to the time a year from now
Make sure that when the user logs in at travelled to time they are duly notified.
To manipulate date for queries, use 12.days.ago, 12.days.from_now, this can be used with seconds,days, minutes, years
I've built a rails app that helps parents keep track of their infants’ sleep. For it to work properly I've had to support different time zones. To avoid annoying the user with time zones, I've created a little javascript that adds a hidden field to the login form, including the timezone offset. Here's the code
var timeZoneField = $("input[name='user_tz_offset']");
if (timeZoneField.length) {
var browserDate = new Date();
var timeZoneOffsetSeconds = (browserDate.getTimezoneOffset() * 60) * -1;
$(timeZoneField).val(timeZoneOffsetSeconds);
}
With the data from that field I set the Time.zone to whatever city corresponds with that offset. Something like this generates the timezone
user_time_zone = ActiveSupport::TimeZone.[](params[:user_tz_offset].to_i)
session[:user_time_zone] = user_time_zone
Finally, I set the time zone in the ApplicationController.
def set_user_time_zone
if (session[:user_id])
Time.zone = session[:user_time_zone]
else
Time.zone = config.time_zone
end
end
All this relies on the login functionality, which I wrote myself. However, I knew that I would need to use a better user management system later, as my own code is neither well done or particularly secure (I was focusing on other functionality first).
Now, I've installed devise and it works well to log in and log out, most other functions of the site work as well. But I don't know how to approach the time zone support with devise as my user management system.
One idea is to override the SessionsController in Devise, add a check for that hidden time zone field and add its value to the user_session. But I feel apprehensive about doing so, it feels like a bad idea.
Is there a better way to add this functionality, without forcing the user to add time zone information during registration?
Thank you!
I manage a scheduling software and we also check the browser's timezone but then just save it in the database and call it with a before_filter in the application controller.
#Application Controller
before_filter :set_time_zone
private
def set_time_zone
if current_user
Time.zone = current_user.time_zone if current_user.time_zone
end
end
After about eight hours of trial and error I have come up with a solution that works for now. Perhaps this may be of interest to someone with a similar setup.
I started by adding a column to the users table and corresponding attribute in the model – session_tz_offset.
Then I started hacking around with Warden callbacks. What worked for me was to put a helper method in the ApplicationController, and call that with a before filter like this:
before_filter :authenticate_user!, :set_session_tz_offset_for_user
helper_method :set_user_time_zone, :set_session_tz_offset_for_user
def set_session_tz_offset_for_user
Warden::Manager.after_authentication do |user, auth, opts|
if (params[:user])
user.session_tz_offset = params[:user][:session_tz_offset]
user.save
end
end
end
The after_authentication callback fires several times during login, why is unknown to me. Not all of these calls have a params[:user] field, and if I didn't check for it, my application crashed with a undefined method [] for nil:NilClass error.
When the session_tz_offset is set, my other controllers use another helper method, also defined in ApplicationController to set Time.zonefor the current request:
def set_user_time_zone
if (user_signed_in?)
if(user_session[:time_zone])
Time.zone = user_session[:time_zone]
else
user_session[:time_zone] =
ActiveSupport::TimeZone.[](current_user.session_tz_offset)
Time.zone = user_session[:time_zone]
end
else
Time.zone = config.time_zone
end
end
My DBs of production and development are somewhat in sync, so development can read images from production paths (S3).
The problem is when I delete, update or create records on development, it affects the S3 image.
I don't want this behavior to happen on development but it should happen on production.
Is there an option to turn paperclip into readonly mode? I still want to see the images from S3 (and not 404 images).
I saw the :preserve_files option which is good to protect delete. Is there an option to protect overwrite / disable upload?
Well, patchy, ugly and unsafe for future versions, but does the job for the meantime.
config/initializers/paperclip.rb
if Rails.env.development?
module Paperclip
class Attachment
def assign uploaded_file
end
def save
end
def clear(*)
end
def destroy
end
private
def post_process(*)
end
def post_process_styles(*)
end
def post_process_style(*)
end
def queue_some_for_delete(*)
end
def queue_all_for_delete
end
def after_flush_writes
end
end
end
end
Assuming you need to use production data in development, I think it would make a lot more sense to create a "User Policy" where a user can only read certain S3 resources. Then change your environment variables accordingly
https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-policies-s3.html
Then, you can handle errors in development (S3 client should fail if you try to update with read only privileges). This ensures you can't touch anything in production
For example (pseudocode),
if Rails.env.development?
// do not update
else
Model.attachment.assign()
end
There are several stages to this, and as I am relatively new to rails I am unsure if I am approaching this in the best way.
Users follow Firms, Firms applications open and close on certain days. If a user follows a firm I would like them to automatically get an email when a) the firms application opens, b) a week before the firms applications close, c) on the day that the firms applications close.
I have tried using named scope. I have the following model method (I presume this will need a little work) setting each firms scope, depending on the date.
model firms.rb
def application_status
if open_date == Today.date
self.opening = true
else
self.opening = false
end
if ((close_day - Today.date) == 7)
self.warning = true
else
self.warning = false
end
if close_day == Today.date
self.closing = true
else
self.closing = false
end
end
I would like this method to be called on each firm once a day, so that each firm has the appropriate scope - so I have tried using the whenever gem (cron) and the following code. Running the above model method on each firm.
Schedule.rb
every 1.day do
runner "Firm.all.each do |firm|
firm.application_status
end"
end
Then for each of the scopes opening, warning, closing i have a method in the whenever schedules file, For simplicity I shall show just the opening methods. The following queries for all firms that have had the opening scope applied to them, and runs the application_open_notification method on them.
Schedule.rb
every 1.day do
runner "Firm.opening.each do |firm|
firm.application_open_notification
end"
end
This calls the following method in the Firm.rb model
def application_open_notification
self.users.each do |user|
FirmMailer.application_open(user, self).deliver
end
end
Which in turn calls the final piece of the puzzle... which should send the user an email, including the name of the firm.
def application_open(user,firm)
#firm = firm
#user = user
mail to: #user.email, subject: #firm' is now accepting applications'
end
end
Is this a viable way to approach this problem? In particular I am not very familiar with coding in the model.
Many thanks for any help that you can offer.
I'll guess that opening, warning and closing are database fields, and you have scopes like:
class Firm < ActiveRecord::Base
scope :opening, :where => { :opening => true }
# etc
end
There is a general rule for database (and, well, all storage): don't store things you can caculate, if you don't have to.
Since an application's status can be dermined from the day's date and the open_date and close_day fields, you could calculate them as needed instead of creating extra fields for them. You can do this with SQL and Active Record:
scope :opening, :where { :open_date => (Date.today .. Date.today+1) }
scope :warning, :where { :close_day => (Date.today+7 .. Date.today+8) }
scope :closing, :where { :close_day => (Date.today .. Date.today+1) }
(Note that these select time ranges. They may have to be changed depending on if you are using date or time fields.)
But there is another issue: what happens if, for some reason (computer crash, code bug etc) your scheduled program doesn't run on a particular day? You need a way of making sure notices are sent eventually even if something breaks. There are two solutions:
Write your schedule program to optionally accept a date besides today (via ARGV)
keep flags for each firm for whether each kind of notice has been sent. These will have to be stored in the databse.
Note that scopes aren't necessary. You are able to do this:
Firm.where(:open_date => (Date.today .. Date.today+1)).each do |firm|
#...
end
but the scope at least encapsulates the details of identifying the various sets of records.