ASP.NET MVC vs. Rails3

I recently was contacted to implement an ASP.NET MVC application and I saw this as a great opportunity to compare it with Rails3.
What immediately strikes you when you start with ASP.NET MVC is how similar it is to Rails. No one can steal ideas like Microsoft!

Rails ASP.NET MVC Purpose (if not obvious)
/app/models /Models
/app/controllers /Controllers
/app/views /Views
/public/javascript /Scripts
/public /Content
/db /App_Data Database data, such as migrations, and models.
/test (unit, functional, fixtures, performance) Separate VS projects
/config /Global.asax, /Properties, Web.config Configuration

Project Generation

Both Rails and ASP.NET MVC relies on code generation to get you started, but the methods they use are different.
Rails uses the command line, which is natural since the Rails approach is to not rely on anything but a good programming editor and the command line.

$ rails tapir
create .gitignore
...
create app/controllers/application_controller.rb
...
create app/views/layouts
create config/database.yml
create db/seeds.rb
...
create public/javascripts/application.js
...
create test/unit
...

ASP.NET MVC uses Wizards inside Visual Studio, which is also expected since Microsoft has a long history of IDE-centric application building.

Worth noting is that I didn’t really want to use VS Unit Test, since I normally prefer NUnit, but after one hour of googling and testing I gave up and went with VS Unit Test anyway.

Environment

When Rails is installed it comes pre-configured with three different environment, development, test and production. It is as easy as writing RAILS_ENV=test to switch from the default development environment to the test environment. In this area Rails really shines. There is nothing similar in ASP.NET MVC.

The Model

Rails Model

Rails comes pre-configured with an ActiveRecord model, if you don’t write otherwise. If you want to use something else it is very easy. Supported frameworks are among others, Neo4J, MongoDB, and DataMapper.
To create a model in Rails3 you use a command line generator. The generator generates a model, a migration and tests. And you can, of course, choose what kind of model you wish to generate (-o), as well as what kind of testing framework you want to use (-t). Here I just go with the default:

$ rails g model customer name:string email:string
invoke active_record
create db/migrate/20100419094010_create_customers.rb
create app/models/customer.rb
invoke test_unit
create test/unit/customer_test.rb
create test/fixtures/customers.yml

Rails comes preconfigured with Sqlite3, and you don’t have to do anything to configure the default database. Moreover, the configuration is set up to use three different databases, one for each environment.
Rails, performs all database changes through scripts, migrations. This is invaluable when you want to upgrade a database that is in production, or when multiple developers are changing the same database model. The migration scripts allow seamless migrations between the different databases.

# An example of a migration
class CreateCustomers < ActiveRecord::Migration

# Called when migrating up to this version
def self.up
create_table :customers do |t|
t.string :name
t.string :email
t.timestamps
end
end

# Called when migrating down from this version
def self.down
drop_table :customers
end
end

To move between the different versions of the database we use the rake db:migrate command.

# Migrate to the latest version
$ rake db:migrate

# Migrate to a specific version
$ rake db:migrate VERSION=20080906120000

# Rollback one version
$ rake db:rollback

# Rollback three versions
$ rake db:rollback STEP=3

Read more about migrations in the Migrations Guide

ASP.NET MVC Model

ASP.NET MVC is not a full stack framework and it does not come preconfigured with a model. It is possible to choose between many solutions, such as NHibernate, Entity Framework or LINQ-to-SQL. I choose LINQ-to-SQL because I think it is an elegant, lightweight OR-Mapper, that is easy to work with. Too bad, it isn't prioritized by Microsoft.
Unfortunately, there is nothing like migrations in LINQ-to-SQL. So I use the LINQ-to-SQL design tool to design the classes.

You can then create the database from the model. It is also possible to do it the opposite way, to create the database first and then generate the model, but I prefer this way.

// Creating a database from a LINQ-to-SQL DataContext
public void CreateDatabase(bool force)
{
var db = new TapirDataContext(@"c:tapirdb.mdf");
if (db.DatabaseExists() || force)
{
Console.WriteLine("Deleting old database...");
db.DeleteDatabase();
}
db.CreateDatabase();
}

As you understand this is not a viable solution for migrating a production database, but it is good enough (almost) for development and testing, since I can allow myself to recreate the database every time. Once I get a larger database, the time it takes to set it up will force me to go with a better way. The lack of migrations hurts.

