master 17b5ee5286c2 cached
133 files
112.6 KB
33.1k tokens
220 symbols
1 requests
Download .txt
Repository: taskrabbit/rails_engines_example
Branch: master
Commit: 17b5ee5286c2
Files: 133
Total size: 112.6 KB

Directory structure:
gitextract_k5k26vxe/

├── .gitignore
├── .rspec
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── apps/
│   ├── account/
│   │   ├── README.md
│   │   ├── account.gemspec
│   │   ├── app/
│   │   │   ├── controllers/
│   │   │   │   └── account/
│   │   │   │       ├── application_controller.rb
│   │   │   │       ├── login_controller.rb
│   │   │   │       └── users_controller.rb
│   │   │   ├── models/
│   │   │   │   └── account/
│   │   │   │       └── user.rb
│   │   │   └── views/
│   │   │       └── account/
│   │   │           ├── login/
│   │   │           │   └── new.haml
│   │   │           └── users/
│   │   │               └── new.haml
│   │   ├── config/
│   │   │   └── routes.rb
│   │   ├── db/
│   │   │   └── migrate/
│   │   │       ├── 20140128234906_create_users.rb
│   │   │       ├── 20140207012153_add_timestamp_to_users.rb
│   │   │       └── 20140207164357_add_admin_to_users.rb
│   │   └── lib/
│   │       ├── account/
│   │       │   └── engine.rb
│   │       └── account.rb
│   ├── admin/
│   │   ├── README.md
│   │   ├── admin.gemspec
│   │   ├── app/
│   │   │   ├── assets/
│   │   │   │   ├── javascript/
│   │   │   │   │   └── admin/
│   │   │   │   │       └── manifests/
│   │   │   │   │           └── application.js
│   │   │   │   └── stylesheets/
│   │   │   │       └── admin/
│   │   │   │           ├── base.css.sass
│   │   │   │           └── manifests/
│   │   │   │               └── application.css
│   │   │   ├── controllers/
│   │   │   │   └── admin/
│   │   │   │       ├── application_controller.rb
│   │   │   │       ├── home_controller.rb
│   │   │   │       ├── login_controller.rb
│   │   │   │       ├── panel_controller.rb
│   │   │   │       ├── posts_controller.rb
│   │   │   │       └── users_controller.rb
│   │   │   ├── helpers/
│   │   │   │   └── admin/
│   │   │   │       └── application_helper.rb
│   │   │   ├── models/
│   │   │   │   └── admin/
│   │   │   │       ├── post.rb
│   │   │   │       └── user.rb
│   │   │   ├── operations/
│   │   │   │   └── admin/
│   │   │   │       ├── post_search.rb
│   │   │   │       └── user_search.rb
│   │   │   └── views/
│   │   │       └── admin/
│   │   │           ├── home/
│   │   │           │   ├── index.haml
│   │   │           │   ├── post_search.haml
│   │   │           │   └── user_search.haml
│   │   │           ├── layouts/
│   │   │           │   └── admin.haml
│   │   │           ├── login/
│   │   │           │   └── new.haml
│   │   │           ├── posts/
│   │   │           │   ├── _panels.haml
│   │   │           │   ├── edit.haml
│   │   │           │   └── show.haml
│   │   │           └── users/
│   │   │               ├── _header.haml
│   │   │               ├── _panels.haml
│   │   │               ├── edit.haml
│   │   │               ├── posts.haml
│   │   │               └── show.haml
│   │   ├── config/
│   │   │   └── routes.rb
│   │   └── lib/
│   │       ├── admin/
│   │       │   └── engine.rb
│   │       └── admin.rb
│   ├── content/
│   │   ├── README.md
│   │   ├── app/
│   │   │   ├── assets/
│   │   │   │   └── stylesheets/
│   │   │   │       └── content/
│   │   │   │           ├── manifests/
│   │   │   │           │   └── posts.css
│   │   │   │           └── posts/
│   │   │   │               └── index.css.sass
│   │   │   ├── controllers/
│   │   │   │   └── content/
│   │   │   │       ├── application_controller.rb
│   │   │   │       └── posts_controller.rb
│   │   │   ├── helpers/
│   │   │   │   └── content/
│   │   │   │       └── post_helper.rb
│   │   │   ├── models/
│   │   │   │   └── content/
│   │   │   │       ├── post.rb
│   │   │   │       └── user.rb
│   │   │   └── views/
│   │   │       └── content/
│   │   │           └── posts/
│   │   │               └── index.haml
│   │   ├── config/
│   │   │   └── routes.rb
│   │   ├── content.gemspec
│   │   ├── db/
│   │   │   └── migrate/
│   │   │       └── 20140207011608_create_posts.rb
│   │   └── lib/
│   │       ├── content/
│   │       │   └── engine.rb
│   │       └── content.rb
│   ├── marketing/
│   │   ├── README.md
│   │   ├── app/
│   │   │   ├── controllers/
│   │   │   │   └── marketing/
│   │   │   │       ├── application_controller.rb
│   │   │   │       └── home_controller.rb
│   │   │   └── views/
│   │   │       └── marketing/
│   │   │           └── home/
│   │   │               └── index.haml
│   │   ├── config/
│   │   │   └── routes.rb
│   │   ├── lib/
│   │   │   ├── marketing/
│   │   │   │   └── engine.rb
│   │   │   └── marketing.rb
│   │   └── marketing.gemspec
│   └── shared/
│       ├── README.md
│       ├── app/
│       │   ├── assets/
│       │   │   ├── javascripts/
│       │   │   │   └── shared/
│       │   │   │       └── manifests/
│       │   │   │           └── application.js
│       │   │   └── stylesheets/
│       │   │       └── shared/
│       │   │           ├── base.css.sass
│       │   │           └── manifests/
│       │   │               └── application.css
│       │   ├── controllers/
│       │   │   └── shared/
│       │   │       └── controller/
│       │   │           ├── authentication.rb
│       │   │           ├── layout.rb
│       │   │           └── manifests.rb
│       │   ├── helpers/
│       │   │   └── shared/
│       │   │       └── helper/
│       │   │           └── errors.rb
│       │   ├── models/
│       │   │   └── shared/
│       │   │       ├── model/
│       │   │       │   └── read_only.rb
│       │   │       ├── operation/
│       │   │       │   └── base.rb
│       │   │       └── user/
│       │   │           ├── display.rb
│       │   │           └── stub.rb
│       │   └── views/
│       │       └── shared/
│       │           └── layouts/
│       │               ├── _errors.haml
│       │               └── application.haml
│       ├── lib/
│       │   ├── shared/
│       │   │   └── engine.rb
│       │   └── shared.rb
│       └── shared.gemspec
├── bin/
│   ├── bundle
│   ├── rails
│   └── rake
├── config/
│   ├── application.rb
│   ├── boot.rb
│   ├── database.yml
│   ├── environment.rb
│   ├── environments/
│   │   ├── development.rb
│   │   ├── production.rb
│   │   └── test.rb
│   ├── initializers/
│   │   ├── backtrace_silencers.rb
│   │   ├── debugger.rb
│   │   ├── filter_parameter_logging.rb
│   │   ├── inflections.rb
│   │   ├── mime_types.rb
│   │   ├── secret_token.rb
│   │   ├── session_store.rb
│   │   └── wrap_parameters.rb
│   ├── locales/
│   │   └── en.yml
│   └── routes.rb
├── config.ru
├── db/
│   ├── schema.rb
│   └── seeds.rb
├── lib/
│   ├── assets/
│   │   └── .keep
│   ├── boot_inquirer.rb
│   └── tasks/
│       └── .keep
├── log/
│   └── .keep
├── public/
│   ├── 404.html
│   ├── 422.html
│   ├── 500.html
│   └── robots.txt
└── spec/
    ├── account/
    │   ├── factories/
    │   │   └── user_factories.rb
    │   └── models/
    │       └── user_spec.rb
    ├── content/
    │   ├── factories/
    │   │   └── post_factories.rb
    │   └── models/
    │       └── post_spec.rb
    ├── fixtures/
    │   ├── posts.yml
    │   └── users.yml
    ├── shared/
    │   └── models/
    │       └── user_display_spec.rb
    ├── spec_helper.rb
    └── support/
        ├── fixture_builder.rb
        ├── fixture_class_name_helper.rb
        └── test_after_commit.rb

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
#   git config --global core.excludesfile '~/.gitignore_global'

# Ignore bundler config.
/.bundle

# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal

