Rails/Heroku Subdomain - ruby-on-rails

I'm currently trying to setup masking for one of my rails app subdomains.
Hypothetically, my main app website is called www.cats.com
I've got a subdomain on this main application called www.care.cats.com
I've got another domain managed by GoDaddy called www.catscare.com
Currently, I have it setup such that going to www.catscare.com goes to www.care.cats.com as it should. However, whenever I try to add a slug at the end of that link for example (www.catscare.com/blah), my rails application gives me a 404, whereas if I go to www.care.cats.com/blah, it gives me the correct page. I think this has something to do with my subdomain constraints in my routes.rb file.
scope "/:slug", module: :claims_portal, as: :claims_portal, constraints: ClaimsPortalConstraint.new do
My actual constraints file is here:
class ClaimsPortalConstraint
def matches?(request)
in_claims_subdomain?(request) && merchant_exists?(request)
end
private
def in_claims_subdomain?(request)
request.subdomain =~ /^(claims|claims-sandbox|claims-staging|claims-regression|care|care-sandbox|care-staging|care-regression)$/
end
def merchant_exists?(request)
Company.find_by(slug: request.params[:slug]).present?
end
end

Related

Managing multiple domains in a rails application error: "NameError (uninitialized constant #<Class:0x0000563d7bf62250>::Heroku):"

