Messenger bot using Rails: setup for multiple pages - ruby-on-rails

I want to create a Messenger Bot used by different users for their Facebook pages. I created a rails app and use the facebook-messenger gem.
I successfully created the bot and it works when I do the set up for one page. Now, I follow the instructions to make my bot live on multiple Facebook Pages (see "Make a configuration provider" section).
I'm new to rails and I'm not sure where to put the class ExampleProvider? I put it in my config/application.rb file:
require_relative 'boot'
require 'rails/all'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module BotletterApp
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
config.paths.add File.join('app', 'bot'), glob: File.join('**', '*.rb')
config.autoload_paths += Dir[Rails.root.join('app', 'bot', '*')]
end
class BotProvider < Facebook::Messenger::Configuration::Providers::Base
def valid_verify_token?(verify_token)
bot.exists?(verify_token: verify_token)
end
def app_secret_for()
ENV['APP_SECRET']
end
def access_token_for(page_id)
bot.find_by(user_id: page_id).page_access_token
end
private
def bot
MyApp::Bot
end
end
Facebook::Messenger.configure do |config|
config.provider = BotProvider.new
end
end
Then I have my app/bot/setup.rb file to create the bot. I don't know how to use the provider I created in place of the ENV variables?
require "facebook/messenger"
include Facebook::Messenger
Facebook::Messenger::Subscriptions.subscribe(access_token: ENV["ACCESS_TOKEN"])
Facebook::Messenger::Profile.set({
get_started: {
payload: 'GET_STARTED_PAYLOAD'
}
}, access_token: ENV['ACCESS_TOKEN'])
I searched in the Rails documentation how to make it work but unfortunately could not find anything.
UPDATE:
Now I'm able to set up the bots for different pages. Unfortunately, the following line of my ConfigProvider is getting an error:
config/initializers/config_provider.rb
def bot
Rails.application.class.parent::Bot
end
I'm getting the following error:
NameError (uninitialized constant BotletterApp::Bot):
config/initializers/config_provider.rb:17:in bot'
config/initializers/config_provider.rb:7:inapp_secret_for'
Do you know how should I name my app?
My BotModule:
module BotletterApp
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
config.paths.add File.join('app', 'listen'), glob: File.join('**', '*.rb')
config.autoload_paths += Dir[Rails.root.join('app', 'listen', '*')]
end
end
UPDATE, it works with ::Application, here is the new file:
class ConfigProvider < Facebook::Messenger::Configuration::Providers::Base
def valid_verify_token?(verify_token)
ENV['VERIFY_TOKEN']
end
def app_secret_for(page_id)
ENV['APP_SECRET']
end
def access_token_for(page_id)
CoreBot.find_by_page_id(page_id).page_access_token
end
private
def bot
BotletterApp::Application
end
end
Facebook::Messenger.configure do |config|
config.provider = ConfigProvider.new
end
The problem is I get the following error unless my db query seems working (it works in the rails console):
ActiveRecord::StatementInvalid (SQLite3::SQLException: no such column:
page_id.id: SELECT "core_bots".* FROM "core_bots" WHERE
"page_id"."id" = ? LIMIT ?):

