I'm trying to write a monkey patch to add a method for created_at.
I created a date_time_extras.rb file and put it in the lib directory, with contents:
class DateTime
def beginning_of_hour
change(:min => 0)
end
end
From the console I do:
record.created_at.beginning_of_hour
But this yields method missing errors. It looks like created_at isn't a datetime? Because DateTime.new.beginning_of_hour works, and record.created_at.class yields ActiveSupport::TimeWithZone.
So how do I write a monkey patch for created_at type dates?
I'm using rails version 3.0.10.
Update
Also tried
module ActiveSupport
class TimeWithZone
def beginning_of_hour
change(:min => 0)
end
end
end
to no avail
Did you try declaring it in class Time?
class DateTime
def beginning_of_hour
change(:min => 0)
end
end
TimeWithZone looks like it delegates its time object to Time not DateTime.
Also TimeWithZone contains more than just the #time object so you would have to do something like
module ActiveSupport
class TimeWithZone
def beginning_of_hour
self.time.change(:min => 0)
end
end
end
But I'm not 100% sure on that code.
Related
I'm using https://github.com/kenn/active_flag and https://github.com/chanzuckerberg/sorbet-rails
This is what its rbi looks like:
module ActiveFlag
extend ActiveSupport::Concern
end
class ActiveFlag::Definition
def column; end
def human(key, options = nil); end
def humans; end
def initialize(column, keys, klass); end
def keys; end
def maps; end
def pairs; end
def set_all!(key); end
def to_array(integer); end
def to_i(arg); end
def to_value(instance, integer); end
def unset_all!(key); end
end
class ActiveFlag::Railtie < Rails::Railtie
end
class ActiveFlag::Value < Set
def method_missing(symbol, *args, &block); end
def raw; end
def set!(key, options = nil); end
def set(key); end
def set?(key); end
def to_human; end
def to_s; end
def unset!(key, options = nil); end
def unset(key); end
def unset?(key); end
def with(instance, definition); end
end
module ActiveFlag::ClassMethods
def flag(column, keys); end
end
class ActiveRecord::Base
extend ActiveFlag::ClassMethods
end
The last bit (extending AR::Base) I added, the rest srb rbi gems generated automatically.
To actually use Active Flag, I then do this in my model:
flag :visible_to, [:employee, :manager, :admin]
visible_to is an integer column. Sorbet Rails has already typed it as such:
sig { returns(Integer) }
def visible_to; end
sig { params(value: Integer).void }
def visible_to=(value); end
sig { returns(T::Boolean) }
def visible_to?; end
I could change this definition, but it's an autogenerated file and I assume the changes will get lost. So the next thing I tried was adding a sig directly above where the method gets used:
sig { returns(ActiveFlag::Value) }
def visible_to; end
flag :visible_to, [:employee, :manager, :admin]
The problem here is that Sorbet fails because the method I've "defined" doesn't return anything. Which I know is fine, because it'll get overriden when flag is called (it uses define_method internally), but I don't know how to communicate this to Sorbet.
Returning value that does not conform to method result type https://srb.help/7005
54 | def visible_to; end
^^^^^^^^^^^^^^^^^^^
Expected ActiveFlag::Value
Method visible_to has return type ActiveFlag::Value
54 | def visible_to; end
^^^^^^^^^^^^^^
Got NilClass originating from:
54 | def visible_to; e
So my question is. What's the best way to tell Sorbet that this method will return an ActiveFlag::Value once it gets defined, ideally without making a manual change to an autogenerated file?
btw. I tried to see how types for enum in Rails are handled... it doesn't look like that's been done on sorbet-typed yet. Possibly for the same reason.
When you want to override a generated rbi file you can create a second rbi file for the same class in your workspace and move the methods in there. Sorbet will handle merging multiple definitions files for you. We keep these files in a separate sorbet/custom directory next to the generated files; others keep the rbi files next to the rb files in their application directory.
As to enums, Sorbet does not natively support enum literal types so that's likely why.
You could implement a custom plugin for sorbet-rails to generate methods added by active_flag and remove the sigs generated wrongly. Here is the documentation for it:
https://github.com/chanzuckerberg/sorbet-rails/blob/master/README.md#extending-model-generation-task-with-custom-plugins
I'm facing a problem with Loading a Constant in Rails console (rails console). Here how my structure look like this
- app
- controllers
- models
- earning
- daily_earning.rb
- monthly_earning.rb
- weekly_earning.rb
- yearly_earning.rb
- views
Some more information
I also have a rake which look like this
namespace :past_days do
desc "Past 7 Days Earning"
task :earning => :environment do
puts $:.select { |i| i=~ /models/ }.to_yaml
7.downto(1).each do |i|
start_date = i.days.ago.beginning_of_day
puts "====== Dumping past #{start_date.strftime('%A')} earning ====="
end_date = start_date.end_of_day
Performer.top_daily_earners(start_date,end_date)
puts "====== Dumped #{start_date.strftime('%A')} earning !!! ======="
puts
end
end
end
And the top_daily_earners method look like this If you check this #klass = DailyEarning
def top_daily_earners(start_date=nil,end_date=nil)
unless start_date or end_date
date = 1.day.ago
#start_date,#end_date = date.beginning_of_day,date.end_of_day
end
if start_date and end_date
#start_date,#end_date = start_date,end_date
end
#klass = DailyEarning
#earning_performers = retrieve_earnings
puts "COUNT -----"
puts #earning_performers.count
puts ""
store_earning
end
Question :
Now when I run rake task bundle exec rake past_days:earning (Rake run without any error) all work fine but when I run this
rails console see attach screenshot
I get errors NameError: uninitialized constant DailyEarning and I have manually require the file as can be seen the above screenshot
So the POINT of all the above question is why the error on rails console (NameError: uninitialized constant DailyEarning) and why not the error in
rake task
Attaching DailyEarning Code based on #dtt comment
puts 'DailyEarning'
class DailyEarning
include Mongoid::Document
store_in session: "writeable"
field :performer_id, :type => Integer
field :user_id,:type => Integer
field :stage_name,:type => String
field :full_name,:type => String
field :start_date,:type => DateTime
field :end_date,:type => DateTime
field :amount,:type => BigDecimal
before_create :other_details
## Please avoid using default scope because it AFAIK it make the date parameter as static
class << self
def default_scoping
where(:start_date.gte => 1.day.ago.beginning_of_day).and(:end_date.lte => 1.day.ago.end_of_day)
end
end
private
def other_details
## Fetch from Mongo Instead of Mysql to avoid the slow sql query
performer_source = PerformerSource.where(performer_id: performer_id).only([:stage_name,:user_id]).first
self.user_id = performer_source.user_id
self.stage_name = self.stage_name
#self.full_name = self.full_name
end
end
My understanding is that to autoload a model in a folder you would need to namespace it:
to autoload the model in app/models/earning/daily_earning.rb
class Earning::DailyEarning
end
it may be that instead you could use:
module Earning
class DailyEarning
end
end
I have two tables for checking views (visits of the page) - views of pic (PhotoView) in gallery and photographers(PhotographerView).
Because these two models (and tables) are the same, I want to create a model for them - something like:
class Func < ActiveRecord::Base
def self.check_views(model_view, data)
last_view = model_viewView.where('ip_address = ? AND request_url = ?', request.remote_ip, request.url).order('created_at DESC').first
unless last_view
model_view+View.new(...).save
model_view.increment_counter(:views, data.id)
else
if (DateTime.now - last_view.created_at.to_datetime) > 1.day
model_view+View.new(...).save
model_view.increment_counter(:views, data.id)
end
end #comparing dates
end
end
and call this method like:
#photo = Photo.find(params[:id])
Func.check_views('Photo', #photo)
When I try use it with the way above, I'll get the error undefined method `check_views' for Func(Table doesn't exist):Class
Could you give me a help, how to make it work?
Thank you
You can use ActiveRecord::Concern and modules to move the common functionality into one place as follows:
module CheckViews
extend ActiveSupport::Concern
module ClassMethods
# all class methods go here, if you don't have any just leave it blank
end
def check_views(data)
last_view = where('ip_address = ? AND request_url = ?', request.remote_ip, request.url).order('created_at DESC').first
unless last_view
##views_class.new(...).save
increment_counter(:views, data.id)
else
if (DateTime.now - last_view.created_at.to_datetime) > 1.day
##views_class.new(...).save
increment_counter(:views, data.id)
end
end #comparing dates
end
end
class Photo < ActiveRecord::Base
include CheckViews
end
you can now do the following:
#photo = Photo.find(params[:id])
#photo.check_views
I'd be very tempted to do this as a module extending the classes which want the Views functionality. Something like the following ought to work; but it's entirely untested and entirely unlike anything I've ever done before so it may be completely buggy. Fair warning.
module CheckViews
def self.extended(host_class)
host_class.class_variable_set("##views_class", "#{host_class}View".constantize)
end
def check_views(data)
last_view = where('ip_address = ? AND request_url = ?', request.remote_ip, request.url).order('created_at DESC').first
unless last_view
##views_class.new(...).save
increment_counter(:views, data.id)
else
if (DateTime.now - last_view.created_at.to_datetime) > 1.day
##views_class.new(...).save
increment_counter(:views, data.id)
end
end #comparing dates
end
end
class Photo < ActiveRecord::Base
extend CheckViews
...
end
(extend adds all the instance methods of the target Module as class methods of the calling class; so Photo gains Photo.check_views(data), and self in that function is the class Photo.)
Trying to rewrite an old alias_method_chain to add a filter on outgoing emails, and it isn't working. I'm pretty sure I've leaving something out/missing something, but I don't know what.
This file is in /lib/outgoing_mail_filter.rb, which is loaded with config/initializers/required.rb
Here's the old code that worked under Rails 2:
class ActionMailer::Base
def deliver_with_recipient_filter!(mail = #mail)
unless 'production' == Rails.env
mail.to = mail.to.to_a.delete_if do |to|
!(to.ends_with?('some_domain.com'))
end
end
unless mail.to.blank?
deliver_without_recipient_filter!(mail)
end
end
alias_method_chain 'deliver!'.to_sym, :recipient_filter
end
And here's my current attempt at re-writing it:
class ActionMailer::Base
module RecipientFilter
def deliver(mail = #mail)
super
unless 'production' == Rails.env
mail.to = mail.to.to_a.delete_if do |to|
!(to.ends_with?('some_domain.com'))
end
end
unless mail.to.blank?
deliver(mail)
end
end
end
include RecipientFilter
end
When I run my tests, it doesn't even look like this is being called or anything. Any help is appreciated
I'm using mail_safe to rewrite emails in the development environment, highly recommended. You could look into it for inspiration if it doesn't fit your bill, the code is very simple.
The following code is extracted from /lib/mail_safe/rails3_hook.rb and should do what you want:
require 'mail'
module MailSafe
class MailInterceptor
def self.delivering_email(mail)
# replace the following line with your code
# and don't forget to return the mail object at the end
MailSafe::AddressReplacer.replace_external_addresses(mail) if mail
end
::Mail.register_interceptor(self)
end
end
Alternate version, registering with ActionMailer::Base instead of Mail (thanks to Kevin Whitaker for letting me know it's possible):
module MailSafe
class MailInterceptor
def self.delivering_email(mail)
# replace the following line with your code
# and don't forget to return the mail object at the end
MailSafe::AddressReplacer.replace_external_addresses(mail) if mail
end
::ActionMailer::Base.register_interceptor(self)
end
end
I've been working on a rails project where I am needed to serialize permissions for user roles and store in the database. As far as that goes I'm all good. Now my problem comes when I want to modify the serialized data from a rails generated form.
I acted on instinct and tried with the expected behavior.
That would be to use something like this:
f.check_box :permissions_customer_club_events_read
But as no getters or setters exist for the serialized data, this doesn't work (obviously :p). Now I wonder how I would go about tackling this problem and the only thing that comes to mind is dynamically generating getter and setter methods from my serialized hash.
Example:
def permissions_customer_club_events_read=(val)
permissions[:customer][:club][:events][:read] = val
end
def permissions_customer_club_events_read
permissions[:customer][:club][:events][:read]
end
Anyone understand what I'm getting at?
Here is my Model:
class User::Affiliation::Role < ActiveRecord::Base
require 'yajl'
class YajlCoder
def dump data
Yajl.dump data
end
def load data
return unless data
Yajl.load data
end
end
serialize :permissions, YajlCoder.new
after_initialize :init
def init
## Sets base permission structure ##
self.permissions ||= YAML.load_file("#{Rails.root}/config/permissions.yml")
end
end
I suggest you have a look at something like attr_bucket. Ostensibly, this can be used to solve some inheritance annoyances, but it will also solve your problem for you. Here is the essence.
It looks like you know what all your permissions are, but you want to serialize all of them into the same database field. But within your actual rails app, you want to treat all your permissions as if they were totally separate fields. This is exactly what a solution like attr_bucket will let you do. Let's take your example, you would do something like this:
class User::Affiliation::Role < ActiveRecord::Base
attr_bucket :permissions => [:permissions_customer_club_events_read, :permissions_customer_club_events_write, :permission_do_crazy_things]
after_initialize :init
def init
## Sets base permission structure ##
self.permissions ||= YAML.load_file("#{Rails.root}/config/permissions.yml")
end
end
Now you will be able to use permissions_customer_club_events_read, permissions_customer_club_events_write, permission_do_crazy_things as if they were separate database fields (this includes using them in forms etc.), but when you actually save your objects all those fields would get 'bucketed' together and serialized into the :permissions field.
The only caveat is the serialization mechanism, I believe attr_bucket will serialize everything using YAML, whereas you were using JSON. If this doesn't matter then you're golden, otherwise you might need to patch attr_bucket to use json instead of YAML which should be pretty straight forward.
Sorry if I did not understand the question ;)
You could have a customdata module, included in your model, and use method_missing:
module CustomData
def self.included(base)
base.instance_eval do
after_save :save_data
end
def method_missing(method, *args, &block)
if method.to_s =~ /^data_/
data[method] ? data[method] : nil
else
super
end
end
def data
#data ||= begin
#get and return your data
end
end
private
def save_data
end
end
With this method, you would have to use f.check_box :data_permissions_customer_club_events_read
It's not really complete, but I hope you get the idea ;)
attr_bucket seems like a good solution too.
This worked out for me in the end, this is how I solved it.
serialize :permissions, YajlCoder.new
after_initialize :init
def init
self.permissions ||= YAML.load_file("#{Rails.root}/config/permissions.yml")['customer']
build_attributes_from self.permissions, :permissions
end
private
def build_attributes_from store, prefix, path=[]
store.each do |k,v|
if v.class == Hash
build_attributes_from v, prefix, ( path + [k] )
else
create_attr_accessors_from prefix, ( path + [k] )
end
end
end
def create_attr_accessors_from prefix, path=[]
method_name = prefix.to_s + "_" + path.join('_')
class << self
self
end.send :define_method, method_name do
self.permissions.dig(:path => path)
end
class << self
self
end.send :define_method, "#{method_name}=" do |value|
self.permissions.dig(:path => path, :value => value)
end
end
And some monkey patching for hashes...
class Hash
def dig(args={})
path = args[:path].to_enum || []
value = args[:value] || nil
if value == nil
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
else
path.inject(self) do |location, key|
location[key] = ( location[key].class == Hash ) ? location[key] : value
end
end
end
end
Now getter and setter methods are generated for all of the serialized fields.