In my rails app, I'm trying to create a booking form external parties(parks) can point their customers to to make a booking for the respective park. The booking form works with the url/route with the subdomain book
https://book.myapp.com/en/parks/:park_id/park_availability
Goal
I would like substitute my domain (myapp.com) with the website of the park, such that I get
https://book.parkapp.com/en/park_availability
Unfortunately I get the error message(s)
upon creation park
NameError (uninitialized constant #<Class:0x0000563d7bf62250>::Heroku):
when using an existing park
{park.website}'s server IP address could not be found.
Outline attempted approach
Park has a website column. In routes.rb I tried setting up the constraints and apply them on the park_availability action.
In the Park model I attempted to add the domain (Park.website) to my Heroku application, once the park is saved.
In my Park controller I try to find the #park, before the park_availability action.
Code
routes.rb
class CustomDomainConstraint
# Implement the .matches? method and pass in the request object
def self.matches? request
matching_site?(request)
end
def self.matching_site? request
# handle the case of the user's domain being either www. or a root domain with one query
if request.subdomain == 'www'
req = request.host[4..-1]
else
req = request.host
end
# first test if there exists a Site with a domain which matches the request,
# if not, check the subdomain. If none are found, the the 'match' will not match anything
Park.where(:website => req).any?
end
end
Rails.application.routes.draw do
resources :parks do
match ':website/park_availability' => 'parks#park_availability', on: :member, :constraints => CustomDomainConstraint, via: :all
end
end
park.rb
class Park < ApplicationRecord
after_save do |park|
heroku_environments = %w(production staging)
if park.website && (heroku_environments.include? Rails.env)
added = false
heroku = Heroku::API.new(api_key: ENV['HEROKU_API_KEY'])
heroku.get_domains(ENV['APP_NAME']).data[:body].each do |domain|
added = true if domain['domain'] == park.website
end
unless added
heroku.post_domain(ENV['APP_NAME'], park.website)
heroku.post_domain(ENV['APP_NAME'], "www.#{park.website}")
end
end
end
parks_controller.rb
class ParksController < ApplicationController
before_action :find_park, only:[:park_availability]
def park_availability
#working code...
end
private
def find_park
# generalise away the potential www. or root variants of the domain name
if request.subdomain == 'www'
req = request.host[4..-1]
else
req = request.host
end
# test if there exists a Park with the requested domain,
#park = Park.find_by(website: req)
# if a matching site wasn't found, redirect the user to the www.<website>
redirect_to :back
end
end
To get around error in question you can try writing classname as ::Heroku, that will make ruby to look for it in correct scope.
But Heroku legacy api has been disabled, so you should use their new platform-api:
heroku = PlatformAPI.connect(ENV['HEROKU_API_KEY']) # note that you may also have to use a new key
domains = heroku.domain.list(ENV['APP_NAME'])
...
heroku.domain.create(ENV['APP_NAME'], ...)
also to check for domain presence you can use domain.info api method instead of fetching all domains that are not needed.
Keep in mind that callbacks are not the best place to make any external calls: if the api call fails for some reason (temporary network problems, api outage, server restart, etc) - whole transaction will be rolled back, item will not be saved and you can loose data. Better way is to enqueue a background job there, which will handle domains later, can retry if needed and so on.

disabling subdomains with Apartment

I'm using the apartment gem in a rails app.
I have one database with schemas for each tenant and one public schema for the Tenant table.
I have excluded the www subdomain:
Apartment::Elevators::Subdomain.excluded_subdomains = ['www']
Then, if I enter public.page.com or www.page.com, Apartment won't switch to another tenant, but it will stay at the public one. Of course "public" is not a tenant itself, is just the common data between tenants, so, I don't want any user using the public schema.
What would be the correct way to avoid this?
This app is running on AWS, so, route 53 is going to prevent this, but, although I want to avoid rails from serving request through this subdomain.
Apart from excluding domain from Apartment, you need to exclude them from routes.
In my project I'm using this code for manage this:
I'm using initializer to create array of excleded subdomains.
# config/initializers/apartment/subdomain_exlusions.rb
Apartment::Elevators::Subdomain.excluded_subdomains = ['www', 'admin']
Then, we can use this array in helper class in routes.
# config/routes.rb
class ExcludedSubdomainConstraint
def self.matches?(request)
request.subdomain.present? && !Apartment::Elevators::Subdomain.excluded_subdomains.include?(request.subdomain)
end
end
Rails.application.routes.draw do
constraints ExcludedSubdomainConstraint do
# here routes that are accessible in subdomains
end
end
As a bonus, you can route excluded subdomains to another constrain
class DashboardSubdomainConstraint
def self.matches?(request)
Apartment::Elevators::Subdomain.excluded_subdomains.include?(request.subdomain) || request.subdomain == ''
end
end
constraints DashboardSubdomainConstraint do
namespace :dashboard do
get '/settings'
end
end
will give you a route like www.domain.com/dashboard/settinigs with access to public tenant.
TIP. And you can use different root method in concerns

Adding subdomain for different user types

I am pretty new to Rails and most of my knowledge depends on tutorials :)
So, I followed this http://www.railstutorial.org tutorial and created really good site but now I run into a problem. For my users I have a special column in my database which shows which type of user he is. For example I have column 'student' which is 'true' if user is student and 'false' if he's not.
Now I would like to create a subdomain for students. So, when student wants to sign up or sign in he would be transferred to www.student.mysite.com instead of www.mysite.com.
How can I accomplish that?
Thank you :)
There are a number of ways to do this, specifically you'll be interested in looking up multi-tenancy in respect to rails
--
Multi Tenancy
Whilst multi tenancy is typically the definition of having multiple databases / assets (one for each user), however, as it's ridiculously difficult to get this working in rails (something we're currently working on), you can use the principle with a single stack of data
There are several tutorials on how to achieve this with Rails here:
Basecamp-style subdomains by DHH (although looks like the post is down)
Multitenancy with PostgreSQL (Railscasts)
Apartment Gem (achieving multi tenancy on Rails)
Although this is not directly related to your question, most of the "multi tenancy" questions
are typically based on "how do I create different subdomains for my users"
--
Subdomains
The basis of subdomains on Rails is to capture the request, and route it to the correct controller. We've managed to achieve that using the following setup:
#config/routes.rb
constraints Subdomain do #-> lib/subdomain.rb & http://railscasts.com/episodes/221-subdomains-in-rails-3
#Account
namespace :accounts, path: "" do #=> http://[account].domain.com/....
#Index
root to: "application#show"
end
end
#lib/subdomain.rb
class Subdomain
def self.matches?(request)
request.subdomain.present? && request.subdomain != 'www'
end
end
This will give you the ability to do the following:
#app/controllers/accounts/application_controller.rb
class Account::ApplicationController < ActionController::Base
before_action :set_account
def show
##account set before_action. If not found, raises "not found" exception ;)
end
private
#Params from Subdomain
def set_account
params[:id] ||= request.subdomains.first unless request.subdomains.blank?
#account = Account.find params[:id]
end
end
Ideally, we'd love to handle this in the middleware, but as it stands, this is what we've got!
This will give you the ability to call the data you need from the #account variable:
#app/views/accounts/application/show.html.erb
<%= #account.name %>