Query Language

Rails3 uses AREL, Active Record Relations, and LINQ-to-SQL uses LINQ (surprise!). They are both beautiful solutions and remarkably similar. Both solutions create lazy, composable queries, that are not executed until the latest possible time, when a result is needed. In LINQ, you can see Eric Meijer's Haskell shining through like magic, and in AREL, the beauty of Ruby.

# A simple query with AREL
User.where(users[:name].eq('Anders')).order('users.id DESC').limit(20)


// The same with C#
// Lambda Syntax
db.Users.where(u => u.Name == "Anders").orderBy(u => u.Id).Take(20)

// LINQ Syntax
(from u in db.Users
where u.Name == "Anders"
orderby u.Id descending
select u).Take(20);

One thing that you don't get with LINQ-to-SQL is all the methods that are dynamically created, by need, in Rails, such as find_by_name, find_by_name_and_age, etc.

The Controller

In ASP.NET MVC, it is easy to create a controller, you just right-click on the controllers folder and select Add > Controller. You then get a dialog where you can type in the name of the controller and, optionally, if you like to create default methods for the standard CRUD scenario, just select the checkbox.

public class CustomersController : Controller {
// GET: /Customers/
public ActionResult Index() {
return View();
}

// GET: /Customers/Details/5
public ActionResult Details(int id) {
return View();
}

// GET: /Customers/Create
public ActionResult Create() {
return View();
}

// POST: /Customers/Create
[HttpPost]
public ActionResult Create(FormCollection collection) {
try {
// TODO: Add insert logic here
return RedirectToAction("Index");
} catch {
return View();
}
}
}

As you can see the code generated is simple and clear. That is the benefit of MVC, simpler models, views, and controllers.
The automatic generation of code, that will probably be changed later is commonly known as scaffolding.

Scaffolding is a temporary structure used to support people and material in the construction or repair of buildings and other large structures. --Wikipedia

Scaffolding is not meant to be used as is. It is meant to get you started!
In Rails3 you add controllers with a generator. You have a lot more options, below are just a few of them.

$ rails g controller
Usage:
rails generate controller NAME [action action] [options]

Options:
-e, [--template-engine=NAME] # Template engine to be invoked
# Default: erb
-t, [--test-framework=NAME] # Test framework to be invoked
# Default: test_unit

As you can see it is possible to choose what template-engine you want to use, and exactly what actions you want to create, the view templates are created automatically with every action you create.

$ rails g controller Customer index create
conflict app/controllers/customer_controller.rb
create app/controllers/customer_controller.rb
route get "customer/create"
route get "customer/index"
invoke erb
create app/views/customer
create app/views/customer/index.html.erb
create app/views/customer/create.html.erb
invoke test_unit
create test/functional/customer_controller_test.rb
invoke helper
create app/helpers/customer_helper.rb
invoke test_unit
create test/unit/helpers/customer_helper_test.rb

If you want to create all the default actions you should instead invoke the scaffold_generator.


$ rails g scaffold_controller
Usage:
rails generate scaffold_controller NAME [options]

It will create all the default actions and the views that go with them.

The generated code is shown below. Again, you see the similarity between the two solutions. Rails, automatically generates support for both HTML and XML, making it easy to consume the data from other clients.

class FishController < ApplicationController
# GET /fish
# GET /fish.xml
def index
@fish = Fish.all

respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @fish }
end
end

# GET /fish/1
# GET /fish/1.xml
def show
@fish = Fish.find(params[:id])

respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @fish }
end
end

# GET /fish/new
# GET /fish/new.xml
def new
@fish = Fish.new

respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @fish }
end
end

# GET /fish/1/edit
def edit
@fish = Fish.find(params[:id])
end

# POST /fish
# POST /fish.xml
def create
@fish = Fish.new(params[:fish])

respond_to do |format|
if @fish.save
format.html { redirect_to(@fish, :notice => 'Fish was successfully created.') }
format.xml { render :xml => @fish, :status => :created, :location => @fish }
else
format.html { render :action => "new" }
format.xml { render :xml => @fish.errors, :status => :unprocessable_entity }
end
end
end
...
end

Filters

In Rails it is easy to create filters, that can be applied to specific actions.