# Ignore all logfiles and tempfiles.
/log/*.log
/tmp

.DS_Store
public/assets


================================================
FILE: .rspec
================================================
--color
--format progress

================================================
FILE: Gemfile
================================================
require File.dirname(__FILE__) + '/lib/boot_inquirer'

source 'https://rubygems.org'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.0.1'


gemspec path: "apps/shared"
BootInquirer.each_active_app do |app|
  gemspec path: "apps/#{app.gem_name}"
end


# Use sqlite3 as the database for Active Record
gem 'sqlite3'

# Use SCSS for stylesheets
gem 'sass-rails', '~> 4.0.0'
gem 'haml'

# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'


# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby

# Use jquery as the JavaScript library
gem 'jquery-rails'

# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
# gem 'jbuilder', '~> 1.2'

group :doc do
  # bundle exec rake doc:rails generates the API under doc/api.
  gem 'sdoc', require: false
end

# Use unicorn as the app server
# gem 'unicorn'

# Use Capistrano for deployment
# gem 'capistrano', group: :development

group :development, :test do
  gem 'byebug'
end

group :test do
  gem 'rspec'
  gem 'rspec-rails'
  gem 'factory_girl_rails'
  gem 'forgery'
  gem 'fixture_builder'
end



================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2014 TaskRabbit

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
# Rails Engines Example

This shows how to use engines for namespacing within a "operator" Rails application.
_View original
[post](http://tech.taskrabbit.com/blog/2014/02/11/rails-4-engines/)_.

## Table of contents<a name="top"></a>
- [Rails Engines](#rails-engines)
- [Versus Many Apps](#versus-many-apps)
- [Versus Single App](#versus-single-app)
- [Engine Usage](#engine-usage)
- [Admin](#admin)
- [Shared Code](#shared-code)
- [API Server](#api-server)
- [Strategies](#strategies)
- [Migrations and Models](#migrations-and-models)
- [Admin](#admin)
- [Assets](#assets)
- [Routes](#routes)
- [Tests](#tests)
- [Memory](#memory)
- [Folders and Files](#folders-and-files)
- [Interaction Between Engines](#interaction-between-engines)
- [Summary](#summary)


At
[TaskRabbit](https://www.taskrabbit.com),
we have gone through a few iterations on how we make our app(s). In the beginning, there was the monolithic Rails app in the standard way with 100+ models and their many corresponding controllers and views. Then we moved to several apps with their own logic and often using the big one via API. Our newest
[project](https://taskrabbit.co.uk)
is a single "app" made up of several Rails engines. We have found that this strikes a great balance between the (initial) straightforwardness of the single Rails app and the modularity of the more service-oriented architecture.

We've talked about this approach with a few people and they often ask very specific questions about the tactics used to make this happen, so let's go through it here and via a

[sample application](https://github.com/taskrabbit/rails_engines_example).[back to top](#top)

## Rails Engines <a name="rails-engines"></a>

[Rails Engines](http://edgeguides.rubyonrails.org/engines.html)
is basically a whole Rails app that lives in the container of another one. Put another way, as the docs note: an app itself is basically just an engine at the root level. Over the years, we've seen engines as parts of gems such as
[devise](https://github.com/plataformatec/devise/blob/7a9ae13baadc3643d0f5b74077d9760d19c56adb/lib/devise/rails.rb) or
[rails_admin](https://github.com/sferik/rails_admin/blob/master/lib/rails_admin/engine.rb).
These example show the power of engines by providing a large set of relatively self-contained functionality "mounted" into an app.

At some point, there was a talk that suggested the approach of putting my our functionality into engines and that the Rails team seemed to be devoting more and more time to make them a first class citizen. Our friends at Pivotal Labs were talking about it a lot, too. Sometimes
[good](http://pivotallabs.com/migrating-from-a-single-rails-app-to-a-suite-of-rails-engines/)
and sometimes
[not so good](http://pivotallabs.com/experience-report-engine-usage-that-didn-t-work/).

[back to top](#top)

## Versus Many Apps <a name="versus-many-apps"></a>

We'd seen an app balloon and get out of control before, leading us to try and find better ways of modularization. It was fun and somewhat liberating to say "Make a new app!" when there was a new problem domain to tackle. We also used it as a way to handle our growing organization. We could ask Team A to work on App A and know that they could run faster by understanding the scope was limited to that. As a side-note and in retrospect, we probably let organizational factors affect architecture way more than appropriate.

Lots of things were great about this scenario. The teams had freedom to explore new approaches and we learned a lot. App B could upgrade Rack (or whatever) because it depended on the crazy thing that App A depended on. App C had the terrible native code-dependent gem and we only had to put that on the App C servers. Memory usage was kept lower, allowing us to run more background workers and unicorn threads.

But things got rough in coordinating across these apps. It wasn't just the data access. We made APIs and allowed any app to have read-only access to the platform app's database. This allowed things go much faster by preventing creation of many GET endpoints and possible points of failure. The main issue in coordinating releases that spanned apps is that they just went slower than if it was one codebase. There was also interminable bumping of gem versions to get shared code to all the apps. Integration testing the whole experience was also very rough.

So it's a simple one, but the main advantage that we've seen in the engine model is that it is one codebase and git repo. A single pull request has everything related to that feature. It rolls out atomically. Gems can be bumped once and our internal gems aren't bumped at all as they live unbuilt in a `gems` folder in the app itself. We still get most of the modularization that multiple apps had. For example, the User model in the payments engine has all the stuff about balances and the one in the profile engine doesn't know anything about all that and it's various helper methods.

The issue with gem upgrades and odd server configurations does continue to exist in the engine model and is mostly fine in the many app model. The gem one is tough and we just try to stay on top of upgrading to the newest things and overall reducing dependencies. The specs will also run slower in the engine app, but you'll have better integration testing. I'll go over a little bit about we've tackled server configurations and memory further down.

[back to top](#top)

## Versus Single App <a name="versus-single-app"></a>

It's very tempting when green-fielding a project to just revert back to the good-old-days of the original app. Man, that was so nice back before the (too) fat models and tangled views and combinatorics of 4 years of iterating screwed things up. And we've learned a lot since then too, right? Especially about saying no to all those
[combinatorics](http://firstround.com/article/The-one-cost-engineers-and-product-managers-dont-consider)
and also using
[decorators](http://robots.thoughtbot.com/tidy-views-and-beyond-with-decorators)
and
[service objects](http://adequate.io/culling-the-activerecord-lifecycle)
and using
[APIs](http://www.api-first.com/).
Maybe.

What we do know is that you can feel that way again even a year into an app. Inside any given engine, you have the scope of a much smaller project. Some engines may grow larger and you'll start to use those tools to keep things under control. Some will (correctly) have limited scope and feel like a simple app in which you understand everything that is happening. For example, decorators are great tool and they came in handy in our big app and larger engines. However, we've found in an a targeted engine that only serves its one purpose, it feels like there is room in that model to have some things that would have been decorated in a larger app. This is because it doesn't have all that other junk in it. Only this engine's junk :-)

[back to top](#top)

## Engine Usage <a name="engine-usage"></a>

We've seen a few different ways to use engines in a Rails project. A few examples are below. The basic variables are what is in the "operator" (root) app and what kind of app we're making (API driven or not).

[back to top](#top)

### Admin <a name="admin"></a>

The first engine we've recommend making to people is the admin engine. In the first app, we made the mistake of putting admin functionality in the "normal" pages. It was very enticing. We had that form already for the user to edit it. Just by changing the permissions, we could allow the admin to edit it, too. Forms are cheap and admins want extra fields. And more info. And basically a different UI.

So we can made an engine basically just like rails_admin did and gave it's own layout and views and JS and models and controllers, etc. Overall, we started treating our hardworking admins like we should: a customer with their own needs and dedicated experience.

The structure looked something like this...

```
app
  assets
  controllers
  models
    user.rb
    post.rb
  views
    layouts
admin
  app
    assets
    controllers
    models
      admin
        user.rb
        post.rb
    views
      layouts
config
db
  migrate
gems
spec
```

When we had this all mixed into one interface and set of models, at least a third of the code in a model like `Post` or `User` would be admin-specific actions. With this approach, we can give the admins a better, targeted experience and keep that code in admin-land.

Throughout these engine discussions, the question of sharing code and/or inheriting from objects will keep coming up. Specifically, for the admin scenario, we say do whatever works for you and on a case by case basis. In the above approach, we would probably tend to have `Admin::Post < ::Post` and other such inheritance. In Rails 2, we probably wouldn't have done what as they would have different `attr_accessible` situations but that's happening in the controller these days, so now inheriting from them will just get the benefit of the data validations, which is something we definitely want to share.

Note that inheriting is probably a bad choice if you have callbacks in the root model that you don't want triggered when the admin saves the record. In that case, it would be better to `Admin::Post < ActiveRecord::Base` and either duplicate the logic, have it only in SQL table (unique indexes for example), or have a mixin that is included in both.

[back to top](#top)

### Shared Code <a name="shared-code"></a>

The note about controllers being in charge of the parameters involved leads to the next possibility. You can have your models (at least the ones you need to have shared) in the operator and all the other stuff in the engines. At this point, maybe you could add the `engines` namespace to be more clear.

```
app
  models
    user.rb
    post.rb
config
db
  migrate
engines
  customer
    app
      assets
      controllers
      models
        customer
          something_admin_doesnt_use.rb
      views
        layouts
  admin
    app
      assets
      controllers
      models
        admin
          admin_notes.rb
      views
        layouts
gems
spec
```

Now you can use `Post` from both and everything is just fine. This would work out well if it's mostly the data definition you are using and like to use things like decorators and/or service objects and/or fat controllers in your engines.

You could also put layouts or mixins in the operator. This might be a good idea if you were sharing the layout between two engines. At that point, maybe we'll just go all in on the engines by making a `shared` engine. Having a namespace for clarity is much simpler.

```
apps
  shared
    app
      assets
      controllers
        shared
          authentication.rb
      models
        shared
          post.rb
          user.rb
      views
        shared
          layouts
  marketing
    app
      controllers
        marketing
          application_controller.rb
          home_controller.rb
  content
    controllers
    models
      content
        something_admin_doesnt_use.rb
  admin
    app
      assets
      controllers
      models
        admin
          admin_notes.rb
      views
        layouts
config
db
  migrate
gems
spec
```

In this structure, admin can still get it's own layout if it wants, but marketing and content can easily share the same layout in addition to the models.

The
[example in Github](https://github.com/taskrabbit/rails_engines_example)
takes this just one step farther by not sharing models at all. Sharing the actual model can still lead to the
[god model](http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/)
situation of a mono-Rails app without the use of other mitigating objects. To keep things as tight as possible, we've allowed each engine to have their own
[User](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/apps/content/app/models/content/user.rb)
object, for example. If there is model code to share, it would still go in the shared engine, but as a mixin like
[this one](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/apps/shared/app/models/shared/user/display.rb).
Note that in a well-designed schema, only one of these actually writes to the database and the others include a `ReadOnly` module from the shared engine.

The repo's structure looks as follows:

```
apps
  shared
    app
      assets
      controllers
        shared
          controller
            authentication.rb
      models
        shared
          model
            read_only.rb
          user
            user_display.rb
      views
        shared
          layouts
  marketing
    app
      controllers
        marketing
          application_controller.rb
          home_controller.rb
        models
          marketing
            user.rb
    db
      migrate
  account
    app
      controllers
      models
        content
          user.rb
          post.rb
    db
      migrate
  content
    app
      assets
      controllers
      models
        admin
          post.rb
          user.rb
    db
      migrate
  admin
    app
      assets
      controllers
      models
        admin
          admin_notes.rb
          post.rb
          user.rb
      views
        layouts
    db
      migrate
config
gems
spec
```

[back to top](#top)

### API Server <a name="api-server"></a>

Our latest project at TaskRabbit basically looks the the above and the
[example](https://github.com/taskrabbit/rails_engines_example)
with one difference: we don't share layouts between our engines. We've made the choice to have all the frontend code in one engine and all of the other engines just serve API endpoints. There are several shared mixins for these backend engines, but they don't need a layout because they are just using
[jbuilder](https://github.com/rails/jbuilder)
to send back JSON to the frontend client. The frontend engine, therefore, doesn't really use any models and has all the assets and such. Admin still has its own layout and uses a more traditional Rails MVC approach.

It looks like this:

```
apps
  shared
    app
      assets
      controllers
        shared
          controller
            authentication.rb
      models
        shared
          model
            read_only.rb
          user
            user_display.rb
  frontend
    app
      assets
      controllers
        marketing
          application_controller.rb
          home_controller.rb
        models
          marketing
            user.rb
      views
        frontend
          layouts
  account
    app
      controllers
      models
        content
          user.rb
          post.rb
      views
        account
          users
            show.json.jbuilder
    db
      migrate
  content
    app
      controllers
      models
        admin
          post.rb
          user.rb
      views
    db
      migrate
  admin
    app
      assets
      controllers
      models
        admin
          admin_notes.rb
          post.rb
          user.rb
      views
        layouts
    db
      migrate
config
gems
spec
```

The API setup alleviates one of the odder things about the example approach. Ideally, there is no interaction between engines. Particularly in the models and views, this is critical. However, some knowledge leaks out in the example though the controllers. For example, the
[login controller](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/apps/account/app/controllers/account/application_controller.rb#L11)
redirects to `/posts` after login. This is in the content engine. It's probably not the end of the world but that is coupling. We get around this using our one frontend engine and the several API ones, but this does some serious commitment.

[back to top](#top)

## Strategies <a name="strategies"></a>

We've gotten lots of questions and read about issues people are having with engines so let's go through them here.

[back to top](#top)

### Migrations and Models <a name="migrations-and-models"></a>

Rails bills itself as "convention over configuration" so it's not too surprising to be confronted with lots of questions about "where to put stuff" when deviating (slightly) from the conventions. The one people seem the most worried about are migrations. We've never had an issue, but there must be scenarios that get a little tricky. If you are sharing the models, we would just put them in the normal `db/migrate` location. If your models live inside the engines, it's probably not a huge deal to still do that, but we've decided to have the migrations live with their models.

As notes, each model/table (say `users`) ideally has one master model. In the sample app, the `User` model's master is in the
[account](https://github.com/taskrabbit/rails_engines_example/tree/434e687b795ec52705a3be1dd2c635f0054336d4/apps/account)
engine. This engine is in charge of signing up and logging in users. Fleshed out, it would also be responsible for reseting a lost password and editing account information. It's the only `User` model that
[mentions](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/apps/account/app/models/account/user.rb#L7)
`has_secure_password` and knows anything about that kind of thing. The rest of the engines may
[need](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/apps/content/app/models/content/user.rb#L5)
a `User` model but they have the `ReadOnly`
[module](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/apps/shared/app/models/shared/model/read_only.rb)
to prevent actually writing to the table.

Therefore, the account engine has the
[migrations](https://github.com/taskrabbit/rails_engines_example/tree/434e687b795ec52705a3be1dd2c635f0054336d4/apps/account/db/migrate)
having to do with the users table. In order to register that migrations are within these engines, we
[add](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/apps/account/lib/account/engine.rb)
a snippet like the following to each engine.

```ruby
initializer 'account.append_migrations' do |app|
  unless app.root.to_s == root.to_s
    config.paths["db/migrate"].expanded.each do |path|
      app.config.paths["db/migrate"].push(path)
    end
  end
end
```

This (via
[here](http://pivotallabs.com/leave-your-migrations-in-your-rails-engines/))
puts the engine's migrations in the path. Migrations continue to work as they normally do with the timestamps and such. So our `db/migrate` folder doesn't have any files in it (and is not checked into git). I have one locally, just because when I make a migration, Rails creates it automatically. However, I end up doing something like this immediately.

```bash
$ bundle exec rails g migration CreatePosts
      invoke  active_record
      create    db/migrate/20140207011608_create_posts.rb
$ mv db/migrate/20140207011608_create_posts.rb apps/content/db/migrate
```

You might wonder, and it does come up, what to do when you are adding a column to the users table for some other feature in some other engine. For example, we added a boolean `admin` column to the example users table to know if the given user is allowed to do stuff in the admin engine. We see the notion of permissions as being within the account engine's scope, even if it's not being actively leveraged there. It's still part of the account. Therefore, we
[added](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/apps/account/db/migrate/20140207164357_add_admin_to_users.rb)
the migration to the account engine.

In part, if I couldn't justify to myself why it would be part of the account engine, it would be a red flag. Specifically, should this even be in the users table at all. If the answer is "yes" for whatever reason, then I'd likely still put the migration in the account engine, but usually it helps me realize that it shouldn't be in the users table at all. A good example that came up in our app was the notion of profile. It seemed like it was 1-to-1 with users and what ever columns supported it should go in the users table. For a variety of reasons, including that we wanted a different engine for that, we ended up making it's own table with a a `has_one` relationship in that engine. This paid off even further as we realized that a `User` should actually have two profiles, one for their activity as a TaskPoster and one as a TaskRabbit, as they record and display very different information. Each has their own table and engine now.

Let's say we wanted to cache the number of posts the user had made. That's a pretty clearcut case to use `counter_cache` and put a `posts_count` in the users table. We'll want to look closely at this situation. First of all, the `counter_cache` code would clearly go on the `User` model in the content engine. That would also require that model to not be read-only or at least not in spirit (depending on the specifics used to implement the feature). It's not a good feeling when you do all this architecture stuff and it gets in the way of something that is so easy and we have to look out for those cases. If this is one of those cases, just do it; literally, however you want. We would probably keep the migration in the account engine.

It might not be one of those cases, though. I have almost never been sorry when I've made another model in these cases. So we could make a `PostStatistic` model or something in the content engine which `belongs_to :user` for recording this (and likely other things that come up). The counter cache feature is not magic - we just increment that table as necessary. It also doesn't feel that superfluous as it exists only inside that engine (which. in turn, doesn't have all the random stuff internal to other engines). We have some tables that started out that way. Mostly because we actively try not to do JOINs on our API calls, these tables ending up being the hub of the most relevant data of what has happening in our marketplace. Another option that we've used in similar situations is not to make the column at all. The content engine, or whoever is using this kind of data, would use the timestamp of the last `Post` or some other data to use as the cache key to look up all kinds of stuff in a store like memcache or Redis. If it's not there, it will take bit the bullet and calculate it and store it in the cache.

Again, architecture does not exist for fun or to get in the way. If something is super-simple and obvious and easy to maintain while doing the "right" way for the design is difficult and fragile, we just do it the easy way. That's the way to ship things for customers. However, we've found that in most case the rules of the system kick off useful discussions and behaviors that tend to work out quite well.

[back to top](#top)

### Admin <a name="admin"></a>

One of the cases where it's important to really examine the value and return on investment in engine separation is with the admin engine. We believe it's a special case.

In our system, the admin engine has it's own migrations. For example, we have a model called `AdminNote` where an admin can jot down little notes about most objects in the system. It clearly owns that. But the reason this whole experience exists in the first place is that it also is able to write more or less whatever it wants to _all_ the objects in the system. This clearly violates our single-model-master rule. So we don't fight an uphill battle here by making a special case and saying that the admin engine can literally do whatever it wants. All the other engines live in complete isolation from each other for a variety of reasons. Admin can depend directly on any or all of them. It's at the top of the food chain because it needs to regulate the whole system.

So it's
[fine](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/apps/admin/app/models/admin/post.rb)
if `Admin::Post < Content::Post` or just uses `Content::Post` directly in it's controllers. It's just not worth it to share all of the data definitions and validations with when it will almost always be with engine X and admin. Note that it's important to have the same validations because admin might be in charge, but it still needs to produce valid data as that other engine will be using it.

In our much larger app, we inherit from and/or use most of the models in the system as well as service objects from other engines. We do not use outside controllers or views. Our admin engine does use it's own layout and much simpler request cycle than our much fancier frontend app. We tried to show the admin engine using a different layout in the example app, but they're both bootstrap so it might be hard to tell. The header is red in admin :-)

[back to top](#top)

### Assets <a name="assets"></a>

Everyone seems to have struggled with this one and I can't even imagine pulling apart assets if they weren't coded in a modular way at the start. However, starting with them separate in Rails 4 has been fairly straightforward. We add the following
[code](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/apps/account/lib/account/engine.rb)
to our engine much like the migration code.

```ruby
initializer 'account.asset_precompile_paths' do |app|
  app.config.assets.precompile += ["account/manifests/*"]
end
```
You could list all the manifests one by one, but we've found that it's simpler to just always put them in a folder created for the purpose. This works for both css and js. You would would reference those files something like this:

```ruby
= stylesheet_link_tag 'account/manifests/application'
= javascript_include_tag 'account/manifests/application'
```

[back to top](#top)

### Routes <a name="routes"></a>

In an Engine, routes go within the engine directory at the
[same](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/apps/account/config/routes.rb)
`config/routes.rb` path. It's important to note here that in order for these routes to be put into use in the overall app, the engine needs to be mounted. In a normal engine use case, you would mount rails_admin (say to /admin) to give a namespace in the url, but we think it's important that all of these engines get mounted at the root level. You can see our root routes.rb file
[here](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/config/routes.rb).

```ruby
RailsEnginesExample::Application.routes.draw do
  BootInquirer.each_active_app do |app|
    mount app.engine => '/', as: app.gem_name
  end
end
```

So as expected, the operator app has no routes of it's own and it's all handled by the engines. I'll add little more about the `BootInquirer` in a bit. It is just a helper class that knows all the engines. This means that the code is functionally something more like this:

```ruby
RailsEnginesExample::Application.routes.draw do
  mount Admin::Engine     => '/', as: 'admin'
  mount Account::Engine   => '/', as: 'account'
  mount Content::Engine   => '/', as: 'content'
  mount Marketing::Engine => '/', as: 'marketing'
end
```

It would really clean to have something other than root in these mountings, but it doesn't seem practical or that important. We want to be able to have full control over our url structure. For example, mounting the account engine at anything but root would prevent it from handling both the `/login` and `/signup` paths. The trade-off is that two engines could claim the same URLs and conflict with much confusion. That's something we can manage with minimal effort. We've found that most engine route files start with `scope` to put most things under one directory or a few `resources` which does basically the same thing.

Another important note is to
[use](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/apps/account/lib/account/engine.rb#L3)
`isolate_namespace` in your Engine declaration. That prevents various things like helper methods from leaking into other engines. This makes sense for our case because the whole point is to stay contained. Another side effect is route helpers like 'posts_path' to work as expected without needing to prefix them like `content.posts_path` in your views. I believe it might also make the parameters more regular (for example having `params[:post]` instead of `params[:content_post]`). Oh, just put it in
[there](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/apps/admin/lib/admin/engine.rb).

[back to top](#top)

### Tests <a name="tests"></a>

Many of the issues noted
[here](http://pivotallabs.com/experience-report-engine-usage-that-didn-t-work/)
revolve around testing. One of the promises of engines is the existence of the subcomponents that you could (theoretically) use in some other app. This is not the goal here. We are using engines maximize local simplicity in our application, not create a reusable library. To that end, we don't think the normal Engine testing mechanism of creating a dummy app within the engine is helpful.

On our first engine application, we put a `spec` folder within each engine and then wrote a `rspec_all.sh` script to run each of them. It was not the right way. To do that really correctly, you'd test at that level and you'd have to test again at the integration level. This is another case of it not being worth it. Now we just put all our specs in the spec
[directory](https://github.com/taskrabbit/rails_engines_example/tree/434e687b795ec52705a3be1dd2c635f0054336d4/spec) and run `rspec spec` to run them all.

Each engine has it's own directory in there to keep it somewhat separate and to be able to easily test all of a single engine and it ends up looking like a normal app's root spec folder with models, requests, controllers, etc. Much like the admin engine, there are no rules about what you can and can't use in the tests. The goal is make sure the code is right, not to follow some architectural edict. For example, in a test that checks whether a Task can be paid for, it's fine to use the models from the payment engine to make sure everything worked together well.

One thing that is interesting is
[fixtures](http://api.rubyonrails.org/v3.2.13/classes/ActiveRecord/Fixtures.html).
We like using fixtures because it's a pretty good balance between speed and fully executing most of the code in out tests. We use
[fixture_builder](https://github.com/rdy/fixture_builder)
to save the hassle of maintaining those yml files precisely. Anyway, the issue in the case where we have multiple engine's each with their own model class is that fixtures (and
[factories](https://github.com/thoughtbot/factory_girl)
for that matter) only get one class. So if you do something like this while testing in the content engine, you'd be in trouble:

```ruby
describe Content::Post do
  fixtures :users

  it "should be associated with a user" do
    user = users(:willy)
    post = Content::Post.new(content: "words")
    post.user = user
    post.save.should == true
    user.posts.count.should == 1
  end
end
```

This is a problem because of classes expecting to be a certain type. You'd get this error:

```bash
Failures:

  1) Content::Post should be associated with a user
     Failure/Error: post.user = user
     ActiveRecord::AssociationTypeMismatch:
       Content::User(#70346317272500) expected, got Account::User(#70346295701620)
```

So the user has to be an instance of the `Content::User` and not an `Account::User` class. We use a
[helper](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/spec/support/fixture_class_name_helper.rb)
to say what the classes are as well as switch between them. So this test will use the correct classes:

```ruby
describe Content::Post do
  fixtures :users

  it "should be associated with a user" do
    user = fixture(:users, :willy, Content)
    post = Content::Post.new(content: "words")
    post.user = user
    post.save.should == true
    user.posts.count.should == 1
  end
end
```

The same sort of thing could be done with FactoryGirl too. Often, we end up just using the ids more than we would in a normal test suite. The important thing to note is to just do whatever you feel gives you the best coverage with the most return on investment for your time.

[back to top](#top)

### Memory <a name="memory"></a>

You may have noticed the
[BootInquirer](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/lib/boot_inquirer.rb)
class mentioned earlier. This is a class that know about all the engines in the system.

```ruby
  APPS = {
      'a' => 'account',
      'c' => 'content',
      'm' => 'marketing',
      'z' => 'admin'
    }
```

It is called from three places.

```ruby
# Gemfile
gemspec path: "apps/shared"
BootInquirer.each_active_app do |app|
  gemspec path: "apps/#{app.gem_name}"
end

# application.rb
require_relative "../lib/boot_inquirer"
BootInquirer.each_active_app do |app|
  require app.gem_name
end

# routes.rb
BootInquirer.each_active_app do |app|
  mount app.engine => '/', as: app.gem_name
end
```

The main point here is to simplify even further how to add a new engine to the app. The secondary point is somewhat interesting, though. One of the potential downsides of an engine-based app, over multiple apps, is the larger memory footprint (or larger scale production rollout) of some obscure and complicated native library for just one of the engines. This would not be a problem if you could "boot" the app with the just _some_ of the engines enabled. The `BootInquirer` makes that possible. It inspects and environment variable to know which engines to add to the gemspec and require and route towards.

```
$ ENGINE_BOOT=am bundle exec rails c
    => will boot the account and marketing engines - but not content, admin, etc.
$ ENGINE_BOOT=-m bundle exec rails c
    => will boot all engines except marketing
```

We haven't actually seen memory be that different that in our large Rails app. In fact, it is less because of a combination of Ruby upgrades and less conspicuous gem consumption. However, memory-wise this setup allows us to use our one codebase like multiple apps. In that case, we use a load balancer to map url paths to the correct app.

This is also useful in processing background workers. You would likely get an extra Resque worker or two. It's important to have a good queue strategy (different queues per engine) and to really not have the engines depend on each other to make this work, of course.

In order for this to work, we need to be more mindful of our gem usage. The first step is changing
[application.rb](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/config/application.rb#L7)
to say `Bundler.setup(:default, Rails.env)` instead of `Bundler.require(:default, Rails.env)` as usual. This mean we will have to explicitly require the gems we are using instead of it happening automatically. Most of those dependencies are in the engines' gemspecs and they'd have to be required anyway. However, by changing this line, we'll have to require what is needed from the main Gemfile as well. Ideally, there wouldn't be anything in
[there](https://github.com/taskrabbit/rails_engines_example/blob/434e687b795ec52705a3be1dd2c635f0054336d4/Gemfile) at all, but we have some Rails and test stuff that all the engines use.

You may notice that the exception we made for the admin engine rears its head here. If admin depends on the other engines, you won't be able to use admin experience unless you launch the app with all those engines. This is definitely true. The servers that the admin urls route to will have to have all of the engines running. We found it was useful to quarantine admin usage anyway as there are a few requests and inputs that could blow out the heap size fairly easily.

[back to top](#top)

### Folders and Files <a name="folders-and-files"></a>

If you're interested in this setup, you're just going to have to get used to it. There are a lot of directories. There are lot of files named the same thing. I've found that Sublime Text is better for this than Textmate. I'm a huge fan of ⌘T to open files and Sublime allows the use of the directory names in that typeahead list. If your editor doesn't do this, then you'll spend more time than you want to look through the six different `user.rb` or `application_contoller.rb` files in the project.

[back to top](#top)

### Interaction Between Engines <a name="interaction-between-engines"></a>

So we've gone through a lot of trouble to keep that shiny new Rails app feel. Each engine has a particular goal in life and everything is nice and simple. Particularly in the API case, it writes and reads its data and generally just takes of business. But the world isn't always perfect and sometimes the engines need to talk to each other. If it's happening too much, we probably didn't modularize along the right lines and we should consider throwing them together. We don't have all the answers, but engine naming and scoping seems to be a fine art. It's very tempting to go very narrow for cleanliness and it's also very tempting to just throw stuff in to an existing one so I'm not surprised when we find that the lines are a not drawn quite right.

There are other cases, though, that are not systemic errors in engine-picking and future-prediction. It's the kind of case I talked about with the `posts_count` above. Let's say we had a good reason to make that happen. Actually let's change it just a little bit to be more realistic. Let's say we had a profile engine where user could manage his online presence. Let's also say that other users could see and rate his posts. It's a completely reasonable thing to have an average post rating shown on his profile. Does this data about posts mean that the profile pages or API should be part of the content engine? We don't think so. This is likely just one tiny detail in an engine otherwise setup to upload photos, quote favorite movies, or whatever. We just need a little average rating on the there somewhere with a link to the posts.

In this case, we use our
[Resque Bus](https://github.com/taskrabbit/resque-bus)
gem extensively. This is a minor add-on to
[Resque](https://github.com/resque/resque/blob/1-x-stable/README.markdown)
that changes the paradigm just enough to allow us to decouple these engines. In a normal Rails apps using Resque, we would queue up a background worker to process the rating. This worker would calculate the new average rating and store it in the profile. Resque Bus uses publishing and subscription to accomplish similar goals. If you buy into this model, you have all of your engines and in this case the content engine, publishing to the bus when interesting things happen. Creation of a post or rating would be a good example. Other engines (or completely separate apps) then subscribe to events they find interesting. There can be more than one subscriber. Even when there is nothing particularly interesting to do, we've found that always having a subscriber to record the event produces a really useful log. In the rating case, though, the profile engine would also subscribe to the event and record the new rating. By one engine simply noting that something happened and the other reacting to the occurrence, we maintain the conceptual as well as physical (these engines could be on different servers) decoupling.

What exactly gets published and how that is used is up to the developers involved. There seems to be a few options in this specific case.

A) The content engine is publishing data changes. `ResqueBus.publish('post_rated', {post_id: 42, author_id: 2, rated_by: 4, rating: 4})`
B) The content engine adds some calculations. `ResqueBus.publish('post_rated', {post_id: 42, author_id: 2, rated_by: 4, rating: 4, new_average: 4.25, total_ratings: 20})`

Choosing option B is interesting for a few reasons:

* It is predicting the information other engines will want to know.
* It decreases the coupling because now the profile engine now just records the info instead of having to calculate it.
* It creates a record of the averages in our event store. Maybe we'll draw a graph of it sometime.
* It adds to the time required to complete the request to create the rating.

This would mean the post engine would have something like this in an initializer:

```ruby
ResqueBus.dispatch('profile') do
  subscribe 'post_rated' do |attributes|
    profile = Profile::Document.find_by(user_id: attributes['author_id'])
    profile.post_ratings_total  = attributes['total_ratings']
    profile.post_rating_average = attributes['new_average']
    profile.save!
  end
end
```

Or in the way that we prefer using a subscriber class that we would put in `profile/app/subscribers`:

```ruby
class Profile::ContentSubscriber
  include ResqueBus::Subscriber

  subscribe :post_created

  def post_created(attributes)
    profile = Profile::Document.find_by(user_id: attributes['post_author_id'])
    profile.post_ratings_total  = attributes['total_ratings']
    profile.post_rating_average = attributes['new_average']
    profile.save!
  end
end
```

It's clearly a fine option and the added time probably isn't too much assuming we have the right indexes on our database, but we actually tend to use option A. We don't particularly like trying to predict which events are interesting and how other engines will use them so we just publish on all creations or updates. We are fine with the profile engine having read-only `Rate` model and code to calculate the average. It could keep a running tally of the total number and just add this one to it, but we tend to recalculate it every time because it's not that hard and is less fragile.

It would look something like this:

```ruby
class Profile::ContentSubscriber
  include ResqueBus::Subscriber

  subscribe :post_rated

  def post_rated(attributes)
    total = Profile::Rate.where(author_id: attributes['author_id']).count
    sum   = Profile::Rate.where(author_id: attributes['author_id']).sum(:rating)

    profile = Profile::Document.find_by(user_id: attributes['post_author_id'])
    profile.post_ratings_total  = total
    profile.post_rating_average = sum.to_f / (5*total.to_f)
    profile.save!
  end
end
```

However you do it, the point is that this engine is working on it's own for it's own purposes. Layering it on, it's quite straightforward to see how we could build spam detection as its own engine or into the admin one. We could subscribe to ratings or post creation and react accordingly, maybe pulling the post or giving the user a score that limits his visibility, etc. Or we could add a metrics engine, to report the conversion of a user on his first post to a variety of external services. Then, when a new developer starts and asks where the metrics code is, we don't have to say what we said before which was, "everywhere." We could show very simple mappings between things that are happening throughout the system and the numbers like revenue or engagement that are getting reported to something like Google Analytics.

[back to top](#top)

## Summary <a name="summary"></a>

Try out engines. We like them.


================================================
FILE: Rakefile
================================================
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.

require File.expand_path('../config/application', __FILE__)

RailsEnginesExample::Application.load_tasks


================================================
FILE: apps/account/README.md
================================================
# Account

Signup, login, user management


================================================
FILE: apps/account/account.gemspec
================================================
$:.push File.expand_path("../lib", __FILE__)

# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
  s.name        = "account"
  s.version     = "0.0.1"
  s.authors     = ["Brian Leonard"]
  s.email       = ["brian@bleonard.com"]
  s.homepage    = "https://github.com/taskrabbit/rails_engines_example"
  s.summary     = "Account pages"
  s.description = "Users!"

  s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile"]
  s.test_files = Dir["spec/**/*"]

  s.add_dependency "rails",       "~> 4.0.1"
  s.add_dependency "bcrypt-ruby", "~> 3.1.2"  # has_secure_password
