Skip to content

Extending an existing Rails application that wasn’t meant to be extended

I am modifying an existing open source rails 4.2 app and wanted to keep my changes (some of which are to models, some to controllers, some to views) as separate as I can, so that when a new release of the app comes out, I won’t be in (too much) merge hell.

This app was not designed to be extended, which makes things more interesting.

For the views, I’m just doing partials with a prefix (_xxx_user_cta.haml).

For the models and controllers, I started out hacking the code directly, but after some digging around I discovered how to monkey patch (I believe that is what it is called) the classes.

In the config/application.rb file, I added the following code:

config.to_prepare do
Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c|
require_dependency(c)
end
end

And then, if I want to make a change to app/models/person.rb, I add the file app/decorators/models/person_decorator.rb. In that file is something like this:

Person.class_eval do
# ... changes
end

This lets me add additional relations, helper methods, and other classes to extend existing functionality. I try to prefix things with a unique identifier (xxx_set_timezone rather than set_timezone) to lessen the chances of a collision, because if a method is added to the Person class with the same name as a method in the decorator, the decorator will win.

Write tests around this new functionality so that if anything changes, I’m aware and can more easily troubleshoot.

The downsides of this approach is that it is harder to track logic, because instead of everything in one file, it is now in two. (I don’t know if there are memory or performance implications.) However, that is a tradeoff I’m willing to make to make it easier to keep up with the upstream development and to pull said development in as often as possible.

I’m still fairly new to rails and didn’t know if this is the only or best way, but thought I’d share.

The single biggest obstacle to running self hosted Sharetribe

dollar-1362244_640So, you checked out Can I customize Sharetribe? and determined you need a backend programmer? And that you’re going to self host, whether on Heroku or elsewhere? That’s great!

Let me let you in on the single biggest obstacle to running Sharetribe self hosted. (It’s not the biggest obstacle to building a successful marketplace–that is building the community and getting liquidity–but it is a big one.)

There’s no great option for taking payments on self hosted Sharetribe.

Braintree has limitations and can only support companies in the USA (I believe).

Paypal isn’t supported with the open source version.

No other payment processors are supported ‘out of the box’.

So, if you are charging nothing for your marketplace, or if you monetize your site in some other way such as a listing fee or using the marketplace as a captive market, you’re fine with the self hosted version.

Otherwise you are going to have to pay a developer to integrate with a payment processor. Here is what to think about when you are evaluating payment processors:

  • Do they support the currency I need to support? (Sharetribe only supports one currency per marketplace at this time).
  • Do they handle splitting payments?  That is, if someone pays $100, and the marketplace commission is 10%, $10 goes to the marketplace and $90 to the seller.  How is that handled–many payment processors don’t support splitting payments in the manner.
    • If you aren’t splitting payments, how are you going to get your money and make sure the seller gets theirs?
  • If I need payment escrow, do they support that?
  • Are there legal ramifications (taxes, fees, etc) that my marketplace has to handle with regards to taking money?

It’s worth looking at these:

And if you are interested in having Stripe connect in your system, I’m working on such an integration.  Please sign up for the list to be notified when it is ready to go.

Can I customize Sharetribe?

I’ve been talking to some people about customizing Sharetribe.  It’s a big piece of software and there are a lot of moving parts, but many of the people I’m talking to are business folks.  They chose Sharetribe because they have a marketplace in mind, and the software is just a tool.  However, all tools have their limits.

So, I thought it’d be fun to organize it as a ‘choose your own adventure’. Click the link to learn how to customize Sharetribe’s awesome marketplace software.

Example of customizing a marketplace on sharetribe.com

Here’s an example of some javascript code I wrote to customize a sharetribe.com marketplace.

This code adds a LinkedIn share button to the listing page.

Comments inline.

window.onload = function () {  // make sure you run after the document has been loaded, otherwise you won't have access to jquery.
	$( document ).ready(function() {
    		startcustomization();
	});
}