Moving to an answer for improved readability ;)
Regarding 'plain'... Instead of
def bot
BotletterApp::Application
end
use
def bot
Bot
end
or (it looks like you named your model containing all pages CoreBot(?) (assuming you have a typical ActiveRecord model in /app/models/core_bot.rb, I was assuming Bot)
def bot
CoreBot
end
Then you should be able to use the template code from the README.md
As for your latest problem: it seems like the access_token_for-method gets called with a hash, searching with something like {id: 1}. You might want to check where that value is coming from. I would suggest to take a few steps back, and stay closer to the template code.

Related

Rails 7 controller decorator uninitialised constant error in production only

I am getting the following error zeitwerk/loader/helpers.rb:95:in const_get': uninitialized constant Controllers::BasePublicDecorator (NameError)
This is an error in a local production console using rails c -e production but not an issue in development which works perfectly.
In an engine, CcsCms::PublicTheme, I have a decorator I am using to extend the controller of another CcsCms::Core engine and it is this decorator that is causing the error.
public_theme/app/decorators/decorators/controllers/base_public_decorator.rb
CcsCms::BasePublicController.class_eval do
before_action :set_theme #ensure that #current_theme is available for the
#header in all public views
private
def set_theme
#current_theme = CcsCms::PublicTheme::Theme.current_theme
end
end
This functionality is working perfectly in development but fails in production with an error as follows
The controller I am trying to decorate in the CcsCms::Core engine is CcsCms::BasePublicController.rb
module CcsCms
class BasePublicController < ApplicationController
layout "ccs_cms/layouts/public"
protected
def authorize
end
end
end
in the theme engine with the decorator I am trying to use I have a Gemfile that defines the core engine as follows
gem 'ccs_cms_core', path: '../core'
In the ccs_cms_public_theme.gemspec I am requiring the core engine as a dependency
spec.add_dependency "ccs_cms_core"
in the engine.rb I am requiring the core engine and loading the decorator paths in a config.to_prepare do block
require "deface"
require 'ccs_cms_admin_dashboard'
require 'ccs_cms_custom_page'
require 'ccs_cms_core'
require 'css_menu'
#require 'tinymce-rails'
require 'delayed_job_active_record'
require 'daemons'
require 'sprockets/railtie'
require 'sassc-rails'
module CcsCms
module PublicTheme
class Engine < ::Rails::Engine
isolate_namespace CcsCms::PublicTheme
paths["app/views"] << "app/views/ccs_cms/public_theme"
initializer "ccs_cms.assets.precompile" do |app|
app.config.assets.precompile += %w( public_theme_manifest.js )
end
initializer :assets do |config|
Rails.application.config.assets.paths << root.join("")
end
initializer :append_migrations do |app|
unless app.root.to_s.match?(root.to_s)
config.paths['db/migrate'].expanded.each do |p|
app.config.paths['db/migrate'] << p
end
end
end
initializer :active_job_setup do |app|
app.config.active_job.queue_adapter = :delayed_job
end
config.to_prepare do
Dir.glob(Engine.root.join("app", "decorators", "**", "*_decorator*.rb")) do |c|
Rails.configuration.cache_classes ? require(c) : load(c)
end
end
config.generators do |g|
g.test_framework :rspec,
fixtures: false,
request: false,
view_specs: false,
helper_specs: false,
controller_specs: false,
routing_specs: false
g.fixture_replacement :factory_bot
g.factory_bot dir: 'spec/factories'
end
end
end
end
Given that my decorator is given the same name as the controller it is decorating from the core engine but with the .decorator extension I am pretty certain that is everything hooked up correctly, as mentioned, this works perfectly in development but I am unable to start a rails console in a production environment due to this error.
It seems that the class_eval is failing somehow and I can only think that this may be a path issue but I can not figure it out
UPDATE
After quite a big learning curve, thank's muchly to #debugger comments and #Xavier Noria
answer it is clear that my issue comes down to Zeitworks autoload functionality
Rails guides here has an interesting and appealing solution to me
Another use case are engines decorating framework classes:
initializer "decorate ActionController::Base" do
> ActiveSupport.on_load(:action_controller_base) do
> include MyDecoration end end
There, the module object stored in MyDecoration by the time the
initializer runs becomes an ancestor of ActionController::Base, and
reloading MyDecoration is pointless, it won't affect that ancestor
chain.
But maybe this isn't the right solution, I again failed to make it work with the following
initializer "decorate CcsCms::BasePublicController" do
ActiveSupport.on_load(:ccs_cms_base_public_controller) do
include CcsCms::BasePublicDecorator
end
end
Generating the following error
zeitwerk/loader/callbacks.rb:25:in `on_file_autoloaded': expected file /home/jamie/Development/rails/comtech/r7/ccs_cms/engines/public_theme/app/decorators/controllers/ccs_cms/base_public_decorator.rb to define constant Controllers::CcsCms::BasePublicDecorator, but didn't (Zeitwerk::NameError)
So back to the solution provided here, thank's again for the answer below I tried the following which did work finally
config.to_prepare do
overrides = Engine.root.join("app", "decorators")
Rails.autoloaders.main.ignore(overrides)
p = Engine.root.join("app", "decorators")
loader = Zeitwerk::Loader.for_gem
loader.ignore(p)
Dir.glob(Engine.root.join("app", "decorators", "**", "*_decorator*.rb")) do |c|
Rails.configuration.cache_classes ? require(c) : load(c)
end
end
Problem here is that when lazy loading, nobody is referencing a constant called ...::BasePublicDecorator. However, Zeitwerk expects that constant to be defined in that file, and the mismatch is found when eager loading.
The solution is to configure the autoloader to ignore the decorators, because you are handling their loading, and because they do not define constants after their names. This documentation has an example. It needs to be adapted to your engine, but you'll see the idea.
For completeness, let me also explain that in Zeitwerk, eager loading is a recursive const_get, not a recursive require. This is to guarantee that if you access the constant, loading succeeds or fails consistently in both modes (and it is also a tad more efficient). Recursive const_get still issues require calls via Module#autoload, and if you ran one for some file idempotence also applies, but Zeitwerk detects the expected constant is not defined anyway, which is an error condition.

undefined local variable or method for method located in lib directory file

I have some code i've inherited and am in the process of upgrading it to Rails 3.1. I'm suuuuper close to done but I got a bug.
In Rails Console I run User.first and I get this error
undefined local variable or method `acts_as_userstamp' for #<Class:0x000000046bef50>
Now acts_as_userstamp is a method located on line two inside my User model
class User < ActiveRecord::Base
#TODO /lib is not loading??? or is it??? why this method not work in browser?
acts_as_userstamp
And is defined in a file called app/lib/model_modifications.rb.
Now I recently discovered that my app/lib folder was not being autoloaded in my application.rb file and I think that's been fixed...or has it? Is this file correct? Or no?
require File.expand_path('../boot', __FILE__)
require 'rails/all'
# evil outdated soap middleware, TODO: kill it with fire
# Does this have to be loaded BEFORE the first line???
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', "vendor", "soap4r"))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', "vendor", "plugins", "soap4r-middleware", "lib"))
# evil outdated soap middleware, TODO: kill it with fire
require 'soap4r-middleware'
require File.join(File.dirname(__FILE__), '..', 'app', 'lib', 'soap.rb')
if defined?(Bundler)
# If you precompile assets before deploying to production, use this line
Bundler.require *Rails.groups(:assets => %w(development test))
# If you want your assets lazily compiled in production, use this line
# Bundler.require(:default, :assets, Rails.env)
end
module MyappDev
class Application < Rails::Application
# startup the lib directory goodies <-- IS THIS CORRECT???
# config.autoload_paths << "#{Rails.root}/lib"
# config.autoload_paths += %W( lib/ )
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]
config.middleware.use MyAPIMiddleware
end
end
I'm trying to debug this file as I post this now. Here is a peak at it's internal structure...(i've just included the overall structure for the sake of brevity)
app/lib/model_modificatons.rb
class Bignum
...
end
class Fixnum
...
end
class ProcessorDaemon
...
end
module ActiveRecord
module UserMonitor
...
end
module MyLogger
...
end
end
class Object
...
end
class Struct
...
end
class String
...
end
class Fixnum
...
end
class OpenStruct
...
end
class ActiveRecord::Base
def self.visible_columns
...
end
...
def self.acts_as_userstamp
logger.info "HI fonso - acts_as_userstamp is called"
include ActiveRecord::UserMonitor
end
...
protected
def self.range_math(*ranges)
...
end
end
class Array
...
end
class DB
...
end
If you can spot a problem with the overall structure or anywhere else please let me know.
So why is this method not found? I'm trying to debug it as I'm posting this and I'm getting nothing.
I suspect the file app/lib/model_modifications.rb is not being loading. That nothing in the /lib directory is being loaded..but how do I confirm this?
Thank you for reading this far, I hope I've not rambled on too much.
autoload_path configuration does not load all the given files on the boot but defines folders where rails will be searching for defined constants.
When your application is loaded, most of the constants in your application are not there. Rails have a "clever" way of delaying loading the files by using a constant_missing method on Module. Basically, when Ruby encounters a constant in the code and fails to resolve it, it executes said method. THe sntandard implementation of this method is to raise UndefinedConstant exception, but rails overrides it to search all of its autoload_paths for a file with a name matching the missing constant, require it and then check again if the missing constant is now present.
So, in your code everything works as expected and you need to load this extension file manually. If you want to have some code that executes on the application boot, put your file within config/initializers folder.
Aside: Try avoiding monkey patching whenever possible. It might be looking clever, but adding more methods to already overpopulated classes will not make them easier to use.

Rails 5.2 autoload is not working in development

I have a project on Rails 5.2 with the following structure:
app/
app/auth
app/auth/oauth_controller.rb
app/auth/oauth.rb
oauth_controller.rb
require_relative "./oauth.rb"
module Auth
class OauthController < Infra::BaseController
include ActionController::Cookies
def start
oauth = Auth::OAuth.new(session: session)
...
end
def callback
oauth = Auth::OAuth.new(session: session)
...
end
end
end
oauth.rb
module Auth
class OAuth
...
end
end
To have the Auth::Oauth working I have to require the oauth.rb file, so I think the eager loading or autoload are not working. But, even with the require(), when I change the file, I get this error again and I have to restart the server again and again.
uninitialized constant Auth::OAuth
Here is my application.rb
config.middleware.use ActionDispatch::Cookies
config.api_only = false
config.eager_load_paths += %W(#{config.root}/app)
config.time_zone = 'Etc/UTC'
config.reload_controllers = !Rails.env.production?
The development.rb was not changed.
It's because of naming convention rails expects. Rails is expecting a file name o_auth.rb to match OAuth. You need to add an infection to support OAuth as oauth.rb
In config/initializers/inflections.rb add
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'OAuth'
end
OR you need to change your file name to o_auth.rb
In both cases you do not need require_relative "./oauth.rb"
In addition, if this is a controller it should live in app/controllers/auth/o_auth and not directly in app/
In addition to the other answer, the folders under app are not interpreted as modules and are for organization only. So app/auth/oauth.rb Should look like
class Oauth
Not
module Auth
class Oauth

rails - files in autoload_paths folder aren't loaded

I have a app/extensions folder where my custom exceptions reside and where I extend some of the Ruby/Rails classes. Currently there are two files: exceptions.rb and float.rb.
The folder is specified in the ActiveSupport::Dependencies.autoload_paths:
/Users/mityakoval/rails/efo/app/extensions/**
/Users/mityakoval/rails/efo/app/assets
/Users/mityakoval/rails/efo/app/channels
/Users/mityakoval/rails/efo/app/controllers
/Users/mityakoval/rails/efo/app/controllers/concerns
/Users/mityakoval/rails/efo/app/extensions
/Users/mityakoval/rails/efo/app/helpers
/Users/mityakoval/rails/efo/app/jobs
/Users/mityakoval/rails/efo/app/mailers
/Users/mityakoval/rails/efo/app/models
/Users/mityakoval/rails/efo/app/models/concerns
/Users/mityakoval/rails/efo/app/template.xlsx
/Users/mityakoval/.rvm/gems/ruby-2.4.1#web_app/gems/font-awesome-rails-4.7.0.2/app/assets
/Users/mityakoval/.rvm/gems/ruby-2.4.1#web_app/gems/font-awesome-rails-4.7.0.2/app/helpers
/Users/mityakoval/rails/efo/test/mailers/previews
The reason for it to be listed there twice is that it should be automatically loaded since it was placed under app directory and I have also manually added it to the autoload_paths in application.rb:
config.autoload_paths << File.join(Rails.root, 'app', 'extensions/**')
The strange thing is that my exceptions.rb is successfully loaded at all times, but the float.rb isn't unless eager loading is enabled.
Answers to this question say that it might be related to Spring (which I tend to believe), so I've added the folder to spring.rb:
%w(
.ruby-version
.rbenv-vars
tmp/restart.txt
tmp/caching-dev.txt
config/application.yml
app/extensions
).each { |path| Spring.watch(path) }
I've restarted Spring and the Rails server multiple times and nothing worked. Does anyone have any suggestions?
Ruby version: 2.4.1
Rails version: 5.1.5
EDIT
/Users/mityakoval/rails/efo/app/extensions/float.rb:
class Float
def comma_sep
self.to_s.gsub('.', ',')
end
end
rails console:
irb> num = 29.1
irb> num.comma_sep
NoMethodError: undefined method `comma_sep' for 29.1:Float
from (irb):2
A better way to monkeypatch a core class is by creating a module and including it in the class to be patched in an initializer:
# /lib/core_extensions/comma_seperated.rb
module CoreExtensions
module CommaSeperated
def comma_sep
self.to_s.gsub('.', ',')
end
end
end
# /app/initializers/core_extensions.rb
require Rails.root.join('lib', 'core_extensions', 'comma_seperated')
# or to require all files in dir:
Dir.glob(Rails.root.join('lib', 'core_extensions', '*.rb')).each do |f|
require f
end
Float.include CoreExtensions::CommaSeperated
Note that here we are not using the Rails autoloader at all and explicitly requiring the patch. Also note that we are placing the files in /lib not /app. Any files that are not application specific should be placed /lib.
Placing the monkey-patch in a module lets you test the code by including it in an arbitrary class.
class DummyFloat
include CoreExtensions::CommaSeperated
def initialize(value)
#value = value
end
def to_s
#value.to_s
end
end
RSpec.describe CoreExtensions::CommaSeperated do
subject { DummyFloat.new(1.01) }
it "produces a comma seperated string" do
expect(subject.comma_sep).to eq "1,01"
end
end
This also provides a much better stacktrace and makes it much easier to turn the monkey patch off and on.
But in this case I would argue that you don't need it in the first place - Rails has plenty of helpers to humanize and localize numbers in ActionView::Helpers::NumberHelper. NumberHelper also correctly provides helper methods instead of monkeypatching a core Ruby class which is generally best avoided.
See:
3 Ways to Monkey-Patch Without Making a Mess

Require module throwing error in Rails 5

I am attempting to create a permissions structure for users in my application. I created a permissions.rb file in the lib/ directory in my rails application.
When I try to include Permissions in my user model I am getting this error.
This is what I have in the user model.
class User < ApplicationRecord
include Permissions
...
end
How can I include this file and its methods without getting this error?
To include modules under lib folder you will need to add your lib folder in autoload_path in your application.rb
config.autoload_paths += %W( #{config.root}/lib/)
add this line in your application.rb.
I think it would be good if you use autoload file when application start then it would like to on the application.rb
config.autoload_paths << Rails.root.join('lib')
Or you can use user.rb
class User < ApplicationRecord
load File.join(Rails.root, 'lib', 'permissions.rb')
end
The module would look like this, always make sure the naming conventions is right like if run module name on the console with underscore then he would give your file name, see the below if your module name is Permissions then
Loading development environment (Rails 5.1.4)
2.3.4 :001 > "Permissions".underscore
=> "permissions"
your file name is permissions.rb
#=> permissions.rb
module Permissions
...
def self.method #=> method name instead of the method
#=> code staff here
end
or
def method #=> method name instead of the method
#=> code staff here
end
...
end
Hope it helps

Resources