end


================================================
FILE: apps/account/app/controllers/account/application_controller.rb
================================================
module Account
  class ApplicationController < ActionController::Base
    # Prevent CSRF attacks by raising an exception.
    # For APIs, you may want to use :null_session instead.
    protect_from_forgery with: :exception
    
    include Shared::Controller::Layout

    def login!(user)
      session[:current_user_id] = user.id
      redirect_to '/posts'
    end

    def logout!
      session.delete(:current_user_id)
      redirect_to '/'
    end
  end
  
end





================================================
FILE: apps/account/app/controllers/account/login_controller.rb
================================================
module Account
  class LoginController < ::Account::ApplicationController
    helper Shared::Helper::Errors

    def new
      login!(current_user) and return if current_user
      @user = Account::User.new
    end

    def create
      @user = Account::User.find_by_email(account_params[:email])
      try_again and return unless @user
      try_again and return unless @user.authenticate(account_params[:password])
      login!(@user)
    end

    def destroy
      logout!
    end

    protected

    def try_again
      @user = Account::User.new(account_params)
      @user.errors.add(:base, "Account not found.")
      render :new
    end

    def account_params
      params.require(:user).permit(:email, :password)
    end
  end
end


================================================
FILE: apps/account/app/controllers/account/users_controller.rb
================================================
module Account
  class UsersController < ::Account::ApplicationController
    helper Shared::Helper::Errors

    def new
      login!(current_user) and return if current_user
      @user = Account::User.new
    end

    def create
      @user = User.new(account_params)
      if @user.save
        login!(@user)
      else
        render :new
      end
    end

    protected

    def account_params
      params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation)
    end
  end
end


================================================
FILE: apps/account/app/models/account/user.rb
================================================
require 'bcrypt'

module Account
  class User < ActiveRecord::Base
    self.table_name = :users

    has_secure_password

    validates :email, presence: true, uniqueness: true
    validates :first_name, :last_name, presence: true
  end
end


================================================
FILE: apps/account/app/views/account/login/new.haml
================================================
%h1 Log In

= show_object_errors(@user)

= form_for @user, url: login_path do |f|
  .form-group
    = f.label :email
    = f.text_field :email, class: 'form-control'

  .form-group
    = f.label :password
    = f.password_field :password, class: 'form-control'

  = f.submit "Log In", class: "btn btn-primary"


================================================
FILE: apps/account/app/views/account/users/new.haml
================================================
%h1 Sign Up

= show_object_errors(@user)

= form_for @user, url: signup_path do |f|
  .form-group
    = f.label :first_name
    = f.text_field :first_name, class: 'form-control'

  .form-group
    = f.label :last_name
    = f.text_field :last_name, class: 'form-control'

  .form-group
    = f.label :email
    = f.text_field :email, class: 'form-control'

  .form-group
    = f.label :password
    = f.password_field :password, class: 'form-control'

  .form-group
    = f.label :password_confirmation
    = f.password_field :password_confirmation, class: 'form-control'

  = f.submit "Create Account", class: "btn btn-primary"


================================================
FILE: apps/account/config/routes.rb
================================================
Account::Engine.routes.draw do
  get  'signup' => 'users#new'
  post 'signup' => 'users#create'

  get  'login'  => 'login#new'
  post 'login'  => 'login#create'
  get  'logout' => 'login#destroy'
end


================================================
FILE: apps/account/db/migrate/20140128234906_create_users.rb
================================================
class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :first_name
      t.string :last_name
      t.string :email, null: false
      t.string :password_digest
    end
  end
end


================================================
FILE: apps/account/db/migrate/20140207012153_add_timestamp_to_users.rb
================================================
class AddTimestampToUsers < ActiveRecord::Migration
  def change
    add_column :users, :created_at, :datetime
    add_column :users, :updated_at, :datetime
  end
end


================================================
FILE: apps/account/db/migrate/20140207164357_add_admin_to_users.rb
================================================
class AddAdminToUsers < ActiveRecord::Migration
  def change
    add_column :users, :admin, :boolean, default: false
  end
end


================================================
FILE: apps/account/lib/account/engine.rb
================================================
module Account
  class Engine < ::Rails::Engine
    isolate_namespace Account
    
    initializer 'account.append_migrations' do |app|
      unless app.root.to_s == root.to_s
        config.paths["db/migrate"].expanded.each do |path|
          app.config.paths["db/migrate"].push(path)
        end
      end
    end

    initializer 'account.asset_precompile_paths' do |app|
      app.config.assets.precompile += ["account/manifests/*"]
    end
  end
end


================================================
FILE: apps/account/lib/account.rb
================================================
require "account/engine"

module Account
end


================================================
FILE: apps/admin/README.md
================================================
# Admin

Tools for admins to keep things running smoothly


================================================
FILE: apps/admin/admin.gemspec
================================================
$:.push File.expand_path("../lib", __FILE__)

# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
  s.name        = "admin"
  s.version     = "0.0.1"
  s.authors     = ["Brian Leonard"]
  s.email       = ["brian@bleonard.com"]
  s.homepage    = "https://github.com/taskrabbit/rails_engines_example"
  s.summary     = "Admin user tools"
  s.description = "Adminstrate!"

  s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile"]
  s.test_files = Dir["spec/**/*"]

  s.add_dependency "rails",       "~> 4.0.1"
  s.add_dependency "redcarpet"
  s.add_dependency "kaminari"
  s.add_dependency "kaminari-bootstrap"

  # admin can use other engines' models
  s.add_dependency "content"
  s.add_dependency "account"
end


================================================
FILE: apps/admin/app/assets/javascript/admin/manifests/application.js
================================================
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require bootstrap.min


================================================
FILE: apps/admin/app/assets/stylesheets/admin/base.css.sass
================================================
body
  padding-top: 70px
  
footer
  padding-left: 15px
  padding-right: 15px

.navbar
  background-color: maroon

a.panel-link.current
  color: black
  
a.panel-link.current:hover
  cursor: default


#title
  .dl-horizontal dt
    width: 60px
    
  .dl-horizontal dd 
    margin-left: 80px

================================================
FILE: apps/admin/app/assets/stylesheets/admin/manifests/application.css
================================================
/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
 * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the top of the
 * compiled file, but it's generally better to create a new file per style scope.
 *
 *= require bootstrap.min
 *= require admin/base
 */


================================================
FILE: apps/admin/app/controllers/admin/application_controller.rb
================================================
module Admin
  class ApplicationController < ActionController::Base
    # Prevent CSRF attacks by raising an exception.
    # For APIs, you may want to use :null_session instead.
    protect_from_forgery with: :exception

    layout 'admin/layouts/admin'
    include Shared::Controller::Manifests
    
    helper_method :current_user

    before_filter :require_admin_user

    def login!(user)
      session[:admin_user_id] = user.id
      redirect_to '/admin'
    end

    def logout!
      session.delete(:admin_user_id)
      redirect_to '/admin/login'
    end

    def current_user
      return @current_user if defined?(@current_user)
      @current_user = nil
      return nil unless session[:admin_user_id]
      @current_user = Admin::User.find_by_id(session[:admin_user_id])
    end

    # for layout
    helper_method :skip_sidebar
    def skip_sidebar
      @skip_sidebar = true
    end


    protected

    def require_admin_user
      redirect_to "/admin/login" and return unless current_user
      logout! unless current_user.admin?
    end

  end
end


================================================
FILE: apps/admin/app/controllers/admin/home_controller.rb
================================================
module Admin
  class HomeController < ::Admin::ApplicationController

    before_action :skip_sidebar
    
    def index
      @post_search = Admin::PostSearch.new
      @user_search = Admin::UserSearch.new
    end

    def post_search
      op = Admin::PostSearch.new(current_user)
      if op.submit(params)
        @results = op.results
        if @results.size == 1
           redirect_to @results.first
        end
      else
        redirect_to root_path, alert: 'No results found'
      end
    end

    def user_search
      op = Admin::UserSearch.new(current_user)
      if op.submit(params)
        @results = op.results
        if @results.size == 1
           redirect_to @results.first
        end
      else
        redirect_to root_path, alert: 'No results found'
      end
    end
  end
end


================================================
FILE: apps/admin/app/controllers/admin/login_controller.rb
================================================
module Admin
  class LoginController < ::Admin::ApplicationController
    helper Shared::Helper::Errors

    skip_before_filter :require_admin_user

    def new
      login!(current_user) and return if current_user
      @user = Admin::User.new
    end

    def create
      @user = Admin::User.find_by_email(account_params[:email])
      try_again and return unless @user
      try_again and return unless @user.authenticate(account_params[:password]) 
      try_again and return unless @user.admin?
      login!(@user)
    end

    def destroy
      logout!
    end

    protected

    def try_again
      @user = Admin::User.new(account_params)
      @user.errors.add(:base, "Account not found.")
      render :new
    end

    def account_params
      params.require(:user).permit(:email, :password)
    end
  end
end


================================================
FILE: apps/admin/app/controllers/admin/panel_controller.rb
================================================
module Admin
  class PanelController < ::Admin::ApplicationController
    class << self
      def parent_prefixes
        out = super
        if @parent_panel
          out.unshift("admin/#{@parent_panel.tableize}")
        end
        out
      end

      def parent_panel(parent)
        @parent_panel = parent.to_s.singularize
        prepend_before_filter :load_parent_object
      end
    end

    protected

    def parent_panel
      self.class.instance_variable_get("@parent_panel")
    end

    def parent_panel_class
      return nil unless parent_panel
      "Admin::#{parent_panel.classify}".constantize
    end

    def load_parent_object
      return unless parent_panel
      parent_obj = parent_panel_class.find(params["#{parent_panel}_id"])
      @parent = parent_obj
      instance_variable_set("@#{parent_panel}", @parent)
      @parent
    end
  end
end


================================================
FILE: apps/admin/app/controllers/admin/posts_controller.rb
================================================
module Admin
  class PostsController < ::Admin::PanelController
    prepend_before_filter :fetch_object

    def show

    end

    def edit

    end

    def update
      if @post.update_attributes(object_params)
        redirect_to @post
      else
        flash[:alert] = @post.errors.full_messages.join(',')
        render :edit
      end
    end

    protected

    def fetch_object
      @post ||= Admin::Post.find(params[:id])
    end

    def object_params
      params.require(:post).permit!
    end
  end
end


================================================
FILE: apps/admin/app/controllers/admin/users_controller.rb
================================================
module Admin
  class UsersController < ::Admin::PanelController
    prepend_before_filter :fetch_object

    def show

    end

    def posts
      @posts = Admin::Post.where(user_id: @user.id).page(params[:page])
    end

    def edit

    end

    def update
      if @user.update_attributes(object_params)
        redirect_to @user
      else
        flash[:alert] = @user.errors.full_messages.join(',')
        render :edit
      end
    end

    protected

    def fetch_object
      @user ||= Admin::User.find(params[:id])
    end

    def object_params
      params.require(:user).permit!
    end
  end
end


================================================
FILE: apps/admin/app/helpers/admin/application_helper.rb
================================================
module Admin
  module ApplicationHelper
    def display_time(time, format=:day_zone)
      return "" if time.nil?
      I18n.l(time, :format => "%Y-%m-%d %H:%M %Z")
    end

    def display_markdown(content)
      return "" if content.blank?
      markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, :autolink => true, :space_after_headers => true)
      markdown.render(content).html_safe
    end

    def display_property(name, value, opts = {})
      opts = opts.reverse_merge({
        :html_escape => true,
      })

      return "" if (value.is_a?(Fixnum) && value == 0 && !opts[:allow_zero]) || (value.blank? && value != false && !opts[:allow_blank])

      if value.is_a?(ActiveRecord::Base)
        case value.class.table_name.to_s
        when "users"
          opts[:formatter] ||= :link_to_name
        end
      end

      value = value.strftime("%Y-%m-%d %H:%M") if value.respond_to?(:strftime)
      if opts[:html_escape]
        value = h(value) if value.is_a?(String)
        name = h(name)
      end

      value = self.send(opts[:formatter], value) if opts[:formatter]
      value = simple_format(value.html_safe) if opts[:paragraphs]

      "<dt>#{name}</dt><dd class='value-#{name.to_s.downcase.gsub(/\W/,"-")}'>#{value}</dd>".html_safe
    end

    def display_properties(obj, *keys)
      return nil if obj.nil?
      options = keys.extract_options!

      out = []
      keys.each do |key|
        name = key.to_s.titleize
        method = key
        value = obj.send(method)
        if value.is_a?(Hash)
          value.each do |hk, hv|
            out << display_property(hk, hv, options)
          end
        else
          out << display_property(name, value, options)
        end
      end
      out.join("\n").html_safe
    end

    def display_name(user, limit = nil)
      return "" unless user
      user.extend(::Shared::User::Display) unless user.respond_to?(:full_name)
      name = user.full_name
      name = truncate(name, length: limit) if limit
      name
    end

    def link_to_name(user, limit = nil)
      return "" unless user
      link_to display_name(user, limit), user_path(user.id)
    end

    def render_rescue(name, *args)
      name = "index_#{name}" if panel_collection?
      render name.to_s, *args
    rescue ActionView::MissingTemplate
      ""
    end

    def skip_sidebar?
      @skip_panels || @skip_sidebar
    end

    def skip_header?
      @skip_panels
    end

    def skip_top_bottom?
      @skip_panels
    end

    def panel_collection?
      @panel_collection
    end

    def skip_panels
      @skip_panels = true
    end

    def panel_collection(options = {})
      @panel_collection = true
      skip_sidebar unless options[:sidebar]
    end

    def alert_bootstrap(rails)
      case rails.to_s
      when "notice"
        "success"
      when "error", "alert"
        "danger"
      else
        # other: "warning"
        "info"
      end
    end
  end
end


================================================
FILE: apps/admin/app/models/admin/post.rb
================================================
module Admin
  class Post < ::Content::Post

  end
end


================================================
FILE: apps/admin/app/models/admin/user.rb
================================================
module Admin
  class User < ::Account::User

  end
end


================================================
FILE: apps/admin/app/operations/admin/post_search.rb
================================================
module Admin

  class PostSearch < ::Shared::Operation::Base

    fields  :query

    validates :query, presence: true
    attr_reader :results

    protected

    def page
      @page || 1
    end

    def perform
      return false if self.query.blank?
      @results = send("search_by_#{search_type}")
      @results = @results.order('posts.created_at DESC')
      @results = @results.page(self.page).per(20)
      @results.size > 0
    end

    # http://www.area-codes.org.uk/formatting.php
    def search_type
      case self.query.to_s
      when /^\d+$/
        :id
      else
        :content
      end
    end

    def search_by_id
      Admin::Post.where(id: self.query.to_i)
    end

    def search_by_content
      q = self.query
      Admin::Post.where("posts.content LIKE ?", "%#{q}%")
    end

  end


end


================================================
FILE: apps/admin/app/operations/admin/user_search.rb
================================================
module Admin

  class UserSearch < ::Shared::Operation::Base

    fields  :query

    validates :query, presence: true
    attr_reader :results

    protected

    def page
      @page || 1
    end

    def perform
      return false if self.query.blank?
      @results = send("search_by_#{search_type}")
      @results = @results.order('users.updated_at DESC')
      @results = @results.page(self.page).per(20)
      @results.size > 0
    end

    # http://www.area-codes.org.uk/formatting.php
    def search_type
      case self.query.to_s
      when /^.+@.+\..+{2,3}$/
        :email
      when /^\d+$/
        :id
      else
        :name
      end
    end

    def search_by_email
      Admin::User.where(email: self.query)
    end

    def search_by_id
      Admin::User.where(id: self.query.to_i)
    end

    def search_by_name
      q = self.query.split(" ")
      Admin::User.where("users.first_name LIKE ? OR users.last_name LIKE ?", "%#{q[0]}%", "%#{q[-1]}%")
    end

  end


end


================================================
FILE: apps/admin/app/views/admin/home/index.haml
================================================
.row
  .col-md-6
    = form_for @post_search, url: post_search_path, method: 'get', html: {class: 'form-inline'} do |f|
      %fieldset
        %legend Post Search
        .form-group
          = f.text_field :query, placeholder: "content, post id", name: 'query', class: "form-control", style: "width: 300px;"
        = f.submit 'Post Search', class: "btn btn-default"

  .col-md-6
    = form_for @user_search, url: user_search_path, method: 'get', html: {class: 'form-inline'} do |f|
      %fieldset
        %legend User Search
        .form-group
          = f.text_field :query, placeholder: "email, name, user id", name: 'query', class: "form-control", style: "width: 300px;"
        = f.submit 'User Search', class: "btn btn-default"