class ItemsController < ApplicationController
before_filter :require_user_admin, :only => [ :destroy, :update ]
before_filter :require_user, :only => [ :new, :create]
end

In ASP.NET MVC, the same thing can be accomplished by overriding OnActionExecuting, in the controller.

override void OnActionExecuting(ActionExecutingContext filterContext)
{
var action = filterContext.ActionDescriptor.ActionName;
if (new List{"Delete", "Edit"}.Contains(action)) {
RequireUserAdmin();
}
if ("Create".Equals(action)) {
RequireUserAdmin();
}
}

The functionality may also be extracted into an ActionFilter, which can be applied to the controller or an individual method via an Attribute.

[RequireUserAdmin("Delete", "Edit")]
[RequireUser("Create")]
public class CustomersController : Controller

This is a pattern that can be seen everywhere, when Rails uses Class Macros, a term from Meta-Programming Ruby, ASP.NET MVC uses attributes. And this is appropriate since it usually constitutes meta-data.

Routing

The Routing in both Rails and ASP.NET MVC is incredibly flexible.

Rails Routing

In Rails it is possible to put constraints right in the routing table on just about anything.

# config/routes.rb
Tapir::Application.routes.draw do |map|
resources :animals

get "customer/index"
get "customer/create"

match "/:year(/:month(/:day))" => "info#about",
:constraints => { :year => /d{4}/,
:month => /d{2}/,
:day => /d{2}/ }
match "/secret" => "info#about",
:constraints => { :user_agent => /Firefox/ }
end

As you can see Rails comes pre-configures with RESTful routing, and it is indeed the recommended way to set up your routes in Rails. To see what the routing result is is easy:

$ rake routes
GET /animals(.:format) {:action=>"index", :controller=>"animals"}
animals POST /animals(.:format) {:action=>"create", :controller=>"animals"}
new_animal GET /animals/new(.:format) {:action=>"new", :controller=>"animals"}
GET /animals/:id(.:format) {:action=>"show", :controller=>"animals"}
PUT /animals/:id(.:format) {:action=>"update", :controller=>"animals"}
animal DELETE /animals/:id(.:format) {:action=>"destroy", :controller=>"animals"}
edit_animal GET /animals/:id/edit(.:format) {:action=>"edit", :controller=>"animals"}
customer_index GET /customer/index {:controller=>"customer", :action=>"index"}
customer_create GET /customer/create {:controller=>"customer", :action=>"create"}
/:year(/:month(/:day)) {:year=>/d{4}/, :month=>/d{2}/, :day=>/d{2}/, :controller=>"info", :action=>"about"}
/secret {:user_agent=>/Firefox/, :controller=>"info", :action=>"about"}

ASP.NET MVC Routing

ASP.NET MVC Routing is not quite as easy, but everything is possible. The standard routing with paths and parameters works easily out of the box.

// Global.asax.cs
public class MvcApplication : System.Web.HttpApplication {
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

// Constrained route
routes.MapRoute( "Product", "Product/{productId}",
new {controller="Product", action="Details"},
new {productId = @"d+" } ); // Constraint

// Route with custom constraint, defined below
routes.MapRoute( "Admin", "Admin/{action}",
new {controller="Admin"},
new {isLocal=new LocalhostConstraint()} );
}
...
}

public class LocalhostConstraint : IRouteConstraint {
public bool Match ( HttpContextBase httpContext, Route route,
string parameterName, RouteValueDictionary values,
RouteDirection routeDirection )
{
return httpContext.Request.IsLocal;
}
}

As you can see, you have the same power in the ASP.NET MVC Routing, but it requires you to add constraints separately.

Views

In both Rails and ASP.NET MVC, the views are automatically matched in the directory with the same name as the controller, and the file name is named by the action.

app/controllers/customer_controller.rb
app/views/customer
app/views/customer/create.html.erb
app/views/customer/index.html.erb

In Visual Studio it is possible to scaffold a view based on a model, just as it is in Rails. Rails is, of course, more flexible, allowing me to scaffold all or some actions at once. In Visual Studio, it is only possible to do one at a time.

Partials

In both Rails and ASP.NET MVC, a partial is a file that contains a part of a HTML file. The file is written in ASP in ASP.NET MVC and in Rails it is possible to pick the templating language of your choice. Default is ERB, but HAML is also a popular choice.


