{"id":15792894,"url":"https://github.com/printercu/rails_stuff","last_synced_at":"2025-07-12T04:36:31.783Z","repository":{"id":56890747,"uuid":"41848253","full_name":"printercu/rails_stuff","owner":"printercu","description":"Collection of useful modules for Rails.","archived":false,"fork":false,"pushed_at":"2018-06-01T17:20:30.000Z","size":173,"stargazers_count":109,"open_issues_count":0,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-15T01:46:54.540Z","etag":null,"topics":["helpers","rails","ruby","utils"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/printercu.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-09-03T07:56:14.000Z","updated_at":"2024-12-06T21:06:26.000Z","dependencies_parsed_at":"2022-08-20T16:00:50.812Z","dependency_job_id":null,"html_url":"https://github.com/printercu/rails_stuff","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/printercu%2Frails_stuff","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/printercu%2Frails_stuff/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/printercu%2Frails_stuff/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/printercu%2Frails_stuff/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/printercu","download_url":"https://codeload.github.com/printercu/rails_stuff/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253150083,"owners_count":21861831,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["helpers","rails","ruby","utils"],"created_at":"2024-10-04T23:06:39.570Z","updated_at":"2025-05-08T21:21:36.393Z","avatar_url":"https://github.com/printercu.png","language":"Ruby","readme":"# RailsStuff\n[![Gem Version](https://badge.fury.io/rb/rails_stuff.svg)](http://badge.fury.io/rb/rails_stuff)\n[![Code Climate](https://codeclimate.com/github/printercu/rails_stuff/badges/gpa.svg)](https://codeclimate.com/github/printercu/rails_stuff)\n[![Build Status](https://travis-ci.org/printercu/rails_stuff.svg)](https://travis-ci.org/printercu/rails_stuff)\n\nCollection of useful modules for ruby projects to provide great DRY, TDD experience.\nWhile some of them are Rails-specific, most will work in any environment\ndespite gem's name.\n\nAll modules are lazy loaded, so it's ok to require whole gem at once.\nSome of them are activated by default for best experience,\nbut this can be configured or turned off (see [usage](#usage)).\n\nWorks with ruby 2.0+, Rails 4.2+, 5+.\n\n#### Controllers:\n\n- __[ResourcesController](#resourcescontroller)__\n  DRY! Keep your controllers clean.\n- __[SortScope](#sortscope)__\n  Helper for `has_scope` to sort collections safely.\n\n#### Models:\n\n- __[NullifyBlankAttrs](#nullifyblankattrs)__\n  Proxies writers to replace empty values with `nil`.\n- __[AssociationWriter](#associationwriter)__\n  Override both writers with single instruction.\n- __[RandomUniqAttr](#randomuniqattr)__\n  You generate random values for attributes, it'll ensure they are uniq.\n- __[Statusable](#statusable)__\n  `ActiveRecord::Enum` with more features.\n- __[TypesTracker](#typestracker)__\n  Advanced descendants tracker.\n\n#### Misc:\n\n- __[ParamsParser](#paramsparser)__\n  Type-cast params outside of `ActiveRecord`.\n- __[RedisStorage](#redisstorage)__\n  Simple way to store collections in key-value storage. With scoping and\n  key generation.\n- __[StrongParameters](#strongparameters)__\n  `require_permitted` helper.\n- __UrlFor__\n  `#url_for_keeping_params` merges passed options with request's query params.\n- __[RequireNested](#requirenested)__\n  helper to load files in subdirectory.\n- `rails g concern %parent%/%module%` generator for concerns.\n\n#### Helpers:\n\n- __TranslationHelper__\n  `translate_action`, `translate_confirmation` helpers to translate\n  action names and confirmations in the same way all over you app.\n- __LinksHelper__\n  Keep your links for basic actions consistent.\n- __Bootstrap__\n  For bootstrap-formatted flash messages.\n- __Forms__\n  `hidden_params_fields` to bypass query params in GET-forms.\n\n__[Helpers usage](#helpers-usage)__\n\n#### Test helpers:\n\n- __Response__\n  `#json_body` to test json responses.\n- Useful RSpec configurations, helpers and matchers for better experience.\n- __Concurrency__\n  Helpers for testing with concurrent requests.\n\n__[Test helpers usage](#test-helpers-usage)__\n\n#### Assets:\n\n- __MediaQueries__\n  `@media #{$sm-up} and #{$portrait} { ... }` queries for SASS.\n- __[PluginManager](#pluginmanager)__\n  Simple way to create jQuery plugins using classes.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'rails_stuff'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install rails_stuff\n\n## Usage\n\nThere is railtie which will include some of modules into `ActiveRecord::Base`\nand `ActionController::Base` by default. You can disable this behavior in\ninitializer:\n\n```ruby\n# Disable auto-setup:\nRailsStuff.load_modules = []\n\n# Enable particular modules:\nRailsStuff.load_modules = %i(sort_scope statusable)\n```\n\nYou can override base classes for controller/model with `.base_controller=`,\n`.base_model=`.\n\nThere can be lack of documentation in README. Please navigate to module and\ncheck docs \u0026 code (press `t` on github) if you miss something.\n\n### ResourcesController\n\nSimilar to [InheritedResource](https://github.com/josevalim/inherited_resources)\nbut much simpler. It adds implementations for basic actions and\naccessors for collection and resource. There is no options for almost everything,\nbut it's easy to extend.\n\nIt's main purpose is to ged rid of `@user ||= User.find params[:id]`, and keep\ncontrollers clean:\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  extend RailsStuff::ResourcesController # when using without railtie\nend\n\nclass UsersController \u003c ApplicationController\n  resources_controller\n  permit_attrs :name, :email\nend\n\nclass ProjectsController \u003c ApplicationController\n  resources_controller sti: true,\n    after_save_action: :index,\n    source_relation: -\u003e { user.projects }\n  resource_helper :user\n\n  # or just\n  resources_controller sti: true,\n    after_save_action: :index,\n    belongs_to: :user\n    # `belongs_to: [:user, optional: true]` for shallow routes\n\n  permit_attrs :name\n  permit_attrs_for Project::External, :company\n  permit_attrs_for Project::Internal, :department\nend\n```\n\nThere is built-in support for `has_scope` gem and pagination with Kaminari.\n\nCurrently depends on `gem 'responders', '\u003e 2.0'`.\n\n### SortScope\n\n```ruby\n# in controller\nextend RailsStuff::SortScope # when using without railtie\n\nhas_sort_scope by: [:name, :created_at, :balance], default: [:name]\n\n# this scope will accept\n#   - `sort=name`\n#   - `sort=name\u0026sort_desc=true`\n#   - `sort[name]\u0026sort[created_at]`\n#   - `sort[name]\u0026sort[created_at]=desc\n\n# access current sort scope hash with\ncurrent_sort_scope # =\u003e {'name' =\u003e :desc} or {'name' =\u003e :asc, 'id' =\u003e :desc}\n```\n\nRequires `gem 'has_scope'`.\n\n### NullifyBlankAttrs\n\nDefines proxies for writers to replace empty values with `nil`.\n\n```ruby\n# in model\nextend RailsStuff::NullifyBlankAttrs # when using without railtie\n\nnullify_blank_attrs :email, :title\n```\n\n### AssociationWriter\n\nActiveRecord's association can be updated with object and by object id.\nOwerwrite this both writers with single instruction:\n\n```ruby\nassociation_writer :product do |val|\n  super(val).tap { update_price if product }\nend\n```\n\n### RandomUniqAttr\n\nUses database's UNIQUE constraints and transactions to generate uniq random values.\nYou need to make field nullable and add unique index on it.\nThe way it works:\n\n- Instance is saved as usual\n- If random fields are not empty, it does nothing\n- Generates random value and tries to update instance\n- If `RecordNotUnique` is occurred, it keeps trying to generate new values.\n\n```ruby\n# in model\nextend RailsStuff::RandomUniqAttr # when using without railtie\n\n# Uses DEFAULT_GENERATOR which is SecureRandom(32)\nrandom_uniq_attr :token\n\n# Uses custom generator, which takes template from settings\nrandom_uniq_attr(:code) do |instance|\n  MyGenerator.generate(instance.parent.code_template)\nend\n```\n\n### Statusable\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  extend RailsStuff::Statusable # when using without railtie\n\n  # Setup with array and it'll store status values as strings.\n  STATUSES = %i(confirmed banned)\n  has_status_field # uses #status field and STATUSES as values\n\n  # Or pass everything explicitly\n  has_status_field :subscription_status, %i(expired active), prefix: :subs_\n  # :prefix is used for methods that are build\nend\n\nclass Order \u003c ActiveRecord::Base\n  # Provide hash, and it'll store mapped values in database.\n  STATUSES_MAPPING = {submitted: 1, confirmed: 2, delivered: 3}\n  has_status_field\nend\n\nuser = User.first\n\n# And you get:\n# Scopes\nUser.confirmed.subs_active\nUser.not_banned.not_subs_expired\n# Useful with has_scope\nUser.with_status(param[:status]).with_subscription_status(params[:subs_status])\n# When using mapped values, scopes will accept status names:\nOrder.with_status(:confirmed)\n\n# Translation \u0026 select helpers (requires activemodel_translation gem)\nUser.statuses.translate(:active)\nuser.subscription_status_name # translates current status\nUser.statuses.select_options\nUser.subscription_statuses.select_options except: [:expired]\nOrder.statuses.map([:submitted, :delivered]) # =\u003e [1, 3]\nOrder.statuses.unmap([1, 3]) # =\u003e [:submitted, :delivered]\n\n# Accessors\nuser.status = 'confirmed' or user.confirmed!\nuser.status_sym # :confirmed\nuser.subscription_status = :active or user.subs_active!\nuser.subscription_status # 'active'\nuser.banned? or user.subs_expired?\n\n# ... and inclusion validator\n```\n\n### TypesTracker\n\n```ruby\nclass Project\n  extend RailsStuff::TypesTracker\n  # you can also override default list class (Array) with:\n  self.types_list_class = FilterableArray\n  # smth ...\n\n  # If you want to show all available descendants in development\n  # (ex. in dropdown/select), you definitely want this:\n  require_nested # will load all .rb files in app/models/project\nend\n\nclass Project::Big \u003c Project\n  unregister_type # remove this class from types_list\n\n  # Or add options for custom list.\n  # Following will call types_list.add Project::Big, :arg, option: :example\n  register_type :arg, option: :example\nend\n\nclass Project::Internal \u003c Project::Big; end\nclass Project::External \u003c Project::Big; end\nclass Project::Small \u003c Project; end\n\nProject.types_list # [Internal, External, Small]\n\n# Scopes for each type:\nProject.internal or Project.big\n```\n\n### ParamsParser\n\nHave you missed type-casting outside of `ActiveRecord::Base`? Here is it:\n\n```ruby\nParamsParser.parse_int(params[:field]) # _float, _decimal, _string, _boolean, _datetime\nParamsParser.parse_int_array(params[:field_with_array])\nParamsParser.parse_json(json_string)\n\n# There is basic .parse method. It runs block only if input is not nil\n# and reraises all errors with ParamsParser::Error\nParamsParser.parse(input) { |x| this_can_raise_exception(x) }\n\n# So you can handle all errors in controller with\nrescue_from ParamsParser::Error, with: -\u003e { head :bad_request }\n```\n\n### RedisStorage\n\nSimple module to organize data in key-value store. Uses `ConnectionPool`\nand works good in multi-threaded environments.\nBest used with [PooledRedis](https://github.com/printercu/pooled_redis).\n\n```ruby\nclass Model\n  extend RailsStuff::SedisStorage\n\n  self.redis_prefix = :other_prefix # default to underscored model name\n\n  # override .dump, .load for custom serialization. Default to Marshal\n\n  # It uses Rails.redis_pool by default. Override it with\n  self.redis_pool = ConnectionPool.new { Redis.new(my_options) }\nend\n\nModel.get('key') # GET other_prefix:key\nModel.get(['composite', 'key']) # GET other_prefix:composite:key\n# .delete works the same way\n\nModel.set('key', data) or Model.set(['composite', 'key'], data)\nnext_id = Model.set(nil, data) # auto-incremented per-model id\nnext_id = Model.set(['composite', nil], data) # auto-incremented per-scope id\nModel.set(id, data, ex: 10) # pass options for redis\n# Or set per-model options for all .set requests:\nModel.redis_set_options = {ex: 10}\n\n# generate ids:\nModel.next_id or Model.next_id(['composite', 'scope'])\nModel.reset_id_seq or Model.reset_id_seq(['composite', 'scope'])\n```\n\n### StrongParameters\n\n`#require_permitted` ensures that required values are scalar:\n\n```ruby\nparams.require_permitted(:access_token, :refresh_token)\n# instead of\nparams.permit(:access_token, :refresh_token).require(:access_token, :refresh_token)\n```\n\n### RequireNested\n\n```ruby\nclass Project \u003c ApplicationRecord\n  # To load all files in app/models/project with require_dependency:\n  require_nested\n  # or pass folder explicitly:\n  require_nested('lib/path/to/projects')\n\n  # For non-rails apps use\n  RailsStuff::RequireNested.require_nested\n  # or call RailsStuff::RequireNested.setup to add #require_nested to Module\nend\n```\n\n### Helpers usage\n\nInclude helper module into `ApplicationHelper`.\nOr use `RailsStuff::Helpers::All` to include all helpers together.\n\nAdd this sections to your translations ymls:\n\n```yml\nhelpers:\n  actions:\n    edit: Change\n    delete: Forget about it\n  confirm: Really?\n  confirmations:\n    delete: Will you miss it?\n```\n\nAnd use helpers:\n\n```ruby\ntranslate_action(:edit) or translate_action(:delete)\nlink_to 'x', url_for(resource),\n  method: :delete, data: {confirm:  translate_confirmation(:delete)}\ntranslate_confirmation(:purge_all) # Fallback to default: 'Really?'\n\n# There are helpers for basic links, take a look on helpers/links.rb to know\n# how to tune it:\nlink_to_edit or link_to_edit('url') or link_to_edit([:scope, resource])\nlink_to_destroy or link_to_destroy('url') or link_to_destroy([:scope, resource])\n```\n\nTranslation helpers are cached, so there is no need to cache it by yourself in\ntemplate if you want to decrease computations. And be aware of it if you\nswitch locales while rendering single view.\n\n### Test helpers usage\n\nAdd `RailsStuff::RSpec.setup` to `rails_helper.rb` to load all the stuff or\npick from `rails_stuff/test_helpers/` and `rails_stuff/rspec`.\n\nPlease check the source for complete list of helpers, while this section is not\nwell-documented.\n\n#### `json_body`\n```ruby\nassert_equal({'id' =\u003e 1, 'name' =\u003e 'John'}, response.json_body)\nassert_equal('John', response.json_body['name'])\nassert_equal('John', response.json_body.name)\n\n# with rspec-its\nsubject { get :show, id: resource.id }\nits(:json_body) { should include 'id' =\u003e 1 }\nits('json_body.name') { should eq 'John' }\n```\n\n`.json_body` helper requires `gem 'hashie'`.\n\nNote that `hashie` conflicts with `Hash` methods, so `.json_body.key` or\n`.json_body.hash` will not work as expected (or at all).\nUse `json_body['key']` instead.\n\nThere is `Configurator` with useful RSpec configs:\n\n#### RSpec configurations\n\n```ruby\n# Setup DatabaseCleaner with basic settings:\nRailsStuff::RSpec.database_cleaner\n\n# Flush redis after suite and exampes with `flush_redis: true`:\nRailsStuff::RSpec.redis\n\n# Run debugger after failed tests:\nRailsStuff::RSpec.debug\n\n# Clear log/test.log after suite:\nRailsStuff::RSpec.clear_logs\n\n# Raise errors from threads:\nRailsStuff::RSpec.thread\n\n# Raise errors for all missing I18n translation:\nRailsStuff::RSpec.i18n\n\n# Freeze time with for group/example with `:frozen_time` metadata (requires timecop gem):\nRailsStuff::RSpec.frozen_time\n```\n\n### PluginManager\n\nProvides simple way to create jQuery plugins. Create class and PluginManager\nwill create jQuery function for it. It'll create instance of class for each\njQuery element and prevent calling constructor twice.\n\n```coffee\nPluginManager.add 'myPlugin', class\n  constructor: (@$element, @options) -\u003e\n     # ...\n\n  customAction: (options)-\u003e\n     # ...\n\n  # Add initializers\n  $ -\u003e $('[data-my-plugin]').myPlugin()\n  # or\n  $(document).on 'click', '[data-my-plugin]', (e) -\u003e\n    $(@).myPlugin('customAction', event: e)\n\n# Or use it manually\n$('.selector').myPlugin().myPlugin('customAction')\n```\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies.\nThen, run `bin/console` for an interactive prompt that will allow you to experiment.\n\nUse [appraisal](https://github.com/thoughtbot/appraisal) to run specs:\n`appraisal rspec`.\n\nTo install this gem onto your local machine, run `bundle exec rake install`.\nTo release a new version, update the version number in `version.rb`,\nand then run `bundle exec rake release` to create a git tag for the version,\npush git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\n1. Fork it ( https://github.com/printercu/rails_stuff/fork )\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Implement your feature:\n  - Write failing spec for your feature\n  - Write code\n  - Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprintercu%2Frails_stuff","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprintercu%2Frails_stuff","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprintercu%2Frails_stuff/lists"}