================================================
FILE: apps/admin/app/views/admin/home/post_search.haml
================================================
%table.table
  %thead
    %tr
      %th Created At
      %th Poster
      %th Content
  %tbody
    - @results.each do |post|
      %tr
        %td= link_to(display_time(post.created_at), post_path(post))
        %td= link_to_name(post.user)
        %td= truncate(post.content, length: 120)


================================================
FILE: apps/admin/app/views/admin/home/user_search.haml
================================================
%table.table
  %thead
    %tr
      %th Created At
      %th Name
      %th Email
  %tbody
    - @results.each do |user|
      %tr
        %td= display_time(user.created_at)
        %td= link_to_name(user)
        %td= link_to user.email, user


================================================
FILE: apps/admin/app/views/admin/layouts/admin.haml
================================================
!!! 5
%html
  %head
    %meta{charset: 'utf-8'}
    %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge,chrome=1'}
    %meta{name: 'viewport',    content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0 user-scalable=no'}
    %meta{name: 'apple-mobile-web-app-capable',          content: 'yes'}
    %meta{name: 'apple-mobile-web-app-status-bar-style', content: 'black'}

    %title TaskRabbit Admin

    = stylesheet_link_tag 'admin/manifests/application'
    = render_manifests('css')

    = yield :javascript

    = csrf_meta_tags

  %body
    .navbar.navbar-fixed-top.navbar-inverse{role: "navigation"}
      .container
        .navbar-header
          %a.navbar-brand{href: root_path} TaskRabbit Admin
        - if current_user
          %ul.nav.navbar-nav.navbar-right
            %li
              %a{href: logout_path} Logout


    .container
      - if !skip_header? && (header = render_rescue(:header)).present?
        #header.page-header
          = header

      - flash.keys.each do |name|
        %div{class: "alert alert-#{alert_bootstrap(name)}"}
          = flash[name] if flash[name].present?
        - flash.delete(name)

      .row
        - unless skip_sidebar?
          .col-md-3
            - if (title = render_rescue(:title)).present?
              #title.well
                = title

            - if (actions = render_rescue(:actions)).present?
              #actions.well
                = actions

            - if (panels = render_rescue(:panels)).present?
              #panels.well{role: "navigation"}
                = panels
        - col_class = skip_sidebar? ? "" : "col-md-9"
        %div{class: col_class}
          .container
            - if !skip_top_bottom? && (main_head = render_rescue(:top)).present?
              #top.row
                = main_head

            #panel.row
              = yield

            - if !skip_top_bottom? &&  (main_bottom = render_rescue(:bottom)).present?
              #bottom.row
                = main_bottom

      %footer

    = javascript_include_tag 'admin/manifests/application'
    = render_manifests('js')


================================================
FILE: apps/admin/app/views/admin/login/new.haml
================================================
%h1 Log In

= show_object_errors(@user)

= form_for @user, url: login_path do |f|
  .form-group
    = f.label :email
    = f.text_field :email, class: 'form-control'

  .form-group
    = f.label :password
    = f.password_field :password, class: 'form-control'

  = f.submit "Log In", class: "btn btn-primary"


================================================
FILE: apps/admin/app/views/admin/posts/_panels.haml
================================================
%ul.nav
  %li= link_to("Post", post_path(@post))
  %li= link_to("Edit", edit_post_path(@post))
  

================================================
FILE: apps/admin/app/views/admin/posts/edit.haml
================================================
%h2 Edit Post

= form_for @post do |f|
  .form-group
    = f.text_area :content, class: 'form-control'

  = f.submit "Post", class: "btn btn-primary"

================================================
FILE: apps/admin/app/views/admin/posts/show.haml
================================================
%h2 Post

%dl.dl-horizontal
  = display_properties(@post, :user, :created_at)

%hr

= display_markdown(@post.content)

================================================
FILE: apps/admin/app/views/admin/users/_header.haml
================================================
%h1=display_name(@user)

================================================
FILE: apps/admin/app/views/admin/users/_panels.haml
================================================
%ul.nav
  %li= link_to("Account", user_path(@user))
  %li= link_to("Edit", edit_user_path(@user))
  %li= link_to("Posts", posts_user_path(@user))

================================================
FILE: apps/admin/app/views/admin/users/edit.haml
================================================
%h2 Edit

= form_for @user do |f|
  .form-group
    = f.label :first_name
    = f.text_field :first_name, class: 'form-control'

  .form-group
    = f.label :last_name
    = f.text_field :last_name, class: 'form-control'

  .form-group
    = f.label :email
    = f.text_field :email, class: 'form-control'

  = f.submit "Edit", class: "btn btn-primary"


================================================
FILE: apps/admin/app/views/admin/users/posts.haml
================================================
%h2 My Posts

%table.table
  %thead
    %tr
      %th Time
      %th Content
  %tbody
    - @posts.each do |post|
      %tr
        %td= link_to(display_time(post.created_at), post_path(post))
        %td= display_markdown(post.content)

= paginate(@posts)

================================================
FILE: apps/admin/app/views/admin/users/show.haml
================================================
%h2 Info

%dl.dl-horizontal
  = display_properties(@user, :id, :email, :first_name, :last_name, :admin, :created_at)


================================================
FILE: apps/admin/config/routes.rb
================================================
Admin::Engine.routes.draw do
  scope "admin" do

    root to: 'home#index'
    get 'search/posts' => "home#post_search", as: 'post_search'
    get 'search/users' => "home#user_search", as: 'user_search'

    get  'login'  => 'login#new'
    post 'login'  => 'login#create'
    get  'logout' => 'login#destroy', as: 'logout'

    resources :users, only: [:show, :edit, :update] do
      member do
        get :posts
      end
    end

    resources :posts, only: [:show, :edit, :update]
  end
end


================================================
FILE: apps/admin/lib/admin/engine.rb
================================================
module Admin
  class Engine < ::Rails::Engine
    isolate_namespace Admin
    
    initializer 'admin.append_migrations' do |app|
      unless app.root.to_s == root.to_s
        config.paths["db/migrate"].expanded.each do |path|
          app.config.paths["db/migrate"].push(path)
        end
      end
    end

    initializer 'admin.asset_precompile_paths' do |app|
      app.config.assets.precompile += ["admin/manifests/*"]
    end
  end
end


================================================
FILE: apps/admin/lib/admin.rb
================================================
require "admin/engine"
require "kaminari"
require "kaminari-bootstrap"
require "redcarpet"

module Admin
end


================================================
FILE: apps/content/README.md
================================================
# Content

Posts, comments, etc


================================================
FILE: apps/content/app/assets/stylesheets/content/manifests/posts.css
================================================
/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
 * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the top of the
 * compiled file, but it's generally better to create a new file per style scope.
 *
 *= require content/posts/index
 */


================================================
FILE: apps/content/app/assets/stylesheets/content/posts/index.css.sass
================================================
.posts-index-container
  .post-list
    padding-top: 20px

  .post
    .timestamp
      float: right

================================================
FILE: apps/content/app/controllers/content/application_controller.rb
================================================
module Content
  class ApplicationController < ActionController::Base
    # Prevent CSRF attacks by raising an exception.
    # For APIs, you may want to use :null_session instead.
    protect_from_forgery with: :exception
    
    include Shared::Controller::Layout

  end
end


================================================
FILE: apps/content/app/controllers/content/posts_controller.rb
================================================
module Content
  class PostsController < ::Content::ApplicationController
    helper Shared::Helper::Errors

    manifest :posts

    before_action :require_user
    before_action :load_post, only: [:show, :edit, :update, :destroy]

    def index
      @post = Content::Post.new
      show_index
    end

    def create
      @post = Post.new(post_params)
      @post.user = current_user

      if @post.save
        flash[:notice] = "Post saved"
        @post = Post.new
      end
      show_index
    end

    protected

    def show_index
      @posts = current_user.posts.page(params[:page]).per(20)
      render :index
    end

    def load_post
      @post = Content::Post.find(params[:id])
      # say not found if not mine
      raise ActiveRecord::RecordNotFound unless @post.user_id == current_user.id
    end

    def post_params
      params.require(:post).permit(:content)
    end
  end
end


================================================
FILE: apps/content/app/helpers/content/post_helper.rb
================================================
module Content
  module PostHelper
    def display_content(content)
      markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, :autolink => true, :space_after_headers => true)
      markdown.render(content).html_safe
    end

    def display_time(datetime)
      diff = Time.now.to_i - datetime.to_i
      if diff.abs < 5
        "just now"
      else
        ago = time_ago_in_words(datetime)
        if diff > 0
          "#{ago} ago"
        else
          "#{ago} from now"
        end
      end
    end
  end
end


================================================
FILE: apps/content/app/models/content/post.rb
================================================
module Content
  class Post < ActiveRecord::Base
    self.table_name = :posts

    belongs_to :user, class_name: "Content::User"

    validates :user, :content, presence: true
  end
end


================================================
FILE: apps/content/app/models/content/user.rb
================================================
module Content
  class User < ActiveRecord::Base
    self.table_name = :users
    
    include Shared::Model::ReadOnly

    has_many :posts
  end
end


================================================
FILE: apps/content/app/views/content/posts/index.haml
================================================
.posts-index-container
  %h1 Posts

  = form_for @post do |f|
    .form-group
      = f.text_area :content, class: 'form-control'

    = f.submit "Post", class: "btn btn-primary"

  .post-list
    - @posts.each do |post|
      .post.well
        = display_content(post.content)
        %span.timestamp= display_time(post.created_at)

= paginate(@posts)


================================================
FILE: apps/content/config/routes.rb
================================================
Content::Engine.routes.draw do
  resources :posts, only: [:index, :create]
end


================================================
FILE: apps/content/content.gemspec
================================================
$:.push File.expand_path("../lib", __FILE__)

# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
  s.name        = "content"
  s.version     = "0.0.1"
  s.authors     = ["Brian Leonard"]
  s.email       = ["brian@bleonard.com"]
  s.homepage    = "https://github.com/taskrabbit/rails_engines_example"
  s.summary     = "Posts and such"
  s.description = "Blog!"

  s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile"]
  s.test_files = Dir["spec/**/*"]

  s.add_dependency "rails",       "~> 4.0.1"
  s.add_dependency "redcarpet"
  s.add_dependency "kaminari"
  s.add_dependency "kaminari-bootstrap"
end


================================================
FILE: apps/content/db/migrate/20140207011608_create_posts.rb
================================================
class CreatePosts < ActiveRecord::Migration
  def change
    create_table :posts do |t|
      t.integer :user_id
      t.text    :content
      t.timestamps
    end
  end
end


================================================
FILE: apps/content/lib/content/engine.rb
================================================
module Content
  class Engine < ::Rails::Engine
    isolate_namespace Content
    
    initializer 'content.append_migrations' do |app|
      unless app.root.to_s == root.to_s
        config.paths["db/migrate"].expanded.each do |path|
          app.config.paths["db/migrate"].push(path)
        end
      end
    end

    initializer 'content.asset_precompile_paths' do |app|
      app.config.assets.precompile += ["content/manifests/*"]
    end
  end
end


================================================
FILE: apps/content/lib/content.rb
================================================
require "content/engine"
require "redcarpet"
require "kaminari"
require "kaminari-bootstrap"

module Content
end


================================================
FILE: apps/marketing/README.md
================================================
# Marketing

Marketing pages for introducing service and such.


================================================
FILE: apps/marketing/app/controllers/marketing/application_controller.rb
================================================
module Marketing
  class ApplicationController < ActionController::Base
    # Prevent CSRF attacks by raising an exception.
    # For APIs, you may want to use :null_session instead.
    protect_from_forgery with: :exception
    
    include Shared::Controller::Layout

  end
  
end





================================================
FILE: apps/marketing/app/controllers/marketing/home_controller.rb
================================================
module Marketing
  class HomeController < ::Marketing::ApplicationController
    def index

    end
  end
end


================================================
FILE: apps/marketing/app/views/marketing/home/index.haml
================================================
%h1 Yeah!


================================================
FILE: apps/marketing/config/routes.rb
================================================
Marketing::Engine.routes.draw do
  root to: 'home#index'
end


================================================
FILE: apps/marketing/lib/marketing/engine.rb
================================================
module Marketing
  class Engine < ::Rails::Engine
    isolate_namespace Marketing

    initializer 'marketing.asset_precompile_paths' do |app|
      app.config.assets.precompile += ["marketing/manifests/*"]
    end
  end
end


================================================
FILE: apps/marketing/lib/marketing.rb
================================================
require "marketing/engine"

module Marketing
end


================================================
FILE: apps/marketing/marketing.gemspec
================================================
$:.push File.expand_path("../lib", __FILE__)

# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
  s.name        = "marketing"
  s.version     = "0.0.1"
  s.authors     = ["Brian Leonard"]
  s.email       = ["brian@bleonard.com"]
  s.homepage    = "https://github.com/taskrabbit/rails_engines_example"
  s.summary     = "Static marketing pages"
  s.description = "Sell it!"

  s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile"]
  s.test_files = Dir["spec/**/*"]

  s.add_dependency "rails", "~> 4.0.1"
end


================================================
FILE: apps/shared/README.md
================================================
# Shared

## Model

## Controller

## Layout


================================================
FILE: apps/shared/app/assets/javascripts/shared/manifests/application.js
================================================
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require bootstrap.min


================================================
FILE: apps/shared/app/assets/stylesheets/shared/base.css.sass
================================================
body
  padding-top: 70px
  
footer
  padding-left: 15px
  padding-right: 15px


================================================
FILE: apps/shared/app/assets/stylesheets/shared/manifests/application.css
================================================
/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
 * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the top of the
 * compiled file, but it's generally better to create a new file per style scope.
 *
 *= require bootstrap.min
 *= require shared/base
 */


================================================
FILE: apps/shared/app/controllers/shared/controller/authentication.rb
================================================
module Shared
  module Controller
    module Authentication
      extend ::ActiveSupport::Concern

      included do
        helper_method :logged_in?
        helper_method :current_user
      end

      def current_user
        return @current_user if defined?(@current_user)
        @current_user = nil
        return nil unless session[:current_user_id]

        namespace = self.class.name.split('::').first
        klass     = "#{namespace}::User".constantize rescue nil
        klass   ||= Shared::User::Stub

        @current_user = klass.find_by_id(session[:current_user_id])
      end

      protected

      def require_user
        redirect_to "/" unless current_user
      end
    end
  end
end

================================================
FILE: apps/shared/app/controllers/shared/controller/layout.rb
================================================
module Shared
  module Controller
    module Layout
      extend ::ActiveSupport::Concern

      included do
        layout 'shared/layouts/application'
        include Shared::Controller::Authentication
        include Shared::Controller::Manifests
      end
    end
  end
end


================================================
FILE: apps/shared/app/controllers/shared/controller/manifests.rb
================================================
module Shared
  module Controller
    module Manifests
      extend ::ActiveSupport::Concern

      included do
        helper Shared::Controller::Manifests::Helper
        helper_method :manifests_registered
      end

      module Helper
        def render_manifests(type)
          out = []
          
          manifests_registered(type).uniq.each do |manifest|
            case type
            when 'js'
              out << javascript_include_tag(manifest)
            when 'css'
              out << stylesheet_link_tag(manifest)
            end
          end
          out.join("\n").html_safe
        end
      end

      def manifests_registered(type)
        self.class.manifests_registered(type)
      end

      module ClassMethods
        def manifests_registered(type)
          case type
          when 'js'
            @js_manifests_registered  ||= []
          when 'css'
            @css_manifests_registered ||= []
          end
        end

        def manifests(*args)
          @js_manifests_registered  ||= []
          @css_manifests_registered ||= []

          args.each do |manifest|
            engine = self.name.split("::").first.underscore
            file = "#{engine}/manifests/#{manifest}"
            @js_manifests_registered  << file if Rails.application.assets.find_asset("#{file}.js").present?
            @css_manifests_registered << file if Rails.application.assets.find_asset("#{file}.css").present?
          end
        end
        alias :manifest :manifests
      end
    end
  end
end

















================================================
FILE: apps/shared/app/helpers/shared/helper/errors.rb
================================================
module Shared
  module Helper
    module Errors
      def show_object_errors(object)
        return "" if object.errors.size == 0
        render "/shared/layouts/errors", object: object
      end
    end
  end
end

================================================
FILE: apps/shared/app/models/shared/model/read_only.rb
================================================
module Shared
  module Model
    module ReadOnly
      
      def readonly?
        true
      end

      def delete
        raise ReadOnlyRecord
      end

    end
  end
end

================================================
FILE: apps/shared/app/models/shared/operation/base.rb
================================================
# Operation
#  the base implementation of a form object.
#  the form has a set of defined fields which can be used in validations.
#  the form object responds to `submit` and invokes validations
#  if validations pass the `perform` method is invoked
#  it is the responsibility of the concrete class to implement `perform`
#
#  Example usage:
#   class SignupForm < ::Backend::Operation
#
#     fields :email, :password, :password_confirmation
#
#     validates :email, :password, :password_confirmation, presence: true
#     validates :email, confirmation: true
#
#     validate :validate_email_is_gmail
#
#     protected
#
#     # at this point, validations have already run
#     def perform
#       user = User.new(self.attributes)
#
#       unless user.save
#        return self.inherit_errors_from(user)
#       end
#
#       true
#     end
#
#     def validate_email_is_gmail
#       unless self.email.to_s =~ /@gmail\.com/
#         self.errors.add(:email, :gmail)
#         return false
#       end
#
#       true
#     end
#   end
#