<%= render 'form' %>



<% Html.RenderPartial("Form", Model);%>

In ASP.NET MVC version 2, the preferred way is actually to use two alternate methods to generate the code. They have the advantage that they are type-safe (if that gives you pleasure) and they keep track of nesting.

<%= DisplayFor("Address", m => m.Address ) %>

<%= EditorFor("Address", m => m.Address ) %>

(For Rubyist, the m => m.Address is not a hash key-value expression, but an anonymous function.)

Helpers

A helper is similar to a partial, but it is not a template, it's a method that generates code.
In Rails you create a helper by creating a simple method.

module ApplicationHelper
def label target, text
""
end
end

And so you can in ASP.NET MVC.

public class LabelHelper {
public static string Label(string target, string text) {
return String.Format("", target, text);
}
}

Conclusion

All in all, ASP.NET MVC with LINQ-to-SQL, is not a bad experience. Eric Meijer and friends have managed to overcome a lot of the hurdles of static typing. They also, let go of it in cases, where dynamic typing is obviously better, such as in the routing and when populating objects from forms. The lambda expressions and the type inference also removes a lot of the boilerplate common in statically typed languages.
Rails is of course a lot more flexible, and faster to work with. The scaffolding is faster and more flexible, the migrations are, as I said before, invaluable when it comes to move between versions.
But, if I'm thrown out of the dynamic Garden of Eden, I'd rather pick my static apples in Redmond, than in California.