User scope with Devise and Rails 4.1

I'm doing some proof of concepts with Rails. I created a Customer, and apply to it DEVISE.
Everythig works fine, but now i'm trying to logging with a customer and enter directly into his scope.
localhost:3000/customers/1
After this hide the /customers/1 with some word, for example: myProfile.
I'm trying to do the first part with
module ApplicationHelper
protected
def after_sign_in_path_for(resource)
#path to redirect
end
end
I was trying with definitions from routes.rb
redirect_to(customers_path)
or something like:
#customer= Customer.find_by_email(current_customer.email)
redirect_to(customer_path(#customer))
But nothing is working yet.
I'm not sure how to send messages to the console of the server (like in Java with some System.out.println) to check the contents...
Thanks a lot!

Dynamic Rails routing based on database

I'm building a CMS with various modules (blog, calendar, etc.) using Rails 2.3. Each module is handled by a different controller and that works just fine.
The only problem I have is with the root URL. Depending on the configuration chosen by the user, this default URL should show a different module i.e. a different controller, but the only way I have to determine the correct controller is by checking the database for what "default" module is to be shown.
For the moment I'm using a specific "root" controller which checks the database and redirects to the correct controller. However I'd prefer the URL not to be changed, which means I want to invoke the correct controller from the very same request.
I've tried using Rails Metal to fetch this info and manually calling the controller I want but I'm thinking I may be reinventing the wheel (identify the request path to choose the controller, manage session, etc.).
Any idea? Thanks a lot in advance!
This problem can be solved with some Rack middleware:
This code in lib/root_rewriter.rb:
module DefV
class RootRewriter
def initialize(app)
#app = app
end
def call(env)
if env['REQUEST_URI'] == '/' # Root is requested!
env['REQUEST_URI'] = Page.find_by_root(true).uri # for example /blog/
end
#app.call(env)
end
end
end
Then in your config/environment.rb at the bottom
require 'root_rewriter'
ActionController::Dispatcher.middleware.insert_after ActiveRecord::QueryCache, DefV::RootRewriter
This middleware will check if the requested page (REQUEST_URI) is '/' and then do a lookup for the actual path (Implementation to this is up to you ;-)). You might do good on caching this info somewhere (Cache.fetch('root_path') { Page.find... })
There are some problems with checking REQUEST_URI, since not all webservers pass this correctly. For the whole implementation detail in Rails see http://api.rubyonrails.org/classes/ActionController/Request.html#M000720 (Click "View source")
In Rails 3.2 this was what I came up with (still a middleware):
class RootRewriter
def initialize(app)
#app = app
end
def call(env)
if ['', '/'].include? env['PATH_INFO']
default_thing = # Do your model lookup here to determine your default item
env['PATH_INFO'] = # Assemble your new 'internal' path here (a string)
# I found useful methods to be: ActiveModel::Naming.route_key() and to_param
end
#app.call(env)
end
end
This tells Rails that the path is different from what was requested (the root path) so references to link_to_unless_current and the like still work well.
Load the middleware in like so in an initialiser:
MyApp::Application.config.middleware.use RootRewriter

Resources