module Shared
  module Operation
    class Base
      include ::ActiveModel::Model
      include ::ActiveModel::Validations::Callbacks

      class_attribute :_fields
      self._fields = []
      class_attribute :_defaults
      self._defaults = {}
      class_attribute :_error_map
      self._error_map = {}



      class << self

        # fields can be provided in the following way:
        # field :field1, :field2
        # field :field3, :field4, default: 'my default'
        # field field5: 'field5 default', field6: 'field6 default'
        def field(*fields)
          last_hash = fields.extract_options!
          options   = last_hash.slice(:default, :scope)

          fields << last_hash.except(:default, :scope)

          fields.each do |f|

            if f.is_a?(Hash)
              f.each do |k,v|
                field(k, options.merge(default: v))
              end
            else

              _field(f, options)
            end
          end

        end
        alias_method :fields, :field

        def default(pairs)
          self._defaults = self._defaults.merge(pairs)
        end
        alias_method :defaults, :default

        def error_map(hash)
          self._error_map = self._error_map.merge(hash)
        end

        def inherited(base)
          super
          base._fields ||= self._fields
          base._defaults ||= self._defaults
          base._error_map ||= self._error_map
        end

        # Allows the form to be "found" by a user id. Used by delay functionality if included.
        def find_by_id(user_id)
          ns    = self.name.split('::').first
          user  = "#{ns}::User".constantize.find(user_id)
          new(user)
        end

        protected

        def _field(field_name, options = {})
          field = [options[:scope], field_name].compact.join('_')
          self._fields += [field]

          attr_accessor field.to_sym

          default(field => options[:default]) if options[:default]
        end

      end



      attr_reader :current_user

      def initialize(current_user = nil, options = {})
        @current_user     = current_user
        @original_params  = {}
        @filtered_params  = {}

        self.class._defaults.each do |k,v|
          self.send("#{k}=", v.respond_to?(:call) ? v.call : v)
        end
      end

      # for the delay functionality (if included into concrete implementation)
      def delay_id
        self.current_user.try(:id)
      end


      def submit!(params = {})
        unless submit(params)
          # todo: create error class for this
          raise ActiveRecord::RecordInvalid.new(self)
        end
        true
      end

      # the action which should be invoked upon form submission (from the controller)
      def submit(params = {})

        @original_params = params.with_indifferent_access
        @filtered_params = filter_params(@original_params)

        apply_params(@filtered_params)

        return false unless self.valid?

        self.perform

      rescue ActiveRecord::RecordInvalid => e
        self.inherit_errors_from(e.record)
        false
      end


      protected


      # implement this in your concrete class.
      def perform
        raise NotImplementedError
      end


      #wrap execution with an active record base transaction
      def transaction
        ActiveRecord::Base.transaction do
          yield
        end
      end


      # applies the errors to the form object from the child object, optionally at the namespace provided
      def inherit_errors_from(object, namespace = nil)
        inherit_errors(object.errors, namespace)
      end


      # applies the errors in error_object to self, optionally at the namespace provided
      # returns false so failure cases can end with this invocation
      def inherit_errors(error_object, namespace = nil)
        error_object.each do |k,v|

          keys  = [k, [namespace, k].compact.join('_')]
          keys  = keys.map{|key| _error_map[key.to_sym] || key }

          match = keys.detect{|key| self.respond_to?(key) || @original_params.try(:has_key?, key) }

          if match
            errors.add(match, v, api_id: v.api_id)
          else
            errors.add(:base, error_object.full_message(k, v), api_id: v.api_id)
          end

        end

        false
      end


      # grabs the attributes that match the given namespace
      def namespaced_attributes(namespace, options = {})

        regex = namespace.is_a?(Regexp) ? namespace : (namespace.blank? ? /(.+)/ : /^#{namespace}_(.+)/)

        ActiveSupport::HashWithIndifferentAccess.new.tap do |out|
          _fields.each do |field|
            if field =~ regex
              v = send(field)
              out[$1.to_sym] = v
            end
          end
        end
      end


      # if you want to use strong parameters or something in your form object you can do so here.
      def filter_params(params)
        params
      end


      def apply_params(params, namespace = nil)
        params.each do |key, value|

          setter = [namespace, key].compact.join('_')

          if respond_to?("#{setter}=") && _fields.include?(setter)
            send("#{setter}=", value)
          elsif value.is_a?(Hash)
            apply_params(value, setter)
          end
        end
      end


      def bool(input)
        !!(input.to_s =~ /t|1|y|ok/)
      end

    end
  end
end


================================================
FILE: apps/shared/app/models/shared/user/display.rb
================================================
module Shared
  module User
    module Display

      def display_name
        return "#{half_email}" if first_name.blank?
        return "#{first_name}" if last_name.blank?
        return "#{first_name} #{last_name[0,1]}."
      end

      def full_name
        return "#{email}" if email.present? && first_name.blank?
        return "#{first_name} #{last_name}" unless first_name.blank? || last_name.blank?
        return "#{first_name}" unless first.blank?
        return "#{last_name}" unless last.blank?
        return ""
      end

      private

      def half_email
        return "" if email.blank?
        index = email.index('@')
        return "" if index.nil? || index.to_i.zero?
        return email[0, index.to_i]
      end
      
    end
  end
end


================================================
FILE: apps/shared/app/models/shared/user/stub.rb
================================================
module Shared
  module User
    class Stub < ActiveRecord::Base
      self.table_name = :users

      include Shared::Model::ReadOnly

    end
  end
end


================================================
FILE: apps/shared/app/views/shared/layouts/_errors.haml
================================================
%ul.error_explanation.bg-danger
  - object.errors.full_messages.each do |msg|
    %li= msg


================================================
FILE: apps/shared/app/views/shared/layouts/application.haml
================================================
!!! 5
%html
  %head
    %meta{charset: 'utf-8'}
    %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge,chrome=1'}
    %meta{name: 'viewport',    content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0 user-scalable=no'}
    %meta{name: 'apple-mobile-web-app-capable',          content: 'yes'}
    %meta{name: 'apple-mobile-web-app-status-bar-style', content: 'black'}
    
    %title Rails Engines Example

    = stylesheet_link_tag 'shared/manifests/application'
    = render_manifests('css')
    
  %body
    .navbar.navbar-fixed-top.navbar-inverse{role: "navigation"}
      .container
        .navbar-header
          %button.navbar-toggle{type: 'button', "data-toggle"=>"collapse", "data-target"=> ".navbar-collapse"}
            %span.icon-bar
            %span.icon-bar
            %span.icon-bar
          %a.navbar-brand{href: "/"} Rails Engine Example
        .collapse.navbar-collapse
          %ul.nav.navbar-nav.navbar-right.dropdown
            - if current_user
              %li
                %a{href: "/posts"} Posts
              %li
                %a{href: "/logout"} Logout
            - else
              %li
                %a{href: "/signup"} Sign Up
              %li
                %a{href: "/login"} Log In
          
    .container
      = yield
      
      %footer
    
    = javascript_include_tag 'shared/manifests/application'
    = render_manifests('js')
        


================================================
FILE: apps/shared/lib/shared/engine.rb
================================================
module Shared
  class Engine < ::Rails::Engine
    isolate_namespace Shared
    
    initializer 'shared.asset_precompile_paths' do |app|
      app.config.assets.precompile += ["shared/manifests/*"]
    end

  end
end


================================================
FILE: apps/shared/lib/shared.rb
================================================
require "shared/engine"
require 'haml'
require 'jquery-rails'
require 'sass-rails'

module Shared
end


================================================
FILE: apps/shared/shared.gemspec
================================================
$:.push File.expand_path("../lib", __FILE__)

# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
  s.name        = "shared"
  s.version     = "0.0.1"
  s.authors     = ["Brian Leonard"]
  s.email       = ["brian@bleonard.com"]
  s.homepage    = "https://github.com/taskrabbit/rails_engines_example"
  s.summary     = "Stuff shared between engines"
  s.description = "Share it!"

  s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile"]
  s.test_files = Dir["spec/**/*"]

  s.add_dependency "rails", "~> 4.0.1"
end


================================================
FILE: bin/bundle
================================================
#!/usr/bin/env ruby
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
load Gem.bin_path('bundler', 'bundle')


================================================
FILE: bin/rails
================================================
#!/usr/bin/env ruby
APP_PATH = File.expand_path('../../config/application',  __FILE__)
require_relative '../config/boot'
require 'rails/commands'


================================================
FILE: bin/rake
================================================
#!/usr/bin/env ruby
require_relative '../config/boot'
require 'rake'
Rake.application.run


================================================
FILE: config/application.rb
================================================
require File.expand_path('../boot', __FILE__)

require 'rails/all'

# Note: this doesn't require the gems
# You should require when needed
Bundler.setup(:default, Rails.env)

# require railties and engines here.
require_relative "../lib/boot_inquirer"
require 'shared'

BootInquirer.each_active_app do |app|
  require app.gem_name
end

module RailsEnginesExample
  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.

    # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
    # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
    # config.time_zone = 'Central Time (US & Canada)'

    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
    # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
    config.i18n.enforce_available_locales = true
    config.i18n.default_locale = :en
  end
end


================================================
FILE: config/boot.rb
================================================
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)

require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])


================================================
FILE: config/database.yml
================================================
# SQLite version 3.x
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
development:
  adapter: sqlite3
  database: db/development.sqlite3
  pool: 5
  timeout: 5000

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  adapter: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000

production:
  adapter: sqlite3
  database: db/production.sqlite3
  pool: 5
  timeout: 5000


================================================
FILE: config/environment.rb
================================================
# Load the Rails application.
require File.expand_path('../application', __FILE__)

# Initialize the Rails application.
RailsEnginesExample::Application.initialize!


================================================
FILE: config/environments/development.rb
================================================
RailsEnginesExample::Application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # In the development environment your application's code is reloaded on
  # every request. This slows down response time but is perfect for development
  # since you don't have to restart the web server when you make code changes.
  config.cache_classes = false

  # Do not eager load code on boot.
  config.eager_load = false

  # Show full error reports and disable caching.
  config.consider_all_requests_local       = true
  config.action_controller.perform_caching = false

  # Don't care if the mailer can't send.
  config.action_mailer.raise_delivery_errors = false

  # Print deprecation notices to the Rails logger.
  config.active_support.deprecation = :log

  # Raise an error on page load if there are pending migrations
  config.active_record.migration_error = :page_load

  # Debug mode disables concatenation and preprocessing of assets.
  # This option may cause significant delays in view rendering with a large
  # number of complex assets.
  config.assets.debug = true
end


================================================
FILE: config/environments/production.rb
================================================
RailsEnginesExample::Application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # Code is not reloaded between requests.
  config.cache_classes = true

  # Eager load code on boot. This eager loads most of Rails and
  # your application in memory, allowing both thread web servers
  # and those relying on copy on write to perform better.
  # Rake tasks automatically ignore this option for performance.
  config.eager_load = true

  # Full error reports are disabled and caching is turned on.
  config.consider_all_requests_local       = false
  config.action_controller.perform_caching = true

  # Enable Rack::Cache to put a simple HTTP cache in front of your application
  # Add `rack-cache` to your Gemfile before enabling this.
  # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
  # config.action_dispatch.rack_cache = true

  # Disable Rails's static asset server (Apache or nginx will already do this).
  config.serve_static_assets = false

  # Compress JavaScripts and CSS.
  config.assets.js_compressor = :uglifier
  # config.assets.css_compressor = :sass

  # Do not fallback to assets pipeline if a precompiled asset is missed.
  config.assets.compile = false

  # Generate digests for assets URLs.
  config.assets.digest = true

  # Version of your assets, change this if you want to expire all your assets.
  config.assets.version = '1.0'

  # Specifies the header that your server uses for sending files.
  # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
  # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx

  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  # config.force_ssl = true

  # Set to :debug to see everything in the log.
  config.log_level = :info

  # Prepend all log lines with the following tags.
  # config.log_tags = [ :subdomain, :uuid ]

  # Use a different logger for distributed setups.
  # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)

  # Use a different cache store in production.
  # config.cache_store = :mem_cache_store

  # Enable serving of images, stylesheets, and JavaScripts from an asset server.
  # config.action_controller.asset_host = "http://assets.example.com"

  # Precompile additional assets.
  # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
  # config.assets.precompile += %w( search.js )

  # Ignore bad email addresses and do not raise email delivery errors.
  # Set this to true and configure the email server for immediate delivery to raise delivery errors.
  # config.action_mailer.raise_delivery_errors = false

  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
  # the I18n.default_locale when a translation can not be found).
  config.i18n.fallbacks = true

  # Send deprecation notices to registered listeners.
  config.active_support.deprecation = :notify

  # Disable automatic flushing of the log to improve performance.
  # config.autoflush_log = false

  # Use default logging formatter so that PID and timestamp are not suppressed.
  config.log_formatter = ::Logger::Formatter.new
end


================================================
FILE: config/environments/test.rb
================================================
RailsEnginesExample::Application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # The test environment is used exclusively to run your application's
  # test suite. You never need to work with it otherwise. Remember that
  # your test database is "scratch space" for the test suite and is wiped
  # and recreated between test runs. Don't rely on the data there!
  config.cache_classes = true

  # Do not eager load code on boot. This avoids loading your whole application
  # just for the purpose of running a single test. If you are using a tool that
  # preloads Rails for running tests, you may have to set it to true.
  config.eager_load = false

  # Configure static asset server for tests with Cache-Control for performance.
  config.serve_static_assets  = true
  config.static_cache_control = "public, max-age=3600"

  # Show full error reports and disable caching.
  config.consider_all_requests_local       = true
  config.action_controller.perform_caching = false

  # Raise exceptions instead of rendering exception templates.
  config.action_dispatch.show_exceptions = false

  # Disable request forgery protection in test environment.
  config.action_controller.allow_forgery_protection = false

  # Tell Action Mailer not to deliver emails to the real world.
  # The :test delivery method accumulates sent emails in the
  # ActionMailer::Base.deliveries array.
  config.action_mailer.delivery_method = :test

  # Print deprecation notices to the stderr.
  config.active_support.deprecation = :stderr
end


================================================
FILE: config/initializers/backtrace_silencers.rb
================================================
# Be sure to restart your server when you modify this file.

# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }

# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
# Rails.backtrace_cleaner.remove_silencers!


================================================
FILE: config/initializers/debugger.rb
================================================
begin
  require 'byebug'

  module Kernel
    alias :debugger :byebug
  end

rescue LoadError => e
  # byebug isn't installed - no debugging here!
end


================================================
FILE: config/initializers/filter_parameter_logging.rb
================================================
# Be sure to restart your server when you modify this file.

# Configure sensitive parameters which will be filtered from the log file.
Rails.application.config.filter_parameters += [:password]


================================================
FILE: config/initializers/inflections.rb
================================================
# Be sure to restart your server when you modify this file.

# Add new inflection rules using the following format. Inflections
# are locale specific, and you may define rules for as many different
# locales as you wish. All of these examples are active by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
#   inflect.plural /^(ox)$/i, '\1en'
#   inflect.singular /^(ox)en/i, '\1'
#   inflect.irregular 'person', 'people'
#   inflect.uncountable %w( fish sheep )
# end

# These inflection rules are supported but not enabled by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
#   inflect.acronym 'RESTful'
# end


================================================
FILE: config/initializers/mime_types.rb
================================================
# Be sure to restart your server when you modify this file.

# Add new mime types for use in respond_to blocks:
# Mime::Type.register "text/richtext", :rtf
# Mime::Type.register_alias "text/html", :iphone


================================================
FILE: config/initializers/secret_token.rb
================================================
# Be sure to restart your server when you modify this file.

# Your secret key is used for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!

# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
# You can use `rake secret` to generate a secure secret key.

# Make sure your secret_key_base is kept private
# if you're sharing your code publicly.
RailsEnginesExample::Application.config.secret_key_base = 'b9908facd821d0e467ad31b4515005c1a48343ff804f06abd5bcdea144202fb8e54e6b4ae420b2179624b5ef0768f4fa4db9bf1eaa8d2a5e29b1b6963875599d'


================================================
FILE: config/initializers/session_store.rb
================================================
# Be sure to restart your server when you modify this file.

RailsEnginesExample::Application.config.session_store :cookie_store, key: '_rails_engines_example_session'


================================================
FILE: config/initializers/wrap_parameters.rb
================================================
# Be sure to restart your server when you modify this file.

# This file contains settings for ActionController::ParamsWrapper which
# is enabled by default.

# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
ActiveSupport.on_load(:action_controller) do
  wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
end

# To enable root element in JSON for ActiveRecord objects.
# ActiveSupport.on_load(:active_record) do
#  self.include_root_in_json = true
# end


================================================
FILE: config/locales/en.yml
================================================
# Files in the config/locales directory are used for internationalization
# and are automatically loaded by Rails. If you want to use locales other
# than English, add the necessary files in this directory.
#
# To use the locales, use `I18n.t`:
#
#     I18n.t 'hello'
#
# In views, this is aliased to just `t`:
#
#     <%= t('hello') %>
#
# To use a different locale, set it with `I18n.locale`:
#
#     I18n.locale = :es
#
# This would use the information in config/locales/es.yml.
#
# To learn more, please read the Rails Internationalization guide
# available at http://guides.rubyonrails.org/i18n.html.

en:
  hello: "Hello world"


================================================
FILE: config/routes.rb
================================================
require 'boot_inquirer'

RailsEnginesExample::Application.routes.draw do

  BootInquirer.each_active_app do |app|
    mount app.engine => '/', as: app.gem_name
  end
end


================================================
FILE: config.ru
================================================
# This file is used by Rack-based servers to start the application.

require ::File.expand_path('../config/environment',  __FILE__)
run Rails.application


================================================
FILE: db/schema.rb
================================================
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20140207164357) do

  create_table "posts", force: true do |t|
    t.integer  "user_id"
    t.text     "content"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "users", force: true do |t|
    t.string   "first_name"
    t.string   "last_name"
    t.string   "email",                           null: false
    t.string   "password_digest"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.boolean  "admin",           default: false
  end

end


================================================
FILE: db/seeds.rb
================================================
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
#
# Examples:
#
#   cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
#   Mayor.create(name: 'Emanuel', city: cities.first)


================================================
FILE: lib/assets/.keep
================================================


================================================
FILE: lib/boot_inquirer.rb
================================================
# This determines which engines to boot / mount within the operator app (v3).
# The boot flag is a collection of characters representing the different engines in this project
# For example:
#   ~> ENGINE_BOOT=am bundle exec rails c
#   => will boot the account and marketing engines - but not content, admin, etc.
#
# The boot flag can be negated to have the opposite effect.
# For example:
#   ~> ENGINE_BOOT=-m bundle exec rails c
#   => will boot all engines except marketing
#
# The boot flag characters are not necessarily the first letter of each engine name, so check this file if you're using boot flags.
#
# When Rails.env is "test" all boot flags are assumed to be present, no matter what you provide.

class BootInquirer
  APPS = {
    'a' => 'account',
    'c' => 'content',
    'm' => 'marketing',
    'z' => 'admin'
  }

  class << self

    def apps
      APPS.map{ |k,v| BootInquirer::App.new(k, v) }
    end

    def each_active_app
      apps.each do |app|
        yield app if app.enabled?
      end
    end

    def any?(*keys)
      keys.any?{|k| send("#{k}?") }
    end

    def all?(*keys)
      keys.all?{|k| send("#{k}?") }
    end

    def using_boot_flags?
      boot_flag.present?
    end

    def method_missing(method_name, *args)
      if method_name.to_s =~ /(.+)\?$/
        app = apps.detect{|app| app.gem_name == $1}
        if app
          app.enabled?
        else
          super
        end
      else
        super
      end
    end

    def boot_flag
      @boot_flag ||= ENV['ENGINE_BOOT']
    end

    def negate?
      boot_flag.to_s =~ /^[\-\^]/
    end

    def boot_flag?(flag)
      return true if boot_flag.nil?

      default_value = !!boot_flag.to_s.index(flag)
      negate? ? !default_value : default_value
    end

  end

  class App
    attr_reader :key, :gem_name
    def initialize(key, val)
      @key = key
      @gem_name = val
    end

    def enabled?
      BootInquirer.boot_flag?(@key)
    end

    def engine
      module_name = gem_name.classify
      module_name << 's' if gem_name[-1] == 's'
      module_name.constantize.const_get(:Engine)
    end
  end
end


================================================
FILE: lib/tasks/.keep
================================================


================================================
FILE: log/.keep
================================================


================================================
FILE: public/404.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>The page you were looking for doesn't exist (404)</title>
  <style>
  body {
    background-color: #EFEFEF;
    color: #2E2F30;
    text-align: center;
    font-family: arial, sans-serif;
  }

  div.dialog {
    width: 25em;
    margin: 4em auto 0 auto;
    border: 1px solid #CCC;
    border-right-color: #999;
    border-left-color: #999;
    border-bottom-color: #BBB;
    border-top: #B00100 solid 4px;
    border-top-left-radius: 9px;
    border-top-right-radius: 9px;
    background-color: white;
    padding: 7px 4em 0 4em;
  }

  h1 {
    font-size: 100%;
    color: #730E15;
    line-height: 1.5em;
  }

  body > p {
    width: 33em;
    margin: 0 auto 1em;
    padding: 1em 0;
    background-color: #F7F7F7;
    border: 1px solid #CCC;
    border-right-color: #999;
    border-bottom-color: #999;
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
    border-top-color: #DADADA;
    color: #666;
    box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
  }
  </style>
</head>

<body>
  <!-- This file lives in public/404.html -->
  <div class="dialog">
    <h1>The page you were looking for doesn't exist.</h1>
    <p>You may have mistyped the address or the page may have moved.</p>
  </div>
  <p>If you are the application owner check the logs for more information.</p>
</body>
</html>