This Post Has 34 Comments

  1. Filip

    Really nice post! Just like to point out that MVC can also handle different templ!ting languages. Cheers!

  2. xnodev

    You are forgout about _patterns_, that MVC is PATTERN

  3. Rikard Ottosson

    Brilliant post! A thorough non-zealous comparison between an M$ solution and something else. Kudos!

  4. Anders Janmyr

    @Fiilip
    Ok, I didn’t know that. What templating languages and how?

  5. florim maxhuni

    As i see you post you come from rails background…

  6. Anders Janmyr

    @Florim
    Yes, I do, but I have also worked with a lot of other frameworks, like ASP.NET, Struts, Wicket, Spring MVC, and Seaside. I think ASP.NET MVC is better than most of them, except for Seaside and Rails. But these are both developed in dynamic languages, Smalltalk and Ruby.

  7. Sohan

    Your post is linked at the drink rails blog’s drink#42.

  8. Peter

    Could you use the database project to version control the database, akin to using db migrate?

  9. Matt Hidinger

    Hi Anders,

    This is a great comparison of the 2 platforms. I haven’t spent a lot of time with RoR yet, but I’ve been using MVC for a while now.

    Just to mention 2 points in your article:

    1) On Migrations, it’s true that LINQ to SQL has no concept of this unfortunately, but typically in .NET apps we use Database Projects to handle database deployments and migrations. While these are not necessarily native to MVC, if you have VS 2008 Developer edition (Or VS2010) then you have an option to create a Database Project that will manage and version control all items in your database, along with automatically deploying migrations to development, staging, prod, etc.

    2) Regarding the flexibility of customizing the code generation templates (like Views, and Controllers) — you can use the T4 templating mechanism in Visual Studio to customize all of those scaffolding templates, including creating your own custom ones that will automatically appear in that IDE drop down list (Where it by default displays options to scaffold views for: List, Edit, Create, etc)

    I think given what you’ve described of both platforms, all things considered they are both able to accomplish the exact same tasks, including most of the flexibility and customizations that RoR provides. Unfortunately, they just might not be as discoverable in MVC as they are in RoR.

    Thanks again for the detailed writeup!

    -Matt

  10. KristoferA

    For version migration / SQL-DDL diff script generation for L2S, you may want to take a look at my add-in. It adds model db sync (both ways) and comparison to the L2S designer in VS2008 and VS2010… You can download it and get a free trial license at http://www.huagati.com/dbmltools/

  11. mendicant

    This is a really shallow comparison. For example, though you can make the routes look the same, Rails routes add a lot of really nice helpers for URLs like:

    new_modelname_url or edit_modelname_url(model)

    It’s a small example, but so much better than Html.ActionLink(controller => controller.New())

    I think ASP.Net MVC is getting better and is a much better option than webforms. But to do a superficial comparison doesn’t do rails justice. Both do the big things well. However, Rails also does the little things well, and thats where it really shines. There isn’t even a comparison because ASP.Net MVC doesn’t do any of the little things for you.

    Plus, at this point in time there are so many more options in terms of community, help, plugins, etc. Where MVC has what… MVC Contrib. MVC Contrib is great, but it’s _all_ that the .Net people have.

  12. Brett Bim

    Getting alternate test project types set up with ASP.Net MVC:

    Nunit: http://www.nikmakris.com/blog/post/Setting-Up-ASPNET-MVC-with-NUnit-for-Visual-Studio-2008-Standard-Edition-Visual-Web-Developer-Express-2008.aspx

    xUnit: http://weblogs.asp.net/podwysocki/archive/2008/04/04/relooking-at-xunit-net-rc2.aspx

    xUnit is especially easy to use since it has an installer that does the work for you. Both NUnit and xUnit are far superior to MSTest if you ask me though getting integration to work is a little more, well, work.

  13. Mark Hoffman

    Great, un-biased write-up. As a long-term .NET guy who has recently made the switch to Rails, I agree that ASP.NET MVC is light years ahead of Webforms but it really doesn’t get anywhere close to Rails. It’s the hundreds of little things that the Rails already has that MS is having to re-invent that make the difference. Things like migrations; RESTful routing, script/console support plus the rich library of gems and plugins make Rails a no-brainer for me. As others have said, the Rails community is ,frankly, light years ahead of the .NET community in terms of practical experience. That community is out-pacing MS at an amazing rate.

  14. Anders Janmyr

    @Peter, @Matt Hidinger:
    Is Database Projects as flexible as migrations? I didn’t know that. I have to read up on it. It sounds to good to be true.

    @Matt Hidinger:
    I’m not good enough in VS, to say that the platforms are equal. Perhaps if you are a VS virtuoso, and easily create new templates you could say that. But I think that the dynamism of Ruby still puts Rails on top. Perhaps with C# 4.0 they will be more equal, but the fact that I have to compile C# makes my development pace slower.

    @KristoferA:
    Interesting, I’ll take a look at it. I also heard that SQL Compare should be good for migrations.

    @mendicant:
    I’m looking forward to reading your more in depth comparison soon!

    @Brett Bim:
    I only tried with NUnit and couldn’t make it work, then I realized it wasn’t such a big deal to get the test generation, since it didn’t generate tests for my newly generated controllers, so I guess I could have gone with NUnit anyway.

    @Mark Hoffman:
    Thanks! I agree. It is the little things that puts Rails ahead. And I think they matter a lot. But, given no choice, I’m happy that MS is borrowing ideas from Rails to make my life easier :)

  15. Gregório Melo

    Great article, Anders.

    Your comparison is, for me, unbiased, totally neutral. In the development world we do need more work like this one. Congratulations!

    Although I’m stronger in the Java world than Rails (and knowing almost nothing in ASP.NET), I heartily believe that Rails has changed the way we code and architect an application. The resulted code for whatever you develop almost always looks much less verbose than the code produced by its ‘competitors’. JSF seems to be years behind.

  16. Anders Janmyr

    @Gregório Melo:
    Thanks! I agree, Rails has changed the web development scene.

  17. Scott Bellware

    Anders,

    I think you’ve done a good job here comparing the aspects of ASP .NET MVC that have correlatives in Rails, but if the comparison went the other way, and included aspects of the ecosystem, as others have suggested, ASP.NET MVC would be hardly compatible.

    If ASP.NET MVC makes your life easier, then an easier life is better, right? If Rails makes life even easier, then why not use it?

    I know that the obvious answer is that your boss wants you to use ASP.NET MVC (via pressure from clients). But there has already been quite a good number of influential .NET devs who have proven that bosses and clients can be moved to Rails by a technology leader with an ability to influence.

    After all, we had to move bosses and clients to ASP.NET in 2001. What’s stopping you from employing the kind of change agency that got ASP.NET as institutionalized as it is?

    Personally, I don’t care which state my technology comes from. Heck, I’d even use technology from Scandinavia! :) My loyalties rest solely with technologies that provide the greatest holistic productivity, and I feel duty-bound to move my clients to that technology, just as I did in 2001 when .NET was in pre-release.

    This will be a good debate at the pub at Oredev. Maybe see you there!

  18. Anders Janmyr

    @Scott
    I don’t know why you think I prefer, ASP.NET MVC over Rails. I don’t. I did the comparison since I was given an assignment where one of the prerequisites was that I had to use DotNet. With that constraint, I chose the best tools that I could find and they were actually better than I expected. That said, if the choice of tools was entirely up to me, I would choose Rails.

    Yes, I think it could be a good debate. I’ll see you at Øredev.

  19. For migrations in .Net or Java world, look at dbDeploy http://dbdeploy.com/
    dbDeploy has been around for a really long time now (comparatively) and works in much the same way as rails migration scripts work.

  20. demersus

    For anyone reading this post from the .NET world:
    Continue your curiosity with Rails! I have successfully influenced my organization to move to Rails, and my stress level has gone down considerably. I’m more productive, and I actually enjoy programming again. Rails has so much more to offer in terms of the little things that matter. You will find that you can do things in far less time and actually feel good about what you made. Its true that the Rails way is light years ahead of .NET.

  21. Anders Janmyr

    @demersus I heartily agree with you. I am focusing all my energy on Ruby and Javascript from now on. You can read why here.

  22. Miraç

    Asp.net MVC caching mechanism of the very best.
    Multi tier application development support of Asp.net Mvc.

    Rails strong, Asp.Net is more strong.

    nice article, good comments, thanks everyone ..

  23. Kevin Veroneau

    Great article! Have you done any work with Django? I would love to see Django compared to ASP.NET MVC and Rails. Also I would love to know which one was the easiest and quickest to learn? I learnt Django in an incredible amount of time, during the time which I was just learning Python too. I am highly considering learning ASP.NET MVC and Rails. Do you know if ASP.NET MVC can be run on Apache’s mod_mono?

  24. Dan

    Nice article it would be awesome to see an updated version of this comparing asp.net MVC 3 to rails 3. Comparing rails 3 to MVC 2 imo is sort of like comparing the latest build of django to rails 1, not really the most fairest of comparisons. Especially when you look at how far rails has had to improve since the first release and how far asp.net mvc has progressed in similar fashion but a shorter amount of time, given it’s current progress ms’ implementation could over take rails in a couple of years time imo.

  25. bbqchickenrobot

    All this there is no “M” in asp.net mvc is non-sense. The “M” is pluggable which I prefer. Just some ideas…. System.Data.dll, Massive, Linq to SQL, Entity Framework (EF), EF Code First using POCOs, RavenDB, MongoDB + NoRM, Roll Y our Own (RYO), Subsonic (Repository + Active Record), LLBLGen, CSLA, etc….

    Not to mention, I can use Postgres, MySQL, MSSQL, Oracle, RavenDB, MongoDB, Memcached, SharedCache, CouchDB, Riak, Cassandra, etc…

    I prefer this over RoR. Also, C# is probably an easier language to learn for most people with a C/++ or Java background. If not, you have IronRuby, IronPython, Phalanger, Basic (VB.NET)

  26. Jay

    Really good post, got me interested in Ruby now.
    But…
    I would like to comment on this statement

    “No one can steal ideas like Microsoft!”

    If you give the MVC pattern to any decent architect and say “Build me a framework” they would almost all the time look like the implementations followed by MS or Ruby. MVC is a generic recipe and if implemented correctly will always look the same.

  27. Anders Janmyr

    @Jay, I disagree with this. The MVC pattern comes in many different forms and none of the web variants looks like the original smalltalk version.

    The convention over configuration was original when it appeared in Rails. Now it is easy to copy it. But it wasn’t at the time. And, the Microsoft version of MVC is VERY similar to Rails, but as I said. This is good.

  28. asdf

    Yah, what thiefs Microsoft made a controllers directory called “controllers”. Stopped reading the fanboyish post at that point.

  29. Anders Janmyr

    @asdf, I’m sorry to hear that, I do prefer Rails to ASP.MVC but, I think that ASP.MVC is very good and a joy to work with. I was also told by a colleague that many of the features I missed, have been added in later versions

    Anyway, I personally try to read posts I disagree with to avoid confirmation bias.

Leave a Reply