function startcustomization() {
	console.log("cust started");  // good old debugging log statements.
 	var d = document.createElement("div");  // adding a div to hold our stuff
	d.className = "listing-tweet-button"    // leveraging a existing css class

 	var s = document.createElement("script");  // inject a script tag
    	s.type = "text/javascript";
    	s.src = "//platform.linkedin.com/in.js";  // this code is all from https://developer.linkedin.com/plugins/share 
        var textnode = document.createTextNode("lang: en_US");
	d.appendChild(s);

 	var s = document.createElement("script");  // had to inject both script tags, otherwise they weren't executed.
    	s.type = "IN/Share";
	// commented out: s["data-url"] = "http://www.foo.com";  If you wanted to share a different page than the one that the sharebutton was on, you'd use this.  The share button defaults to sharing the page it is installed on.  
	d.appendChild(s);

	$(".listing-social").append(d);  // find where the other share buttons are, and append our div
}

 

You can, of course, stack as many interesting custom functions as you’d like into this script.  Do make sure you’re hosting the script on an SSL server (an S3 bucket will do fine) otherwise the script won’t be loaded, since sharetribe.com runs SSL only.

Running costs for self hosted Sharetribe

I have one instance of Sharetribe’s open source platform self hosted, and I thought I’d share the costs around that.

This is hosted on Heroku. Why? It’s not the cheapest option, but it is the easiest option to get up and running with regard to developer time. It also is what sharetribe.com runs, so there’s less possibility of future incompatibilities. But I understand if you want to do all your own devops stuff (scaling, upgrading the server, backing up the db, etc) to save some bucks.

Anyway, on production, you’ll want to run a 2X dyno for the web dyno, and a 1X dyno for the css_compile and worker dynos. I run 1 2X dyno for the web, and a 1x dyno for the worker tasks. That means about $75/month, plus maybe $5/month for heroku run bash and css compiles. You can obviously scale up the web dynos as you need to.