================================================
FILE: public/422.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>The change you wanted was rejected (422)</title>
  <style>
  body {
    background-color: #EFEFEF;
    color: #2E2F30;
    text-align: center;
    font-family: arial, sans-serif;
  }

  div.dialog {
    width: 25em;
    margin: 4em auto 0 auto;
    border: 1px solid #CCC;
    border-right-color: #999;
    border-left-color: #999;
    border-bottom-color: #BBB;
    border-top: #B00100 solid 4px;
    border-top-left-radius: 9px;
    border-top-right-radius: 9px;
    background-color: white;
    padding: 7px 4em 0 4em;
  }

  h1 {
    font-size: 100%;
    color: #730E15;
    line-height: 1.5em;
  }

  body > p {
    width: 33em;
    margin: 0 auto 1em;
    padding: 1em 0;
    background-color: #F7F7F7;
    border: 1px solid #CCC;
    border-right-color: #999;
    border-bottom-color: #999;
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
    border-top-color: #DADADA;
    color: #666;
    box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
  }
  </style>
</head>

<body>
  <!-- This file lives in public/422.html -->
  <div class="dialog">
    <h1>The change you wanted was rejected.</h1>
    <p>Maybe you tried to change something you didn't have access to.</p>
  </div>
  <p>If you are the application owner check the logs for more information.</p>
</body>
</html>


================================================
FILE: public/500.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>We're sorry, but something went wrong (500)</title>
  <style>
  body {
    background-color: #EFEFEF;
    color: #2E2F30;
    text-align: center;
    font-family: arial, sans-serif;
  }

  div.dialog {
    width: 25em;
    margin: 4em auto 0 auto;
    border: 1px solid #CCC;
    border-right-color: #999;
    border-left-color: #999;
    border-bottom-color: #BBB;
    border-top: #B00100 solid 4px;
    border-top-left-radius: 9px;
    border-top-right-radius: 9px;
    background-color: white;
    padding: 7px 4em 0 4em;
  }

  h1 {
    font-size: 100%;
    color: #730E15;
    line-height: 1.5em;
  }

  body > p {
    width: 33em;
    margin: 0 auto 1em;
    padding: 1em 0;
    background-color: #F7F7F7;
    border: 1px solid #CCC;
    border-right-color: #999;
    border-bottom-color: #999;
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
    border-top-color: #DADADA;
    color: #666;
    box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
  }
  </style>
</head>

<body>
  <!-- This file lives in public/500.html -->
  <div class="dialog">
    <h1>We're sorry, but something went wrong.</h1>
  </div>
  <p>If you are the application owner check the logs for more information.</p>
</body>
</html>


================================================
FILE: public/robots.txt
================================================
# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
# User-agent: *
# Disallow: /


================================================
FILE: spec/account/factories/user_factories.rb
================================================
FactoryGirl.define do

  factory :user, class: Account::User do
    sequence(:email) { |n| "user-#{n}-#{rand(9999)}@users.com" }
    password '12341234'
    password_confirmation '12341234'
    first_name { Forgery::Name.first_name }
    last_name  { Forgery::Name.last_name }
  end
end


================================================
FILE: spec/account/models/user_spec.rb
================================================
require 'spec_helper'

describe Account::User do
  fixtures :users

  let(:user) { users(:paul) }
  it "should authenticate" do
    user.authenticate("12341234").should be_true
  end
  
end


================================================
FILE: spec/content/factories/post_factories.rb
================================================
FactoryGirl.define do

  factory :post, class: Content::Post do
    user { Content::User.find(FactoryGirl.create(:user).id) }
    sequence(:content) { |n| "#{n} words here" }
  end
end


================================================
FILE: spec/content/models/post_spec.rb
================================================
require 'spec_helper'

describe Content::Post do
  fixtures :users

  it "should validate content" do
    Content::Post.new.should have(1).error_on(:content)
  end

  it "should be associated with a user" do
    user = fixture(:users, :willy, Content)
    post = Content::Post.new(content: "words")
    post.user = user
    post.save.should == true
    user.posts.count.should == 1
  end

end


================================================
FILE: spec/fixtures/posts.yml
================================================
---
post:
  id: 1
  user_id: 2
  content: Fixtures are cool.
  created_at: '2014-02-10 16:00:58.050118'
  updated_at: '2014-02-10 16:00:58.050118'


================================================
FILE: spec/fixtures/users.yml
================================================
---
willy:
  id: 1
  first_name: Willy
  last_name: Watcher
  email: willy@example.com
  password_digest: $2a$04$nYODEwHhzx0X/h0.sTTnNOEkaHjzA/M.WMadzNNIPONNwAn81khPm
  created_at: '2014-02-10 16:00:57.969093'
  updated_at: '2014-02-10 16:00:57.969093'
  admin: f
paul:
  id: 2
  first_name: Paul
  last_name: Poster
  email: paul@example.com
  password_digest: $2a$04$MTxF1wejSRbCAkZ8.0Kk7.8R1/SAMOE9DVAzC8uErmCc30r6NJgl2
  created_at: '2014-02-10 16:00:58.018112'
  updated_at: '2014-02-10 16:00:58.018112'
  admin: f


================================================
FILE: spec/shared/models/user_display_spec.rb
================================================
require 'spec_helper'

module UserDisplayTest
  class User < ActiveRecord::Base
    self.table_name = :users
    include Shared::User::Display
  end
end

describe Shared::User::Display do
  fixtures :users

  let(:user) { fixture(:users, :paul, UserDisplayTest) }
  it "should calculate display names" do
    user.class.name.should == "UserDisplayTest::User"
    user.display_name.should == "Paul P."
    user.full_name.should == "Paul Poster"

    user.first_name = nil
    user.last_name = nil
    user.display_name.should == "paul"
  end
end


================================================
FILE: spec/spec_helper.rb
================================================
ENV['RAILS_ENV'] = 'test'

require_relative "../config/environment.rb"

require 'rspec/rails'

require 'rack/utils'
require 'rspec/autorun'

ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)

RSpec.configure do |config|
  config.infer_base_class_for_anonymous_controllers = false
  config.order = "random"

  config.expect_with :rspec do |c|
    c.syntax = [:should, :expect]
  end

  config.before(:each) do
    Rails.cache.clear rescue nil

    Time.zone = 'UTC'
  end

  # define the factories
  require_relative 'support/test_after_commit'
  require 'factory_girl'
  require 'forgery'

  Dir[Rails.root.join("spec/*/factories/**/*.rb")].each  { |f| require f }

  # configure fixture options
  config.fixture_path               = "#{Rails.root}/spec/fixtures/"
  config.use_transactional_fixtures = true

  # fixup any namespace weirdness
  require_relative 'support/fixture_class_name_helper'
  config.include ::FixtureClassNameHelper

  # build the fixtures
  require_relative 'support/fixture_builder'
end


================================================
FILE: spec/support/fixture_builder.rb
================================================
require 'fixture_builder'

FixtureBuilder.configure do |fbuilder|
  fbuilder.files_to_check += Dir["spec/*/factories/**/*.rb", "spec/support/fixture_builder.rb", "apps/*/db/seeds.rb"]

  fbuilder.factory do
    # =================================================================
    # Seeds
    # =================================================================
    load(Rails.root.join('db/seeds.rb'))

    # =================================================================
    # Account
    # =================================================================
    willy = fbuilder.name(:willy, FactoryGirl.create(:user, first_name: 'Willy', last_name: 'Watcher',  email: 'willy@example.com')).first
    paul  = fbuilder.name(:paul,  FactoryGirl.create(:user, first_name: 'Paul',  last_name: 'Poster',   email: 'paul@example.com')).first

    # =================================================================
    # Content
    # =================================================================
    post = fbuilder.name(:post, FactoryGirl.create(:post, user_id: paul.id, content: "Fixtures are cool.")).first

  end

end


================================================
FILE: spec/support/fixture_class_name_helper.rb
================================================
# maps fixutres to their class names if different than defaults
# gets included by spec helper in the config block
module FixtureClassNameHelper

  extend ::ActiveSupport::Concern

  included do
    set_fixture_class({
      users: 'Account::User'
    })
  end


  def fixture(standard_method, standard_name, override_namespace = nil)
    model = send(standard_method, standard_name)


    # if the override namespace is not provided this uses the current test
    # context as a best guess for it
    if !override_namespace && described_class
      potential_namespace = described_class.name.split('::').first
      if potential_namespace.constantize.const_defined?(:Engine)
        override_namespace = potential_namespace
      end
    end

    return model unless override_namespace

    parts     = model.class.name.split('::')
    parts[0]  = override_namespace
    klass     = parts.join('::').constantize

    klass.send(:instantiate, model.attributes)
  end

end


================================================
FILE: spec/support/test_after_commit.rb
================================================
require 'active_record/connection_adapters/abstract/transaction'

module ActiveRecord
  module ConnectionAdapters
    class SavepointTransaction < ::ActiveRecord::ConnectionAdapters::OpenTransaction

      def perform_commit_with_transactional_fixtures
        out = perform_commit_without_transactional_fixtures
        commit_records if number == 1 # last one before test one that's always there, do callbacks
        out
      end
      alias_method_chain :perform_commit, :transactional_fixtures

    end
  end
end
Download .txt
gitextract_k5k26vxe/

├── .gitignore
├── .rspec
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── apps/
│   ├── account/
│   │   ├── README.md
│   │   ├── account.gemspec
│   │   ├── app/
│   │   │   ├── controllers/
│   │   │   │   └── account/
│   │   │   │       ├── application_controller.rb
│   │   │   │       ├── login_controller.rb
│   │   │   │       └── users_controller.rb
│   │   │   ├── models/
│   │   │   │   └── account/
│   │   │   │       └── user.rb
│   │   │   └── views/
│   │   │       └── account/
│   │   │           ├── login/
│   │   │           │   └── new.haml
│   │   │           └── users/
│   │   │               └── new.haml
│   │   ├── config/
│   │   │   └── routes.rb
│   │   ├── db/
│   │   │   └── migrate/
│   │   │       ├── 20140128234906_create_users.rb
│   │   │       ├── 20140207012153_add_timestamp_to_users.rb
│   │   │       └── 20140207164357_add_admin_to_users.rb
│   │   └── lib/
│   │       ├── account/
│   │       │   └── engine.rb
│   │       └── account.rb
│   ├── admin/
│   │   ├── README.md
│   │   ├── admin.gemspec
│   │   ├── app/
│   │   │   ├── assets/
│   │   │   │   ├── javascript/
│   │   │   │   │   └── admin/
│   │   │   │   │       └── manifests/
│   │   │   │   │           └── application.js
│   │   │   │   └── stylesheets/
│   │   │   │       └── admin/
│   │   │   │           ├── base.css.sass
│   │   │   │           └── manifests/
│   │   │   │               └── application.css
│   │   │   ├── controllers/
│   │   │   │   └── admin/
│   │   │   │       ├── application_controller.rb
│   │   │   │       ├── home_controller.rb
│   │   │   │       ├── login_controller.rb
│   │   │   │       ├── panel_controller.rb
│   │   │   │       ├── posts_controller.rb
│   │   │   │       └── users_controller.rb
│   │   │   ├── helpers/
│   │   │   │   └── admin/
│   │   │   │       └── application_helper.rb
│   │   │   ├── models/
│   │   │   │   └── admin/
│   │   │   │       ├── post.rb
│   │   │   │       └── user.rb
│   │   │   ├── operations/
│   │   │   │   └── admin/
│   │   │   │       ├── post_search.rb
│   │   │   │       └── user_search.rb
│   │   │   └── views/
│   │   │       └── admin/
│   │   │           ├── home/
│   │   │           │   ├── index.haml
│   │   │           │   ├── post_search.haml
│   │   │           │   └── user_search.haml
│   │   │           ├── layouts/
│   │   │           │   └── admin.haml
│   │   │           ├── login/
│   │   │           │   └── new.haml
│   │   │           ├── posts/
│   │   │           │   ├── _panels.haml
│   │   │           │   ├── edit.haml
│   │   │           │   └── show.haml
│   │   │           └── users/
│   │   │               ├── _header.haml
│   │   │               ├── _panels.haml
│   │   │               ├── edit.haml
│   │   │               ├── posts.haml
│   │   │               └── show.haml
│   │   ├── config/
│   │   │   └── routes.rb
│   │   └── lib/
│   │       ├── admin/
│   │       │   └── engine.rb
│   │       └── admin.rb
│   ├── content/
│   │   ├── README.md
│   │   ├── app/
│   │   │   ├── assets/
│   │   │   │   └── stylesheets/
│   │   │   │       └── content/
│   │   │   │           ├── manifests/
│   │   │   │           │   └── posts.css
│   │   │   │           └── posts/
│   │   │   │               └── index.css.sass
│   │   │   ├── controllers/
│   │   │   │   └── content/
│   │   │   │       ├── application_controller.rb
│   │   │   │       └── posts_controller.rb
│   │   │   ├── helpers/
│   │   │   │   └── content/
│   │   │   │       └── post_helper.rb
│   │   │   ├── models/
│   │   │   │   └── content/
│   │   │   │       ├── post.rb
│   │   │   │       └── user.rb
│   │   │   └── views/
│   │   │       └── content/
│   │   │           └── posts/
│   │   │               └── index.haml
│   │   ├── config/
│   │   │   └── routes.rb
│   │   ├── content.gemspec
│   │   ├── db/
│   │   │   └── migrate/
│   │   │       └── 20140207011608_create_posts.rb
│   │   └── lib/
│   │       ├── content/
│   │       │   └── engine.rb
│   │       └── content.rb
│   ├── marketing/
│   │   ├── README.md
│   │   ├── app/
│   │   │   ├── controllers/
│   │   │   │   └── marketing/
│   │   │   │       ├── application_controller.rb
│   │   │   │       └── home_controller.rb
│   │   │   └── views/
│   │   │       └── marketing/
│   │   │           └── home/
│   │   │               └── index.haml
│   │   ├── config/
│   │   │   └── routes.rb
│   │   ├── lib/
│   │   │   ├── marketing/
│   │   │   │   └── engine.rb
│   │   │   └── marketing.rb
│   │   └── marketing.gemspec
│   └── shared/
│       ├── README.md
│       ├── app/
│       │   ├── assets/
│       │   │   ├── javascripts/
│       │   │   │   └── shared/
│       │   │   │       └── manifests/
│       │   │   │           └── application.js
│       │   │   └── stylesheets/
│       │   │       └── shared/
│       │   │           ├── base.css.sass
│       │   │           └── manifests/
│       │   │               └── application.css
│       │   ├── controllers/
│       │   │   └── shared/
│       │   │       └── controller/
│       │   │           ├── authentication.rb
│       │   │           ├── layout.rb
│       │   │           └── manifests.rb
│       │   ├── helpers/
│       │   │   └── shared/
│       │   │       └── helper/
│       │   │           └── errors.rb
│       │   ├── models/
│       │   │   └── shared/
│       │   │       ├── model/
│       │   │       │   └── read_only.rb
│       │   │       ├── operation/
│       │   │       │   └── base.rb
│       │   │       └── user/
│       │   │           ├── display.rb
│       │   │           └── stub.rb
│       │   └── views/
│       │       └── shared/
│       │           └── layouts/
│       │               ├── _errors.haml
│       │               └── application.haml
│       ├── lib/
│       │   ├── shared/
│       │   │   └── engine.rb
│       │   └── shared.rb
│       └── shared.gemspec
├── bin/
│   ├── bundle
│   ├── rails
│   └── rake
├── config/
│   ├── application.rb
│   ├── boot.rb
│   ├── database.yml
│   ├── environment.rb
│   ├── environments/
│   │   ├── development.rb
│   │   ├── production.rb
│   │   └── test.rb
│   ├── initializers/
│   │   ├── backtrace_silencers.rb
│   │   ├── debugger.rb
│   │   ├── filter_parameter_logging.rb
│   │   ├── inflections.rb
│   │   ├── mime_types.rb
│   │   ├── secret_token.rb
│   │   ├── session_store.rb
│   │   └── wrap_parameters.rb
│   ├── locales/
│   │   └── en.yml
│   └── routes.rb
├── config.ru
├── db/
│   ├── schema.rb
│   └── seeds.rb
├── lib/
│   ├── assets/
│   │   └── .keep
│   ├── boot_inquirer.rb
│   └── tasks/
│       └── .keep
├── log/
│   └── .keep
├── public/
│   ├── 404.html
│   ├── 422.html
│   ├── 500.html
│   └── robots.txt
└── spec/
    ├── account/
    │   ├── factories/
    │   │   └── user_factories.rb
    │   └── models/
    │       └── user_spec.rb
    ├── content/
    │   ├── factories/
    │   │   └── post_factories.rb
    │   └── models/
    │       └── post_spec.rb
    ├── fixtures/
    │   ├── posts.yml
    │   └── users.yml
    ├── shared/
    │   └── models/
    │       └── user_display_spec.rb
    ├── spec_helper.rb
    └── support/
        ├── fixture_builder.rb
        ├── fixture_class_name_helper.rb
        └── test_after_commit.rb
Download .txt
SYMBOL INDEX (220 symbols across 50 files)

FILE: apps/account/app/controllers/account/application_controller.rb
  type Account (line 1) | module Account
    class ApplicationController (line 2) | class ApplicationController < ActionController::Base
      method login! (line 9) | def login!(user)
      method logout! (line 14) | def logout!

FILE: apps/account/app/controllers/account/login_controller.rb
  type Account (line 1) | module Account
    class LoginController (line 2) | class LoginController < ::Account::ApplicationController
      method new (line 5) | def new
      method create (line 10) | def create
      method destroy (line 17) | def destroy
      method try_again (line 23) | def try_again
      method account_params (line 29) | def account_params

FILE: apps/account/app/controllers/account/users_controller.rb
  type Account (line 1) | module Account
    class UsersController (line 2) | class UsersController < ::Account::ApplicationController
      method new (line 5) | def new
      method create (line 10) | def create
      method account_params (line 21) | def account_params

FILE: apps/account/app/models/account/user.rb
  type Account (line 3) | module Account
    class User (line 4) | class User < ActiveRecord::Base

FILE: apps/account/db/migrate/20140128234906_create_users.rb
  class CreateUsers (line 1) | class CreateUsers < ActiveRecord::Migration
    method change (line 2) | def change

FILE: apps/account/db/migrate/20140207012153_add_timestamp_to_users.rb
  class AddTimestampToUsers (line 1) | class AddTimestampToUsers < ActiveRecord::Migration
    method change (line 2) | def change

FILE: apps/account/db/migrate/20140207164357_add_admin_to_users.rb
  class AddAdminToUsers (line 1) | class AddAdminToUsers < ActiveRecord::Migration
    method change (line 2) | def change

FILE: apps/account/lib/account.rb
  type Account (line 3) | module Account

FILE: apps/account/lib/account/engine.rb
  type Account (line 1) | module Account
    class Engine (line 2) | class Engine < ::Rails::Engine

FILE: apps/admin/app/controllers/admin/application_controller.rb
  type Admin (line 1) | module Admin
    class ApplicationController (line 2) | class ApplicationController < ActionController::Base
      method login! (line 14) | def login!(user)
      method logout! (line 19) | def logout!
      method current_user (line 24) | def current_user
      method skip_sidebar (line 33) | def skip_sidebar
      method require_admin_user (line 40) | def require_admin_user

FILE: apps/admin/app/controllers/admin/home_controller.rb
  type Admin (line 1) | module Admin
    class HomeController (line 2) | class HomeController < ::Admin::ApplicationController
      method index (line 6) | def index
      method post_search (line 11) | def post_search
      method user_search (line 23) | def user_search

FILE: apps/admin/app/controllers/admin/login_controller.rb
  type Admin (line 1) | module Admin
    class LoginController (line 2) | class LoginController < ::Admin::ApplicationController
      method new (line 7) | def new
      method create (line 12) | def create
      method destroy (line 20) | def destroy
      method try_again (line 26) | def try_again
      method account_params (line 32) | def account_params

FILE: apps/admin/app/controllers/admin/panel_controller.rb
  type Admin (line 1) | module Admin
    class PanelController (line 2) | class PanelController < ::Admin::ApplicationController
      method parent_prefixes (line 4) | def parent_prefixes
      method parent_panel (line 12) | def parent_panel(parent)
      method parent_panel (line 20) | def parent_panel
      method parent_panel_class (line 24) | def parent_panel_class
      method load_parent_object (line 29) | def load_parent_object

