├── .gitignore ├── CHANGES.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib ├── trailblazer-loader.rb └── trailblazer │ ├── loader.rb │ └── loader │ ├── pipeline.rb │ └── version.rb ├── test ├── app │ └── concepts │ │ └── song │ │ ├── operation │ │ └── create.rb │ │ └── query │ │ └── index.rb ├── loader_test.rb └── test_helper.rb └── trailblazer-loader.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # This gem is deprecated. Don't use it for new projects. 2 | 3 | # 0.1.2 4 | 5 | * Introduce `Loader#default_circuit` to override specific positions in the loading process. This is a bit hacky and will be 6 | refined in v1.0. 7 | 8 | # 0.1.1 9 | 10 | * The loading order on OSX is now identical to Linux. Thanks to @pnikolov. 11 | 12 | # 0.1.0 13 | 14 | * New option `:root` to allow using loader for sources other than the current directory, e.g. for gems or Rails engines. 15 | * `:concept_root` is now prefixed with `:root`. 16 | * Use `File::SEPARATOR` for filename operations. 17 | * Don't use a regexp anymore to find concept files, but `Loader#concept_dirs` which includes plural names. 18 | * Allow irregular directory names such as `policies`. 19 | * Allow prepending to the pipeline using `:prepend`. Note that this is a temporary API and will be removed. 20 | 21 | # 0.0.9 22 | 23 | * Bug fix :cough:. 24 | 25 | # 0.0.8 26 | 27 | * Add `debug: true` option instead of relying on env vars. 28 | 29 | # 0.0.7 30 | 31 | * Fix a ordering bug. Thanks @Hermanverschooten. 32 | 33 | # 0.0.6 34 | 35 | * Allow injection of additional files in `Loader::ConceptFiles` from earlier functions. 36 | 37 | # 0.0.5 38 | 39 | * Remove `representable` dependency. This is a temporary fix until we have a pipelining gem. 40 | 41 | # 0.0.4 42 | 43 | * Fix loading for explicit layouts. 44 | 45 | # 0.0.3 46 | 47 | * Bump to Representable 2.4.0 as we use `Pipeline::Collect`. This doesn't mean this dependency will last forever. We might switch to `call_sheet` for pipelines. 48 | 49 | # 0.0.2 50 | 51 | * Internally, use `Representable::Pipeline` to model the loading steps. This allows gems like `trailblazer-rails` to plug in additional loading steps. 52 | 53 | # 0.0.1 54 | 55 | * Obviously, the first version ever. This implements the "lexical-width" strategy, only. 56 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in trailblazer-loader.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2022 Nick Sutterer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trailblazer::Loader 2 | # This gem is deprecated. Don't use it for new projects. 3 | 4 | _Generic loader for Trailblazer projects._ 5 | 6 | Reportedly works with Rails, Grape, Lotus, and Roda, and many more, for sure. 7 | 8 | **Rails users**: This gem is bundled with [trailblazer-rails](https://github.com/trailblazer/trailblazer-rails). 9 | 10 | ## Overview 11 | 12 | While Trailblazer enforces a new file structure where you organize by concept, and not by technology, the naming and the structuring within each concept allows different styles. 13 | 14 | Trailblazer-loader supports the following directory layouts concurrently. 15 | 16 | ## Compound-Singular 17 | 18 | Per concept, you have one file per abstraction layer (called a _compound_ file). All is singular and reflects the namespace (except for operations which sit in the concept's namespace). 19 | 20 | ``` 21 | app 22 | ├── concepts 23 | │   ├── comment 24 | │   │   ├── callback.rb 25 | │   │   ├── cell.rb 26 | │   │   ├── contract.rb 27 | │   │   ├── operation.rb 28 | │   │   ├── policy.rb 29 | │   │   └── views 30 | │   │   ├── grid.haml 31 | │   │   └── show.haml 32 | ``` 33 | 34 | You may nest concepts in concepts. 35 | 36 | ``` 37 | app 38 | ├── concepts 39 | │   ├── comment 40 | │   │   ├── contract.rb 41 | │   │   ├── operation.rb 42 | │   │   ├── admin 43 | │   │   ├── contract.rb 44 | │   │   └── operation.rb 45 | ``` 46 | 47 | Note: This is the structuring used in the [Trailblazer book](http://trailblazer.to/books/trailblazer.html). 48 | 49 | 50 | ## Explicit-Singular 51 | 52 | Per concept, you have one directory per abstraction layer and one file per class. All is singular and reflects the namespace (except for operations which sit in the concept's namespace). 53 | 54 | ``` 55 | app 56 | ├── concepts 57 | │   ├── comment 58 | │   │   ├── contract 59 | │   │   │ ├── create.rb 60 | │   │   │ └── update.rb 61 | │   │   ├── cell 62 | │   │   │ └── form.rb 63 | │   │   ├── operation 64 | │   │   │ ├── create.rb 65 | │   │   │ └── update.rb 66 | │   │   └── views 67 | │   │   ├── grid.haml 68 | │   │   └── show.haml 69 | ``` 70 | 71 | You may nest concepts in concepts. 72 | 73 | ``` 74 | app 75 | ├── concepts 76 | │   ├── comment 77 | │   │   ├── contract 78 | │   │   │ ├── create.rb 79 | │   │   │ └── update.rb 80 | │   │   ├── operation 81 | │   │   │ ├── create.rb 82 | │   │   │ └── update.rb 83 | │   │   ├── admin 84 | │   │   │   └── contract 85 | │   │   │   ├── create.rb 86 | │   │   │   └── update.rb 87 | ``` 88 | 89 | ## Explicit-Plural 90 | 91 | Per concept, you have one pluralized directory per abstraction layer and one file per class. 92 | 93 | ``` 94 | app 95 | ├── concepts 96 | │   ├── comment 97 | │   │   ├── contracts 98 | │   │   │ ├── create.rb 99 | │   │   │ └── update.rb 100 | │   │   ├── cells 101 | │   │   │ └── form.rb 102 | │   │   ├── operations 103 | │   │   │ ├── create.rb 104 | │   │   │ └── update.rb 105 | │   │   └── views 106 | │   │   ├── grid.haml 107 | │   │   └── show.haml 108 | ``` 109 | 110 | And, yes, you may nest concepts in concepts. 111 | 112 | ``` 113 | app 114 | ├── concepts 115 | │   ├── comment 116 | │   │   ├── contracts 117 | │   │   │ ├── create.rb 118 | │   │   │ └── update.rb 119 | │   │   ├── operations 120 | │   │   │ ├── create.rb 121 | │   │   │ └── update.rb 122 | │   │   ├── admin 123 | │   │   │   └── contracts 124 | │   │   │   ├── create.rb 125 | │   │   │   └── update.rb 126 | ``` 127 | 128 | 129 | ## Loading order 130 | 131 | The loading order is identical for all styles. 132 | 133 | 1. The loader finds all concept directories. 134 | 2. Concept directories are sorted by nesting level, deeper nestings are loaded later as they might reference concepts they're nested in. For example, `concepts/comment/admin` might reuse existing code from `concepts/comment`. 135 | 3. Per concept, files are lexically sorted, e.g. `create.rb` will be loaded *before* `update.rb` as we mostly do `Update < Create`. 136 | 4. Per concept, operation files will be loaded after all other layer files have been required. This is because abstraction files like representers or contracts should not reference their operation. The operation, however, as an orchestrating asset needs to refer to various abstraction objects. 137 | 138 | Here's a sample of a explicit-singular session. 139 | 140 | ```ruby 141 | [ 142 | "app/concepts/navigation/cell.rb", 143 | "app/concepts/session/impersonate.rb", 144 | "app/concepts/session/operation.rb", 145 | "app/concepts/user/operation.rb", 146 | "app/concepts/comment/cell/cell.rb", 147 | "app/concepts/comment/cell/grid.rb", 148 | "app/concepts/comment/operation/create.rb", 149 | "app/concepts/api/v1.rb", 150 | "app/concepts/thing/callback/default.rb", 151 | "app/concepts/thing/callback/upload.rb", 152 | "app/concepts/thing/cell.rb", 153 | "app/concepts/thing/cell/decorator.rb", 154 | "app/concepts/thing/cell/form.rb", 155 | "app/concepts/thing/cell/grid.rb", 156 | "app/concepts/thing/contract/create.rb", 157 | "app/concepts/thing/contract/update.rb", 158 | "app/concepts/thing/policy.rb", 159 | "app/concepts/thing/signed_in.rb", 160 | "app/concepts/thing/operation/create.rb", 161 | "app/concepts/thing/operation/delete.rb", 162 | "app/concepts/thing/operation/show.rb", 163 | "app/concepts/thing/operation/update.rb", 164 | "app/concepts/api/v1/comment/representer/show.rb", 165 | "app/concepts/api/v1/comment/operation/create.rb", 166 | "app/concepts/api/v1/comment/operation/show.rb", 167 | "app/concepts/api/v1/thing/representer/create.rb", 168 | "app/concepts/api/v1/thing/representer/index.rb", 169 | "app/concepts/api/v1/thing/representer/show.rb", 170 | "app/concepts/api/v1/thing/operation/create.rb", 171 | "app/concepts/api/v1/thing/operation/index.rb", 172 | "app/concepts/api/v1/thing/operation/update.rb" 173 | ] 174 | ``` 175 | 176 | ## Installation 177 | 178 | Add this line to your application's Gemfile: 179 | 180 | ```ruby 181 | gem 'trailblazer-loader' 182 | ``` 183 | 184 | You do not need this step should you use one of the following binding gems. 185 | 186 | * [trailblazer-rails](https://github.com/trailblazer/trailblazer-rails) 187 | * [trailblazer-grape](https://github.com/trailblazer/trailblazer-grape) 188 | * [trailblazer-roda](https://github.com/trailblazer/trailblazer-roda) 189 | * [trailblazer-lotus](https://github.com/trailblazer/trailblazer-lotus) 190 | 191 | ## API 192 | 193 | ```ruby 194 | Trailblazer::Loader.new.() { |file| require_dependency(File.join(Rails.application.root, file)) } 195 | ``` 196 | 197 | Per default, Trailblazer-loader will use `app/concepts` as the root path for the file search. Change that with the `:concepts_root` option. 198 | 199 | ```ruby 200 | Trailblazer::Loader.new.(concepts_root: "./concepts/") { |file| require_relative(file) } 201 | ``` 202 | 203 | Note that `:concepts_root` needs a trailing slash. 204 | 205 | ## Mixing 206 | 207 | Note that you're free to mix these styles the way it feels right for your project. 208 | 209 | For example, you can have compound files and explicit layout in one concept. 210 | 211 | ``` 212 | app 213 | ├── concepts 214 | │   ├── comment 215 | │   │   ├── contract.rb - compound vs. 216 | │   │   ├── operation - explicit directory 217 | │   │   │ ├── create.rb 218 | │   │   │ └── update.rb 219 | ``` 220 | 221 | ## Namespacing Operations 222 | 223 | Normally, operations in Trailblazer use the concept's namespace, e.g. `Comment::Create`, even though they can sit in an explicit file. 224 | 225 | ``` 226 | app 227 | ├── concepts 228 | │   ├── comment 229 | │   │   ├── operation - explicit directory 230 | │   │   │ ├── create.rb - contains Comment::Create 231 | │   │   │ └── update.rb 232 | ``` 233 | 234 | You are free to namespace your operations, if you like that better. 235 | 236 | ```ruby 237 | module Comment::Operation 238 | class Create < Trailblazer::Operation 239 | ``` 240 | 241 | ## Debugging 242 | 243 | Turn on debugging as follows. 244 | 245 | ```ruby 246 | Trailblazer::Loader.new.(debug: true) { |file| require_relative("../#{file}") } 247 | ``` 248 | 249 | This will print the file list before requiring them. 250 | 251 | TODO: document PrintFiles 252 | 253 | Booting your app fails because the loading order is incorrect? This happens, as we can't cover every possible combination. 254 | 255 | In any case, you can use `require` or `require_relative` and load files manually in the file depending on a specific class. 256 | 257 | For example, say you derive in another order and you're using the explicit layout. 258 | 259 | ```ruby 260 | require_relative "update.rb" 261 | class Comment::Create < Comment::Update 262 | ``` 263 | 264 | Instead of painfully reconfiguring, require explicitly and save yourself a lot of pain. BTW, that's how every other programming language does dependency management and even [Matz is not too happy about autoloading anymore](https://twitter.com/yukihiro_matz/status/676170870226706432). 265 | 266 | ## Customizing 267 | 268 | Trailblazer-loader allows you to inject your own sorting and filtering logic, should you refuse to go mainstream. 269 | 270 | ## License 271 | 272 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 273 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test" 6 | t.libs << "lib" 7 | t.test_files = FileList['test/**/*_test.rb'] 8 | end 9 | 10 | task :default => :test 11 | -------------------------------------------------------------------------------- /lib/trailblazer-loader.rb: -------------------------------------------------------------------------------- 1 | require "trailblazer/loader" -------------------------------------------------------------------------------- /lib/trailblazer/loader.rb: -------------------------------------------------------------------------------- 1 | require "trailblazer/loader/version" 2 | require "pp" 3 | 4 | module Trailblazer 5 | class Loader 6 | 7 | def concept_dirs 8 | %w{ callback cell contract operation policy representer view 9 | callbacks cells contracts operations policies representers views } 10 | end 11 | 12 | # Please note that this is subject to change - we're still finding out the best way 13 | # to explicitly load files. 14 | def call(options={}, &block) 15 | options[:root] ||= "." 16 | options[:concepts_root] ||= "#{options[:root]}/app/concepts/" 17 | options[:concept_dirs] ||= concept_dirs 18 | 19 | pipeline = options[:pipeline] || default_circuit 20 | 21 | if args = options[:insert] # FIXME: this only implements a sub-set. 22 | # pipeline = Representable::Pipeline::Insert.(pipeline, *args) # FIXME: implement :before in Pipeline. 23 | pipeline.last.insert(pipeline.last.index(args.last[:before]), args.first) 24 | end 25 | if args = options[:prepend] 26 | pipeline << args 27 | end 28 | 29 | files = pipeline.([], options).flatten 30 | 31 | debug(files, options) 32 | 33 | load_files(files, &block) 34 | end 35 | 36 | def default_circuit 37 | Pipeline[ 38 | FindDirectories, 39 | FindConcepts, 40 | SortByLevel, 41 | Pipeline::Collect[ConceptName, ConceptFiles, SortCreateFirst, SortOperationLast, AddConceptFiles] # per concept. 42 | ] 43 | end 44 | 45 | def debug(files, options) 46 | pp files if options[:debug] 47 | end 48 | 49 | FindDirectories = ->(input, options) { Dir.glob("#{options[:concepts_root]}**/") } 50 | # Filter out all directories containing /(callback|cell|contract|operation|policy|representer|view)/ 51 | FindConcepts = ->(input, options) { input.reject { |dir| (dir.split(File::SEPARATOR) & options[:concept_dirs]).any? } } 52 | PrintConcepts = ->(input, options) { puts " concepts: #{input.inspect}"; input } 53 | 54 | # lame heuristic, but works for me: sort by directory levels. 55 | # app/concepts/comment 56 | # app/concepts/api/v1/comment 57 | SortByLevel = ->(input, options) { input.sort { |a, b| a.split(File::SEPARATOR).size <=> b.split(File::SEPARATOR).size } } 58 | # Extract concept name from path, e.g. /api/v1/comment. 59 | ConceptName = ->(input, options) { options[:name] = input.sub(options[:concepts_root], "").chomp("/"); [] } 60 | # Find all .rb files in one particular concept directory, e.g. as in /concepts/comment/*.rb. 61 | ConceptFiles = ->(input, options) do 62 | input + # allow injecting other dependencies, e.g. app/models/comment.rb. 63 | 64 | Dir.glob("#{options[:concepts_root]}#{options[:name]}/*.rb") + # .rb files directly in this concept. 65 | Dir.glob("#{options[:concepts_root]}#{options[:name]}/*/*.rb"). # .rb in :concept/operation/*.rb 66 | find_all { |file| (file.split(File::SEPARATOR) & options[:concept_dirs]).any? } # but only those, no sub-concepts! 67 | end 68 | 69 | # operation files should be loaded after callbacks, policies, and the like: [callback.rb, contract.rb, policy.rb, operation.rb] 70 | SortOperationLast = ->(input, options) { input.sort { |a, b| a =~ /operation/ && b !~ /operation/ ? 1 : a !~ /operation/ && b =~ /operation/ ? -1 : a <=> b } } 71 | SortCreateFirst = ->(input, options) { input.sort } 72 | AddConceptFiles = ->(input, options) { input } 73 | 74 | 75 | # FindRbFiles = ->(input, options) { input + Dir.glob("#{options[:concepts_root]}#{options[:name]}/*.rb") } 76 | private 77 | 78 | def load_files(files) 79 | files.each { |file| yield file } 80 | end 81 | end 82 | end 83 | 84 | require "trailblazer/loader/pipeline" 85 | -------------------------------------------------------------------------------- /lib/trailblazer/loader/pipeline.rb: -------------------------------------------------------------------------------- 1 | module Trailblazer 2 | # WARNING: this will be removed soon with Uber::Pipeline or CallSheet. 3 | class Loader::Pipeline < Array 4 | Stop = Class.new 5 | 6 | # options is mutuable. 7 | def call(input, options) 8 | inject(input) do |memo, block| 9 | res = evaluate(block, memo, options) 10 | return(Stop)if Stop == res 11 | res 12 | end 13 | end 14 | 15 | private 16 | def evaluate(block, input, options) 17 | block.call(input, options) 18 | end 19 | 20 | 21 | module Macros # TODO: explicit test. 22 | # Macro to quickly modify an array of functions via Pipeline::Insert and return a 23 | # Pipeline instance. 24 | def insert(functions, new_function, options) 25 | Pipeline.new(Pipeline::Insert.(functions, new_function, options)) 26 | end 27 | end 28 | extend Macros 29 | 30 | # Collect applies a pipeline to each element of input. 31 | class Collect < self 32 | # when stop, the element is skipped. (should that be Skip then?) 33 | def call(input, options) 34 | arr = [] 35 | input.each_with_index do |item_fragment, i| 36 | result = super(item_fragment, options.merge(index: i)) # DISCUSS: NO :fragment set. 37 | Stop == result ? next : arr << result 38 | end 39 | arr 40 | end 41 | end # Collect 42 | 43 | module Function 44 | class Insert 45 | def call(arr, func, options) 46 | arr = arr.dup 47 | delete!(arr, func) if options[:delete] 48 | replace!(arr, options[:replace], func) if options[:replace] 49 | arr 50 | end 51 | 52 | private 53 | def replace!(arr, old_func, new_func) 54 | arr.each_with_index { |func, index| 55 | if func.is_a?(Collect) 56 | arr[index] = Collect[*Pipeline::Insert.(func, new_func, replace: old_func)] 57 | end 58 | 59 | arr[index] = new_func if func==old_func 60 | } 61 | end 62 | 63 | def delete!(arr, removed_func) 64 | arr.delete(removed_func) 65 | 66 | # TODO: make nice. 67 | arr.each_with_index { |func, index| 68 | if func.is_a?(Collect) 69 | arr[index] = Collect[*Pipeline::Insert.(func, removed_func, delete: true)] 70 | end 71 | } 72 | end 73 | end 74 | end 75 | 76 | Insert = Function::Insert.new 77 | end 78 | end -------------------------------------------------------------------------------- /lib/trailblazer/loader/version.rb: -------------------------------------------------------------------------------- 1 | module Trailblazer 2 | class Loader 3 | VERSION = "0.1.2" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/app/concepts/song/operation/create.rb: -------------------------------------------------------------------------------- 1 | module Song 2 | class Create 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /test/app/concepts/song/query/index.rb: -------------------------------------------------------------------------------- 1 | module Song 2 | module Query 3 | class Index 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/loader_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Trailblazer::LoaderTest < Minitest::Spec 4 | it "#call" do 5 | loaded = [] 6 | Trailblazer::Loader.new.(root: "#{Dir.pwd}/test", debug: true) { |f| loaded << f.split("test/").last } 7 | 8 | loaded.must_equal [ 9 | "app/concepts/song/operation/create.rb", 10 | "app/concepts/song/query/index.rb" 11 | ] 12 | end 13 | 14 | it do 15 | input = [ 16 | "app/concepts/order/cell/index.rb", 17 | "app/concepts/order/operation/update.rb", 18 | "app/concepts/order/cell/part.rb", 19 | "app/concepts/order/operation/index.rb", 20 | "app/concepts/order/cell/update.rb", 21 | "app/concepts/order/operation/search.rb", 22 | "app/concepts/order/cell/create.rb", 23 | "app/concepts/order/policy.rb", 24 | "app/concepts/order/cell.rb", 25 | "app/concepts/order/contract/update.rb", 26 | "app/concepts/order/contract/create.rb", 27 | "app/concepts/order/operation/from_traject.rb", 28 | "app/concepts/order/operation/create.rb", 29 | "app/concepts/contractor/operation.rb" 30 | ] 31 | 32 | expected = [ 33 | "app/concepts/order/cell.rb", 34 | "app/concepts/order/cell/create.rb", 35 | "app/concepts/order/cell/index.rb", 36 | "app/concepts/order/cell/part.rb", 37 | "app/concepts/order/cell/update.rb", 38 | "app/concepts/order/contract/create.rb", 39 | "app/concepts/order/contract/update.rb", 40 | "app/concepts/order/policy.rb", 41 | "app/concepts/contractor/operation.rb", 42 | "app/concepts/order/operation/create.rb", 43 | "app/concepts/order/operation/from_traject.rb", 44 | "app/concepts/order/operation/index.rb", 45 | "app/concepts/order/operation/search.rb", 46 | "app/concepts/order/operation/update.rb", 47 | ] 48 | 49 | input = ::Trailblazer::Loader::SortCreateFirst.(input, {}) 50 | result = ::Trailblazer::Loader::SortOperationLast.(input, {}) 51 | 52 | result.must_equal expected 53 | end 54 | 55 | def test_ordering_mac_osx 56 | input = [ 57 | "app/models/user.rb", 58 | "app/concepts/user/callback.rb", 59 | "app/concepts/user/policy.rb", 60 | "app/concepts/user/scope.rb", 61 | "app/concepts/user/cell/row.rb", 62 | "app/concepts/user/cell/table.rb", 63 | "app/concepts/user/contract/create.rb", 64 | "app/concepts/user/contract/update.rb", 65 | "app/concepts/user/operation/create.rb", 66 | "app/concepts/user/operation/index.rb", 67 | "app/concepts/user/operation/show.rb", 68 | "app/concepts/user/operation/update.rb" 69 | ] 70 | 71 | expected = [ 72 | "app/concepts/user/callback.rb", 73 | "app/concepts/user/cell/row.rb", 74 | "app/concepts/user/cell/table.rb", 75 | "app/concepts/user/contract/create.rb", 76 | "app/concepts/user/contract/update.rb", 77 | "app/concepts/user/policy.rb", 78 | "app/concepts/user/scope.rb", 79 | "app/models/user.rb", 80 | "app/concepts/user/operation/create.rb", 81 | "app/concepts/user/operation/index.rb", 82 | "app/concepts/user/operation/show.rb", 83 | "app/concepts/user/operation/update.rb" 84 | ] 85 | 86 | input = ::Trailblazer::Loader::SortCreateFirst.(input, {}) 87 | result = ::Trailblazer::Loader::SortOperationLast.(input, {}) 88 | 89 | assert_equal expected, result 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require 'trailblazer/loader' 3 | 4 | require 'minitest/autorun' 5 | -------------------------------------------------------------------------------- /trailblazer-loader.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'trailblazer/loader/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "trailblazer-loader" 8 | spec.version = Trailblazer::Loader::VERSION 9 | spec.authors = ["Nick Sutterer"] 10 | spec.email = ["apotonick@gmail.com"] 11 | spec.license = "MIT" 12 | 13 | spec.summary = %q{Loads all concepts files.} 14 | spec.description = %q{Loads all Trailblazer concepts files at startup.} 15 | spec.homepage = "http://trailblazer.to/gems/trailblazer/loader.html" 16 | 17 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 18 | spec.bindir = "exe" 19 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 20 | spec.require_paths = ["lib"] 21 | 22 | spec.add_development_dependency "bundler" 23 | spec.add_development_dependency "rake" 24 | spec.add_development_dependency "minitest" 25 | end 26 | --------------------------------------------------------------------------------