And here are the addons I’ve found useful:

  • Cleardb for mysql, ‘drift’ level (make sure to be careful about your total db connections–each passenger process uses 5 and ‘drift’ only has 30. $49.99
  • flying sphinx for searches, ‘wooden’, $12. We have few enough listings that I’m not worried about deltas yet. $12
  • memcachier for caching. Been able to stay at the developer level for now, which is free.
  • new relic for monitoring and performance reporting. Been able to stay at the ‘wayne’ level for now, which is free.
  • papertrail for logfile review and alerting. Very valuable! Haven’t needed more than 2 days of logs, so am at the ‘choklad’ level, free.
  • heroku scheduler, which is essentially cron. Free.
  • sendgrid for emails, which lets us send 12k emails for free.
  • SSL addon, which lets us serve up the app under SSL with a custom domain name. $20

So, add these all up and you are looking at a running cost of $75+$5+$50+$12+$20 == $162 for a base level heroku install of Sharetribe (plus the SSL certificate, which is another $20, but is a yearly expense). It’s worth remembering that these will increase as you have more traffic and success.

Note, this is for production only. You probably want a staging server. I’ve been able to get by with free dynos for staging, though if you want to support/test full text searches or have a DB with more than 5MB of data in it, you’ll need to pay *something* for staging.

Getting started with sharetribe development–vagrant style

I have recently spent a fair bit of time working with Sharetribe, an open source, MIT licensed marketplace platform that also powers a hosted solution.

First, let me say that the software is the least significant piece of a marketplace (like AirBnB).  The least significant! (Check out the Sharetribe Academy for some great content about the other steps.)  

But it is still a necessary component.  If you can get by with the hosted solution to prove out your idea, I suggest you do so–$100/month is a lot cheaper than hours of software development. There may come a time when you want to customize the sharetribe interface beyond what javascript injection can do.  If this is the case, you need a developer.  And that developer needs an environment.  That’s what this post is really about.

The sharetribe github readme explains the installation process pretty well, but I find it tedious, so I created a quick start vagrant VM. This VM has a sharetribe installation ready to go.  I use vagrant 1.6.3 and Virtualbox 5–google around for instructions on how to get those up and running. The guest VM is Ubuntu 14.04. This VM uses rvm to manage ruby versions, but I couldn’t be bothered with nvm. It will install sharetribe 5.8.0 and all needed components.

Assuming you have vagrant and virtual box installed, download the Vagrant file and put it in the directory where you want to work. Edit it and change any options you’d like. The options I changed were the port forwarding (I set it to 3003), networking options, and the amount of memory used (I allocate 4GB).

Then run vagrant box add http://www.mooreds.com/sharetribe/sharetribe-base-mooreds.box --name sharetribe-base-mooreds to download the file. It’s downloading a large file from my (small) server, so expect it to take a while (hours).

Then run vagrant init.

When you can login (password is vagrant if you use vagrant putty or you can use vagrant ssh) you can go to the sharetribe directory and do the following:

  • fork the sharetribe repo
  • update your git remote so that your origin is your forked repo (and not mine, because you won’t get write access to mine)
  • create a branch in your repo off of the 5.8.0 tag. There’s one startup script I tweaked a bit, but you can just ignore those changes.
  • update your mysql password: SET PASSWORD FOR 'root'@'localhost' = PASSWORD('MyNewPass');
  • start mailcatcher listening to all interfaces: mailcatcher --ip=0.0.0.0
  • start the rails/react server: foreman start -f Procfile.static
  • visit lvh.me:3003 and start your server
  • set up your local super user and first marketplace
  • edit the url you are redirected to to have the correct external port on it (from the vagrant settings): from http://testdev.lvh.me:3000/?auth=baEOj7kFrsw to http://testdev.lvh.me:3003/?auth=baEOj7kFrsw for example

This is running sharetribe 5.8.0, and I’m sure there will be follow on releases. Here’s how to sync the releases coming from the sharetribe team with your current repo. I’ve taken the liberty of creating an upstream branch for you.

This doesn’t cover deploying the code anywhere–I’d recommend this gist. Make sure you read the comments! Or I can install a vanilla version of sharetribe to heroku for a flat fee–contact me for details.

Adding a sitemap to sharetribe

map-1434486_640I have using the excellent Sharetribe framework to build a marketplace for food businesses and commercial kitchens for my new startup, The Food Corridor.  However, it didn’t have support for generating a sitemap.xml file for all the listings available.

How is someone going to find the right kitchen space when they use google, but we don’t have a sitemap so google can keep apprised of all the options?

This wouldn’t do.  So, I added the ability to generate a sitemap for all the listings in the marketplace.

First off, install the gem–I used sitemap_generator as it seemed to do what I needed–allow me to call out certain routes and add them to my sitemap.  Then you need to create a configuration file, at config/sitemap.rb.  Mine looks like:


SitemapGenerator::Sitemap.default_host = "https://"+APP_CONFIG.domain

SitemapGenerator::Sitemap.create do
  Listing.where(deleted: false, open: true).find_each do |listing|
    add listing_path(listing), :lastmod => listing.updated_at
  end
end

 

Then I just ran bundle exec rake sitemap:refresh:no_ping and a sitemap.xml.gz was generated in my public directory.

If you are running on AWS or someplace else with a persistent filesystem, you can skip to the text starting with “Then, I scheduled”.

If you are running on a PAAS like Heroku, where you don’t get a persistent filesystem, you’ll want to push this generated file to a persistent place. I chose S3. Since sharetribe already has paperclip as a dependency, I used the instructions here and here, with a few modifications for sharetribe.

My rake task to upload the sitemap file was:


require 'aws'
namespace :sitemap do
  desc 'Upload the sitemap files to S3'
  task upload_to_s3: :environment do
    s3 = AWS::S3.new(
      access_key_id: ENV['aws_access_key_id'],
      secret_access_key: ENV['aws_secret_access_key']
    )
    bucket = s3.buckets[ENV['s3_bucket_name']]
      file = File.join(Rails.root, "public", "sitemap.xml.gz")
      path = "sitemap/sitemap.xml.gz"

      begin
        object = bucket.objects[path]
        object.write(file: file)
        object.acl=(:public_read)
      rescue Exception => e
        raise e
      end
  end
end


I then run the sitemap:refresh:no_ping and upload_to_s3 tasks in the same heroku scheduled task: rake sitemap:refresh:no_ping sitemap:upload_to_s3. If you don’t do that (and instead do separate dynos) then the upload task won’t have access to the file (because it will have been generated on the first dyno’s filesystem).

You also need to make sure to add a sitemap controller to redirect from yourdomain.com/sitemap.xml.gz to the S3 bucket (again, as outlined in the articles linked above.

Then, I scheduled a daily refresh of the sitemap.xml file and submitted the file to relevant search engines.

Things I didn’t do:

  • handle more than 50k urls
  • support multiple communities (not really needed for me, but I bet if the folks behind sharetribe.com wanted to use this, they’d want such support).
  • add the sitemap.xml file to my robots.txt file, as outlined here.