FILE: apps/admin/app/controllers/admin/posts_controller.rb
  type Admin (line 1) | module Admin
    class PostsController (line 2) | class PostsController < ::Admin::PanelController
      method show (line 5) | def show
      method edit (line 9) | def edit
      method update (line 13) | def update
      method fetch_object (line 24) | def fetch_object
      method object_params (line 28) | def object_params

FILE: apps/admin/app/controllers/admin/users_controller.rb
  type Admin (line 1) | module Admin
    class UsersController (line 2) | class UsersController < ::Admin::PanelController
      method show (line 5) | def show
      method posts (line 9) | def posts
      method edit (line 13) | def edit
      method update (line 17) | def update
      method fetch_object (line 28) | def fetch_object
      method object_params (line 32) | def object_params

FILE: apps/admin/app/helpers/admin/application_helper.rb
  type Admin (line 1) | module Admin
    type ApplicationHelper (line 2) | module ApplicationHelper
      function display_time (line 3) | def display_time(time, format=:day_zone)
      function display_markdown (line 8) | def display_markdown(content)
      function display_property (line 14) | def display_property(name, value, opts = {})
      function display_properties (line 40) | def display_properties(obj, *keys)
      function display_name (line 60) | def display_name(user, limit = nil)
      function link_to_name (line 68) | def link_to_name(user, limit = nil)
      function render_rescue (line 73) | def render_rescue(name, *args)
      function skip_sidebar? (line 80) | def skip_sidebar?
      function skip_header? (line 84) | def skip_header?
      function skip_top_bottom? (line 88) | def skip_top_bottom?
      function panel_collection? (line 92) | def panel_collection?
      function skip_panels (line 96) | def skip_panels
      function panel_collection (line 100) | def panel_collection(options = {})
      function alert_bootstrap (line 105) | def alert_bootstrap(rails)

FILE: apps/admin/app/models/admin/post.rb
  type Admin (line 1) | module Admin
    class Post (line 2) | class Post < ::Content::Post

FILE: apps/admin/app/models/admin/user.rb
  type Admin (line 1) | module Admin
    class User (line 2) | class User < ::Account::User

FILE: apps/admin/app/operations/admin/post_search.rb
  type Admin (line 1) | module Admin
    class PostSearch (line 3) | class PostSearch < ::Shared::Operation::Base
      method page (line 12) | def page
      method perform (line 16) | def perform
      method search_type (line 25) | def search_type
      method search_by_id (line 34) | def search_by_id
      method search_by_content (line 38) | def search_by_content

FILE: apps/admin/app/operations/admin/user_search.rb
  type Admin (line 1) | module Admin
    class UserSearch (line 3) | class UserSearch < ::Shared::Operation::Base
      method page (line 12) | def page
      method perform (line 16) | def perform
      method search_type (line 25) | def search_type
      method search_by_email (line 36) | def search_by_email
      method search_by_id (line 40) | def search_by_id
      method search_by_name (line 44) | def search_by_name

FILE: apps/admin/lib/admin.rb
  type Admin (line 6) | module Admin

FILE: apps/admin/lib/admin/engine.rb
  type Admin (line 1) | module Admin
    class Engine (line 2) | class Engine < ::Rails::Engine

FILE: apps/content/app/controllers/content/application_controller.rb
  type Content (line 1) | module Content
    class ApplicationController (line 2) | class ApplicationController < ActionController::Base

FILE: apps/content/app/controllers/content/posts_controller.rb
  type Content (line 1) | module Content
    class PostsController (line 2) | class PostsController < ::Content::ApplicationController
      method index (line 10) | def index
      method create (line 15) | def create
      method show_index (line 28) | def show_index
      method load_post (line 33) | def load_post
      method post_params (line 39) | def post_params

FILE: apps/content/app/helpers/content/post_helper.rb
  type Content (line 1) | module Content
    type PostHelper (line 2) | module PostHelper
      function display_content (line 3) | def display_content(content)
      function display_time (line 8) | def display_time(datetime)

FILE: apps/content/app/models/content/post.rb
  type Content (line 1) | module Content
    class Post (line 2) | class Post < ActiveRecord::Base

FILE: apps/content/app/models/content/user.rb
  type Content (line 1) | module Content
    class User (line 2) | class User < ActiveRecord::Base

FILE: apps/content/db/migrate/20140207011608_create_posts.rb
  class CreatePosts (line 1) | class CreatePosts < ActiveRecord::Migration
    method change (line 2) | def change

FILE: apps/content/lib/content.rb
  type Content (line 6) | module Content

FILE: apps/content/lib/content/engine.rb
  type Content (line 1) | module Content
    class Engine (line 2) | class Engine < ::Rails::Engine

FILE: apps/marketing/app/controllers/marketing/application_controller.rb
  type Marketing (line 1) | module Marketing
    class ApplicationController (line 2) | class ApplicationController < ActionController::Base

FILE: apps/marketing/app/controllers/marketing/home_controller.rb
  type Marketing (line 1) | module Marketing
    class HomeController (line 2) | class HomeController < ::Marketing::ApplicationController
      method index (line 3) | def index

FILE: apps/marketing/lib/marketing.rb
  type Marketing (line 3) | module Marketing

FILE: apps/marketing/lib/marketing/engine.rb
  type Marketing (line 1) | module Marketing
    class Engine (line 2) | class Engine < ::Rails::Engine

FILE: apps/shared/app/controllers/shared/controller/authentication.rb
  type Shared (line 1) | module Shared
    type Controller (line 2) | module Controller
      type Authentication (line 3) | module Authentication
        function current_user (line 11) | def current_user
        function require_user (line 25) | def require_user

FILE: apps/shared/app/controllers/shared/controller/layout.rb
  type Shared (line 1) | module Shared
    type Controller (line 2) | module Controller
      type Layout (line 3) | module Layout

FILE: apps/shared/app/controllers/shared/controller/manifests.rb
  type Shared (line 1) | module Shared
    type Controller (line 2) | module Controller
      type Manifests (line 3) | module Manifests
        type Helper (line 11) | module Helper
          function render_manifests (line 12) | def render_manifests(type)
        function manifests_registered (line 27) | def manifests_registered(type)
        type ClassMethods (line 31) | module ClassMethods
          function manifests_registered (line 32) | def manifests_registered(type)
          function manifests (line 41) | def manifests(*args)

FILE: apps/shared/app/helpers/shared/helper/errors.rb
  type Shared (line 1) | module Shared
    type Helper (line 2) | module Helper
      type Errors (line 3) | module Errors
        function show_object_errors (line 4) | def show_object_errors(object)

FILE: apps/shared/app/models/shared/model/read_only.rb
  type Shared (line 1) | module Shared
    type Model (line 2) | module Model
      type ReadOnly (line 3) | module ReadOnly
        function readonly? (line 5) | def readonly?
        function delete (line 9) | def delete

FILE: apps/shared/app/models/shared/operation/base.rb
  type Shared (line 42) | module Shared
    type Operation (line 43) | module Operation
      class Base (line 44) | class Base
        method field (line 63) | def field(*fields)
        method default (line 84) | def default(pairs)
        method error_map (line 89) | def error_map(hash)
        method inherited (line 93) | def inherited(base)
        method find_by_id (line 101) | def find_by_id(user_id)
        method _field (line 109) | def _field(field_name, options = {})
        method initialize (line 124) | def initialize(current_user = nil, options = {})
        method delay_id (line 135) | def delay_id
        method submit! (line 140) | def submit!(params = {})
        method submit (line 149) | def submit(params = {})
        method perform (line 170) | def perform
        method transaction (line 176) | def transaction
        method inherit_errors_from (line 184) | def inherit_errors_from(object, namespace = nil)
        method inherit_errors (line 191) | def inherit_errors(error_object, namespace = nil)
        method namespaced_attributes (line 212) | def namespaced_attributes(namespace, options = {})
        method filter_params (line 228) | def filter_params(params)
        method apply_params (line 233) | def apply_params(params, namespace = nil)
        method bool (line 247) | def bool(input)

FILE: apps/shared/app/models/shared/user/display.rb
  type Shared (line 1) | module Shared
    type User (line 2) | module User
      type Display (line 3) | module Display
        function display_name (line 5) | def display_name
        function full_name (line 11) | def full_name
        function half_email (line 21) | def half_email

FILE: apps/shared/app/models/shared/user/stub.rb
  type Shared (line 1) | module Shared
    type User (line 2) | module User
      class Stub (line 3) | class Stub < ActiveRecord::Base

FILE: apps/shared/lib/shared.rb
  type Shared (line 6) | module Shared

FILE: apps/shared/lib/shared/engine.rb
  type Shared (line 1) | module Shared
    class Engine (line 2) | class Engine < ::Rails::Engine

FILE: config/application.rb
  type RailsEnginesExample (line 17) | module RailsEnginesExample
    class Application (line 18) | class Application < Rails::Application

FILE: config/initializers/debugger.rb
  type Kernel (line 4) | module Kernel

FILE: lib/boot_inquirer.rb
  class BootInquirer (line 16) | class BootInquirer
    method apps (line 26) | def apps
    method each_active_app (line 30) | def each_active_app
    method any? (line 36) | def any?(*keys)
    method all? (line 40) | def all?(*keys)
    method using_boot_flags? (line 44) | def using_boot_flags?
    method method_missing (line 48) | def method_missing(method_name, *args)
    method boot_flag (line 61) | def boot_flag
    method negate? (line 65) | def negate?
    method boot_flag? (line 69) | def boot_flag?(flag)
    class App (line 78) | class App
      method initialize (line 80) | def initialize(key, val)
      method enabled? (line 85) | def enabled?
      method engine (line 89) | def engine

FILE: spec/shared/models/user_display_spec.rb
  type UserDisplayTest (line 3) | module UserDisplayTest
    class User (line 4) | class User < ActiveRecord::Base

FILE: spec/support/fixture_class_name_helper.rb
  type FixtureClassNameHelper (line 3) | module FixtureClassNameHelper
    function fixture (line 14) | def fixture(standard_method, standard_name, override_namespace = nil)

FILE: spec/support/test_after_commit.rb
  type ActiveRecord (line 3) | module ActiveRecord
    type ConnectionAdapters (line 4) | module ConnectionAdapters
      class SavepointTransaction (line 5) | class SavepointTransaction < ::ActiveRecord::ConnectionAdapters::Ope...
        method perform_commit_with_transactional_fixtures (line 7) | def perform_commit_with_transactional_fixtures
Condensed preview — 133 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (128K chars).
[
  {
    "path": ".gitignore",
    "chars": 491,
    "preview": "# See https://help.github.com/articles/ignoring-files for more about ignoring files.\n#\n# If you find yourself ignoring t"
  },
  {
    "path": ".rspec",
    "chars": 25,
    "preview": "--color\n--format progress"
  },
  {
    "path": "Gemfile",
    "chars": 1179,
    "preview": "require File.dirname(__FILE__) + '/lib/boot_inquirer'\n\nsource 'https://rubygems.org'\n\n# Bundle edge Rails instead: gem '"
  },
  {
    "path": "LICENSE",
    "chars": 1077,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2014 TaskRabbit\n\nPermission is hereby granted, free of charge, to any person obtain"
  },
  {
    "path": "README.md",
    "chars": 43610,
    "preview": "# Rails Engines Example\n\nThis shows how to use engines for namespacing within a \"operator\" Rails application.\n_View orig"
  },
  {
    "path": "Rakefile",
    "chars": 264,
    "preview": "# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they wil"
  },
  {
    "path": "apps/account/README.md",
    "chars": 42,
    "preview": "# Account\n\nSignup, login, user management\n"
  },
  {
    "path": "apps/account/account.gemspec",
    "chars": 614,
    "preview": "$:.push File.expand_path(\"../lib\", __FILE__)\n\n# Describe your gem and declare its dependencies:\nGem::Specification.new d"
  },
  {
    "path": "apps/account/app/controllers/account/application_controller.rb",
    "chars": 468,
    "preview": "module Account\n  class ApplicationController < ActionController::Base\n    # Prevent CSRF attacks by raising an exception"
  },
  {
    "path": "apps/account/app/controllers/account/login_controller.rb",
    "chars": 740,
    "preview": "module Account\n  class LoginController < ::Account::ApplicationController\n    helper Shared::Helper::Errors\n\n    def new"
  },
  {
    "path": "apps/account/app/controllers/account/users_controller.rb",
    "chars": 521,
    "preview": "module Account\n  class UsersController < ::Account::ApplicationController\n    helper Shared::Helper::Errors\n\n    def new"
  },
  {
    "path": "apps/account/app/models/account/user.rb",
    "chars": 241,
    "preview": "require 'bcrypt'\n\nmodule Account\n  class User < ActiveRecord::Base\n    self.table_name = :users\n\n    has_secure_password"
  },
  {
    "path": "apps/account/app/views/account/login/new.haml",
    "chars": 310,
    "preview": "%h1 Log In\n\n= show_object_errors(@user)\n\n= form_for @user, url: login_path do |f|\n  .form-group\n    = f.label :email\n   "
  },
  {
    "path": "apps/account/app/views/account/users/new.haml",
    "chars": 629,
    "preview": "%h1 Sign Up\n\n= show_object_errors(@user)\n\n= form_for @user, url: signup_path do |f|\n  .form-group\n    = f.label :first_n"
  },
  {
    "path": "apps/account/config/routes.rb",
    "chars": 201,
    "preview": "Account::Engine.routes.draw do\n  get  'signup' => 'users#new'\n  post 'signup' => 'users#create'\n\n  get  'login'  => 'log"
  },
  {
    "path": "apps/account/db/migrate/20140128234906_create_users.rb",
    "chars": 226,
    "preview": "class CreateUsers < ActiveRecord::Migration\n  def change\n    create_table :users do |t|\n      t.string :first_name\n     "
  },
  {
    "path": "apps/account/db/migrate/20140207012153_add_timestamp_to_users.rb",
    "chars": 167,
    "preview": "class AddTimestampToUsers < ActiveRecord::Migration\n  def change\n    add_column :users, :created_at, :datetime\n    add_c"
  },
  {
    "path": "apps/account/db/migrate/20140207164357_add_admin_to_users.rb",
    "chars": 127,
    "preview": "class AddAdminToUsers < ActiveRecord::Migration\n  def change\n    add_column :users, :admin, :boolean, default: false\n  e"
  },
  {
    "path": "apps/account/lib/account/engine.rb",
    "chars": 456,
    "preview": "module Account\n  class Engine < ::Rails::Engine\n    isolate_namespace Account\n    \n    initializer 'account.append_migra"
  },
  {
    "path": "apps/account/lib/account.rb",
    "chars": 45,
    "preview": "require \"account/engine\"\n\nmodule Account\nend\n"
  },
  {
    "path": "apps/admin/README.md",
    "chars": 58,
    "preview": "# Admin\n\nTools for admins to keep things running smoothly\n"
  },
  {
    "path": "apps/admin/admin.gemspec",
    "chars": 753,
    "preview": "$:.push File.expand_path(\"../lib\", __FILE__)\n\n# Describe your gem and declare its dependencies:\nGem::Specification.new d"
  },
  {
    "path": "apps/admin/app/assets/javascript/admin/manifests/application.js",
    "chars": 648,
    "preview": "// This is a manifest file that'll be compiled into application.js, which will include all the files\n// listed below.\n//"
  },
  {
    "path": "apps/admin/app/assets/stylesheets/admin/base.css.sass",
    "chars": 291,
    "preview": "body\n  padding-top: 70px\n  \nfooter\n  padding-left: 15px\n  padding-right: 15px\n\n.navbar\n  background-color: maroon\n\na.pan"
  },
  {
    "path": "apps/admin/app/assets/stylesheets/admin/manifests/application.css",
    "chars": 559,
    "preview": "/*\n * This is a manifest file that'll be compiled into application.css, which will include all the files\n * listed below"
  },
  {
    "path": "apps/admin/app/controllers/admin/application_controller.rb",
    "chars": 1067,
    "preview": "module Admin\n  class ApplicationController < ActionController::Base\n    # Prevent CSRF attacks by raising an exception.\n"
  },
  {
    "path": "apps/admin/app/controllers/admin/home_controller.rb",
    "chars": 807,
    "preview": "module Admin\n  class HomeController < ::Admin::ApplicationController\n\n    before_action :skip_sidebar\n    \n    def index"
  },
  {
    "path": "apps/admin/app/controllers/admin/login_controller.rb",
    "chars": 822,
    "preview": "module Admin\n  class LoginController < ::Admin::ApplicationController\n    helper Shared::Helper::Errors\n\n    skip_before"
  },
  {
    "path": "apps/admin/app/controllers/admin/panel_controller.rb",
    "chars": 874,
    "preview": "module Admin\n  class PanelController < ::Admin::ApplicationController\n    class << self\n      def parent_prefixes\n      "
  },
  {
    "path": "apps/admin/app/controllers/admin/posts_controller.rb",
    "chars": 519,
    "preview": "module Admin\n  class PostsController < ::Admin::PanelController\n    prepend_before_filter :fetch_object\n\n    def show\n\n "
  },
  {
    "path": "apps/admin/app/controllers/admin/users_controller.rb",
    "chars": 614,
    "preview": "module Admin\n  class UsersController < ::Admin::PanelController\n    prepend_before_filter :fetch_object\n\n    def show\n\n "
  },
  {
    "path": "apps/admin/app/helpers/admin/application_helper.rb",
    "chars": 2948,
    "preview": "module Admin\n  module ApplicationHelper\n    def display_time(time, format=:day_zone)\n      return \"\" if time.nil?\n      "
  },
  {
    "path": "apps/admin/app/models/admin/post.rb",
    "chars": 55,
    "preview": "module Admin\n  class Post < ::Content::Post\n\n  end\nend\n"
  },
  {
    "path": "apps/admin/app/models/admin/user.rb",
    "chars": 55,
    "preview": "module Admin\n  class User < ::Account::User\n\n  end\nend\n"
  },
  {
    "path": "apps/admin/app/operations/admin/post_search.rb",
    "chars": 821,
    "preview": "module Admin\n\n  class PostSearch < ::Shared::Operation::Base\n\n    fields  :query\n\n    validates :query, presence: true\n "
  },
  {
    "path": "apps/admin/app/operations/admin/user_search.rb",
    "chars": 993,
    "preview": "module Admin\n\n  class UserSearch < ::Shared::Operation::Base\n\n    fields  :query\n\n    validates :query, presence: true\n "
  },
  {
    "path": "apps/admin/app/views/admin/home/index.haml",
    "chars": 740,
    "preview": ".row\n  .col-md-6\n    = form_for @post_search, url: post_search_path, method: 'get', html: {class: 'form-inline'} do |f|\n"
  },
  {
    "path": "apps/admin/app/views/admin/home/post_search.haml",
    "chars": 290,
    "preview": "%table.table\n  %thead\n    %tr\n      %th Created At\n      %th Poster\n      %th Content\n  %tbody\n    - @results.each do |p"
  },
  {
    "path": "apps/admin/app/views/admin/home/user_search.haml",
    "chars": 244,
    "preview": "%table.table\n  %thead\n    %tr\n      %th Created At\n      %th Name\n      %th Email\n  %tbody\n    - @results.each do |user|"
  },
  {
    "path": "apps/admin/app/views/admin/layouts/admin.haml",
    "chars": 2110,
    "preview": "!!! 5\n%html\n  %head\n    %meta{charset: 'utf-8'}\n    %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge,chrome=1'"
  },
  {
    "path": "apps/admin/app/views/admin/login/new.haml",
    "chars": 310,
    "preview": "%h1 Log In\n\n= show_object_errors(@user)\n\n= form_for @user, url: login_path do |f|\n  .form-group\n    = f.label :email\n   "
  },
  {
    "path": "apps/admin/app/views/admin/posts/_panels.haml",
    "chars": 97,
    "preview": "%ul.nav\n  %li= link_to(\"Post\", post_path(@post))\n  %li= link_to(\"Edit\", edit_post_path(@post))\n  "
  },
  {
    "path": "apps/admin/app/views/admin/posts/edit.haml",
    "chars": 149,
    "preview": "%h2 Edit Post\n\n= form_for @post do |f|\n  .form-group\n    = f.text_area :content, class: 'form-control'\n\n  = f.submit \"Po"
  },
  {
    "path": "apps/admin/app/views/admin/posts/show.haml",
    "chars": 117,
    "preview": "%h2 Post\n\n%dl.dl-horizontal\n  = display_properties(@post, :user, :created_at)\n\n%hr\n\n= display_markdown(@post.content)"
  },
  {
    "path": "apps/admin/app/views/admin/users/_header.haml",
    "chars": 23,
    "preview": "%h1=display_name(@user)"
  },
  {
    "path": "apps/admin/app/views/admin/users/_panels.haml",
    "chars": 145,
    "preview": "%ul.nav\n  %li= link_to(\"Account\", user_path(@user))\n  %li= link_to(\"Edit\", edit_user_path(@user))\n  %li= link_to(\"Posts\""
  },
  {
    "path": "apps/admin/app/views/admin/users/edit.haml",
    "chars": 353,
    "preview": "%h2 Edit\n\n= form_for @user do |f|\n  .form-group\n    = f.label :first_name\n    = f.text_field :first_name, class: 'form-c"
  },
  {
    "path": "apps/admin/app/views/admin/users/posts.haml",
    "chars": 256,
    "preview": "%h2 My Posts\n\n%table.table\n  %thead\n    %tr\n      %th Time\n      %th Content\n  %tbody\n    - @posts.each do |post|\n      "
  },
  {
    "path": "apps/admin/app/views/admin/users/show.haml",
    "chars": 117,
    "preview": "%h2 Info\n\n%dl.dl-horizontal\n  = display_properties(@user, :id, :email, :first_name, :last_name, :admin, :created_at)\n"
  },
  {
    "path": "apps/admin/config/routes.rb",
    "chars": 496,
    "preview": "Admin::Engine.routes.draw do\n  scope \"admin\" do\n\n    root to: 'home#index'\n    get 'search/posts' => \"home#post_search\","
  },
  {
    "path": "apps/admin/lib/admin/engine.rb",
    "chars": 446,
    "preview": "module Admin\n  class Engine < ::Rails::Engine\n    isolate_namespace Admin\n    \n    initializer 'admin.append_migrations'"
  },
  {
    "path": "apps/admin/lib/admin.rb",
    "chars": 109,
    "preview": "require \"admin/engine\"\nrequire \"kaminari\"\nrequire \"kaminari-bootstrap\"\nrequire \"redcarpet\"\n\nmodule Admin\nend\n"
  },
  {
    "path": "apps/content/README.md",
    "chars": 32,
    "preview": "# Content\n\nPosts, comments, etc\n"
  },
  {
    "path": "apps/content/app/assets/stylesheets/content/manifests/posts.css",
    "chars": 542,
    "preview": "/*\n * This is a manifest file that'll be compiled into application.css, which will include all the files\n * listed below"
  },
  {
    "path": "apps/content/app/assets/stylesheets/content/posts/index.css.sass",
    "chars": 100,
    "preview": ".posts-index-container\n  .post-list\n    padding-top: 20px\n\n  .post\n    .timestamp\n      float: right"
  },
  {
    "path": "apps/content/app/controllers/content/application_controller.rb",
    "chars": 278,
    "preview": "module Content\n  class ApplicationController < ActionController::Base\n    # Prevent CSRF attacks by raising an exception"
  },
  {
    "path": "apps/content/app/controllers/content/posts_controller.rb",
    "chars": 904,
    "preview": "module Content\n  class PostsController < ::Content::ApplicationController\n    helper Shared::Helper::Errors\n\n    manifes"
  },
  {
    "path": "apps/content/app/helpers/content/post_helper.rb",
    "chars": 525,
    "preview": "module Content\n  module PostHelper\n    def display_content(content)\n      markdown = Redcarpet::Markdown.new(Redcarpet::"
  },
  {
    "path": "apps/content/app/models/content/post.rb",
    "chars": 186,
    "preview": "module Content\n  class Post < ActiveRecord::Base\n    self.table_name = :posts\n\n    belongs_to :user, class_name: \"Conten"
  },
  {
    "path": "apps/content/app/models/content/user.rb",
    "chars": 150,
    "preview": "module Content\n  class User < ActiveRecord::Base\n    self.table_name = :users\n    \n    include Shared::Model::ReadOnly\n\n"
  },
  {
    "path": "apps/content/app/views/content/posts/index.haml",
    "chars": 353,
    "preview": ".posts-index-container\n  %h1 Posts\n\n  = form_for @post do |f|\n    .form-group\n      = f.text_area :content, class: 'form"
  },
  {
    "path": "apps/content/config/routes.rb",
    "chars": 79,
    "preview": "Content::Engine.routes.draw do\n  resources :posts, only: [:index, :create]\nend\n"
  },
  {
    "path": "apps/content/content.gemspec",
    "chars": 647,
    "preview": "$:.push File.expand_path(\"../lib\", __FILE__)\n\n# Describe your gem and declare its dependencies:\nGem::Specification.new d"
  },
  {
    "path": "apps/content/db/migrate/20140207011608_create_posts.rb",
    "chars": 175,
    "preview": "class CreatePosts < ActiveRecord::Migration\n  def change\n    create_table :posts do |t|\n      t.integer :user_id\n      t"
  },
  {
    "path": "apps/content/lib/content/engine.rb",
    "chars": 456,
    "preview": "module Content\n  class Engine < ::Rails::Engine\n    isolate_namespace Content\n    \n    initializer 'content.append_migra"
  },
  {
    "path": "apps/content/lib/content.rb",
    "chars": 113,
    "preview": "require \"content/engine\"\nrequire \"redcarpet\"\nrequire \"kaminari\"\nrequire \"kaminari-bootstrap\"\n\nmodule Content\nend\n"
  },
  {
    "path": "apps/marketing/README.md",
    "chars": 63,
    "preview": "# Marketing\n\nMarketing pages for introducing service and such.\n"
  },
  {
    "path": "apps/marketing/app/controllers/marketing/application_controller.rb",
    "chars": 286,
    "preview": "module Marketing\n  class ApplicationController < ActionController::Base\n    # Prevent CSRF attacks by raising an excepti"
  },
  {
    "path": "apps/marketing/app/controllers/marketing/home_controller.rb",
    "chars": 110,
    "preview": "module Marketing\n  class HomeController < ::Marketing::ApplicationController\n    def index\n\n    end\n  end\nend\n"
  },
  {
    "path": "apps/marketing/app/views/marketing/home/index.haml",
    "chars": 10,
    "preview": "%h1 Yeah!\n"
  },
  {
    "path": "apps/marketing/config/routes.rb",
    "chars": 61,
    "preview": "Marketing::Engine.routes.draw do\n  root to: 'home#index'\nend\n"
  },
  {
    "path": "apps/marketing/lib/marketing/engine.rb",
    "chars": 225,
    "preview": "module Marketing\n  class Engine < ::Rails::Engine\n    isolate_namespace Marketing\n\n    initializer 'marketing.asset_prec"
  },
  {
    "path": "apps/marketing/lib/marketing.rb",
    "chars": 49,
    "preview": "require \"marketing/engine\"\n\nmodule Marketing\nend\n"
  },
  {
    "path": "apps/marketing/marketing.gemspec",
    "chars": 553,
    "preview": "$:.push File.expand_path(\"../lib\", __FILE__)\n\n# Describe your gem and declare its dependencies:\nGem::Specification.new d"
  },
  {
    "path": "apps/shared/README.md",
    "chars": 45,
    "preview": "# Shared\n\n## Model\n\n## Controller\n\n## Layout\n"
  },
  {
    "path": "apps/shared/app/assets/javascripts/shared/manifests/application.js",
    "chars": 648,
    "preview": "// This is a manifest file that'll be compiled into application.js, which will include all the files\n// listed below.\n//"
  },
  {
    "path": "apps/shared/app/assets/stylesheets/shared/base.css.sass",
    "chars": 78,
    "preview": "body\n  padding-top: 70px\n  \nfooter\n  padding-left: 15px\n  padding-right: 15px\n"
  },
  {
    "path": "apps/shared/app/assets/stylesheets/shared/manifests/application.css",
    "chars": 560,
    "preview": "/*\n * This is a manifest file that'll be compiled into application.css, which will include all the files\n * listed below"
  },
  {
    "path": "apps/shared/app/controllers/shared/controller/authentication.rb",
    "chars": 706,
    "preview": "module Shared\n  module Controller\n    module Authentication\n      extend ::ActiveSupport::Concern\n\n      included do\n   "
  },
  {
    "path": "apps/shared/app/controllers/shared/controller/layout.rb",
    "chars": 278,
    "preview": "module Shared\n  module Controller\n    module Layout\n      extend ::ActiveSupport::Concern\n\n      included do\n        lay"
  },
  {
    "path": "apps/shared/app/controllers/shared/controller/manifests.rb",
    "chars": 1547,
    "preview": "module Shared\n  module Controller\n    module Manifests\n      extend ::ActiveSupport::Concern\n\n      included do\n        "
  },
  {
    "path": "apps/shared/app/helpers/shared/helper/errors.rb",
    "chars": 213,
    "preview": "module Shared\n  module Helper\n    module Errors\n      def show_object_errors(object)\n        return \"\" if object.errors."
  },
  {
    "path": "apps/shared/app/models/shared/model/read_only.rb",
    "chars": 174,
    "preview": "module Shared\n  module Model\n    module ReadOnly\n      \n      def readonly?\n        true\n      end\n\n      def delete\n   "
  },
  {
    "path": "apps/shared/app/models/shared/operation/base.rb",
    "chars": 6445,
    "preview": "# Operation\n#  the base implementation of a form object.\n#  the form has a set of defined fields which can be used in va"
  },
  {
    "path": "apps/shared/app/models/shared/user/display.rb",
    "chars": 764,
    "preview": "module Shared\n  module User\n    module Display\n\n      def display_name\n        return \"#{half_email}\" if first_name.blan"
  },
  {
    "path": "apps/shared/app/models/shared/user/stub.rb",
    "chars": 153,
    "preview": "module Shared\n  module User\n    class Stub < ActiveRecord::Base\n      self.table_name = :users\n\n      include Shared::Mo"
  },
  {
    "path": "apps/shared/app/views/shared/layouts/_errors.haml",
    "chars": 91,
    "preview": "%ul.error_explanation.bg-danger\n  - object.errors.full_messages.each do |msg|\n    %li= msg\n"
  },
  {
    "path": "apps/shared/app/views/shared/layouts/application.haml",
    "chars": 1420,
    "preview": "!!! 5\n%html\n  %head\n    %meta{charset: 'utf-8'}\n    %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge,chrome=1'"
  },
  {
    "path": "apps/shared/lib/shared/engine.rb",
    "chars": 218,
    "preview": "module Shared\n  class Engine < ::Rails::Engine\n    isolate_namespace Shared\n    \n    initializer 'shared.asset_precompil"
  },
  {
    "path": "apps/shared/lib/shared.rb",
    "chars": 102,
    "preview": "require \"shared/engine\"\nrequire 'haml'\nrequire 'jquery-rails'\nrequire 'sass-rails'\n\nmodule Shared\nend\n"
  },
  {
    "path": "apps/shared/shared.gemspec",
    "chars": 557,
    "preview": "$:.push File.expand_path(\"../lib\", __FILE__)\n\n# Describe your gem and declare its dependencies:\nGem::Specification.new d"
  },
  {
    "path": "bin/bundle",
    "chars": 129,
    "preview": "#!/usr/bin/env ruby\nENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)\nload Gem.bin_path('bundler', '"
  },
  {
    "path": "bin/rails",
    "chars": 146,
    "preview": "#!/usr/bin/env ruby\nAPP_PATH = File.expand_path('../../config/application',  __FILE__)\nrequire_relative '../config/boot'"
  },
  {
    "path": "bin/rake",
    "chars": 90,
    "preview": "#!/usr/bin/env ruby\nrequire_relative '../config/boot'\nrequire 'rake'\nRake.application.run\n"
  },
  {
    "path": "config/application.rb",
    "chars": 1159,
    "preview": "require File.expand_path('../boot', __FILE__)\n\nrequire 'rails/all'\n\n# Note: this doesn't require the gems\n# You should r"
  },
  {
    "path": "config/boot.rb",
    "chars": 171,
    "preview": "# Set up gems listed in the Gemfile.\nENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)\n\nrequire 'bun"
  },
  {
    "path": "config/database.yml",
    "chars": 576,
    "preview": "# SQLite version 3.x\n#   gem install sqlite3\n#\n#   Ensure the SQLite 3 gem is defined in your Gemfile\n#   gem 'sqlite3'\n"
  },
  {
    "path": "config/environment.rb",
    "chars": 165,
    "preview": "# Load the Rails application.\nrequire File.expand_path('../application', __FILE__)\n\n# Initialize the Rails application.\n"
  },
  {
    "path": "config/environments/development.rb",
    "chars": 1127,
    "preview": "RailsEnginesExample::Application.configure do\n  # Settings specified here will take precedence over those in config/appl"
  },
  {
    "path": "config/environments/production.rb",
    "chars": 3263,
    "preview": "RailsEnginesExample::Application.configure do\n  # Settings specified here will take precedence over those in config/appl"
  },
  {
    "path": "config/environments/test.rb",
    "chars": 1573,
    "preview": "RailsEnginesExample::Application.configure do\n  # Settings specified here will take precedence over those in config/appl"
  },
  {
    "path": "config/initializers/backtrace_silencers.rb",
    "chars": 404,
    "preview": "# Be sure to restart your server when you modify this file.\n\n# You can add backtrace silencers for libraries that you're"
  },
  {
    "path": "config/initializers/debugger.rb",
    "chars": 151,
    "preview": "begin\n  require 'byebug'\n\n  module Kernel\n    alias :debugger :byebug\n  end\n\nrescue LoadError => e\n  # byebug isn't inst"
  },
  {
    "path": "config/initializers/filter_parameter_logging.rb",
    "chars": 194,
    "preview": "# Be sure to restart your server when you modify this file.\n\n# Configure sensitive parameters which will be filtered fro"
  },
  {
    "path": "config/initializers/inflections.rb",
    "chars": 647,
    "preview": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format. Infl"
  },
  {
    "path": "config/initializers/mime_types.rb",
    "chars": 205,
    "preview": "# Be sure to restart your server when you modify this file.\n\n# Add new mime types for use in respond_to blocks:\n# Mime::"
  },
  {
    "path": "config/initializers/secret_token.rb",
    "chars": 673,
    "preview": "# Be sure to restart your server when you modify this file.\n\n# Your secret key is used for verifying the integrity of si"
  },
  {
    "path": "config/initializers/session_store.rb",
    "chars": 168,
    "preview": "# Be sure to restart your server when you modify this file.\n\nRailsEnginesExample::Application.config.session_store :cook"
  },
  {
    "path": "config/initializers/wrap_parameters.rb",
    "chars": 517,
    "preview": "# Be sure to restart your server when you modify this file.\n\n# This file contains settings for ActionController::ParamsW"
  },
  {
    "path": "config/locales/en.yml",
    "chars": 634,
    "preview": "# Files in the config/locales directory are used for internationalization\n# and are automatically loaded by Rails. If yo"
  },
  {
    "path": "config/routes.rb",
    "chars": 170,
    "preview": "require 'boot_inquirer'\n\nRailsEnginesExample::Application.routes.draw do\n\n  BootInquirer.each_active_app do |app|\n    mo"
  },
  {
    "path": "config.ru",
    "chars": 154,
    "preview": "# This file is used by Rack-based servers to start the application.\n\nrequire ::File.expand_path('../config/environment',"
  },
  {
    "path": "db/schema.rb",
    "chars": 1263,
    "preview": "# encoding: UTF-8\n# This file is auto-generated from the current state of the database. Instead\n# of editing this file, "
  },
  {
    "path": "db/seeds.rb",
    "chars": 343,
    "preview": "# This file should contain all the record creation needed to seed the database with its default values.\n# The data can t"
  },
  {
    "path": "lib/assets/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "lib/boot_inquirer.rb",
    "chars": 2128,
    "preview": "# This determines which engines to boot / mount within the operator app (v3).\n# The boot flag is a collection of charact"
  },
  {
    "path": "lib/tasks/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "log/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "public/404.html",
    "chars": 1351,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The page you were looking for doesn't exist (404)</title>\n  <style>\n  body {\n    "
  },
  {
    "path": "public/422.html",
    "chars": 1334,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The change you wanted was rejected (422)</title>\n  <style>\n  body {\n    backgroun"
  },
  {
    "path": "public/500.html",
    "chars": 1266,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>We're sorry, but something went wrong (500)</title>\n  <style>\n  body {\n    backgr"
  },
  {
    "path": "public/robots.txt",
    "chars": 204,
    "preview": "# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file\n#\n# To ban all spide"
  },
  {
    "path": "spec/account/factories/user_factories.rb",
    "chars": 287,
    "preview": "FactoryGirl.define do\n\n  factory :user, class: Account::User do\n    sequence(:email) { |n| \"user-#{n}-#{rand(9999)}@user"
  },
  {
    "path": "spec/account/models/user_spec.rb",
    "chars": 190,
    "preview": "require 'spec_helper'\n\ndescribe Account::User do\n  fixtures :users\n\n  let(:user) { users(:paul) }\n  it \"should authentic"
  },
  {
    "path": "spec/content/factories/post_factories.rb",
    "chars": 185,
    "preview": "FactoryGirl.define do\n\n  factory :post, class: Content::Post do\n    user { Content::User.find(FactoryGirl.create(:user)."
  },
  {
    "path": "spec/content/models/post_spec.rb",
    "chars": 393,
    "preview": "require 'spec_helper'\n\ndescribe Content::Post do\n  fixtures :users\n\n  it \"should validate content\" do\n    Content::Post."
  },
  {
    "path": "spec/fixtures/posts.yml",
    "chars": 147,
    "preview": "---\npost:\n  id: 1\n  user_id: 2\n  content: Fixtures are cool.\n  created_at: '2014-02-10 16:00:58.050118'\n  updated_at: '2"
  },
  {
    "path": "spec/fixtures/users.yml",
    "chars": 520,
    "preview": "---\nwilly:\n  id: 1\n  first_name: Willy\n  last_name: Watcher\n  email: willy@example.com\n  password_digest: $2a$04$nYODEwH"
  },
  {
    "path": "spec/shared/models/user_display_spec.rb",
    "chars": 545,
    "preview": "require 'spec_helper'\n\nmodule UserDisplayTest\n  class User < ActiveRecord::Base\n    self.table_name = :users\n    include"
  },
  {
    "path": "spec/spec_helper.rb",
    "chars": 1039,
    "preview": "ENV['RAILS_ENV'] = 'test'\n\nrequire_relative \"../config/environment.rb\"\n\nrequire 'rspec/rails'\n\nrequire 'rack/utils'\nrequ"
  },
  {
    "path": "spec/support/fixture_builder.rb",
    "chars": 1125,
    "preview": "require 'fixture_builder'\n\nFixtureBuilder.configure do |fbuilder|\n  fbuilder.files_to_check += Dir[\"spec/*/factories/**/"
  },
  {
    "path": "spec/support/fixture_class_name_helper.rb",
    "chars": 972,
    "preview": "# maps fixutres to their class names if different than defaults\n# gets included by spec helper in the config block\nmodul"
  },
  {
    "path": "spec/support/test_after_commit.rb",
    "chars": 519,
    "preview": "require 'active_record/connection_adapters/abstract/transaction'\n\nmodule ActiveRecord\n  module ConnectionAdapters\n    cl"
  }
]

About this extraction

This page contains the full source code of the taskrabbit/rails_engines_example GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 133 files (112.6 KB), approximately 33.1k tokens, and a symbol index with 220 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!