### Install Superfeature Gem Source: https://github.com/rubymonolith/superfeature/blob/main/README.md Instructions for installing the Superfeature gem using Bundler and generating its initial configuration files. ```bash bundle add superfeature rails generate superfeature:install ``` -------------------------------- ### Price Comparisons in Ruby Source: https://github.com/rubymonolith/superfeature/blob/main/README.md This Ruby code illustrates how to compare `Price` objects with each other and with numerical values. It shows examples of greater than, equal to, and less than comparisons, demonstrating that `Price` objects can be compared directly with integers and other `Price` objects. ```ruby Price(100) > Price(50) # => true Price(100) == 100 # => true Price(100) < 200 # => true ``` -------------------------------- ### Building a Complete Pricing Table Source: https://context7.com/rubymonolith/superfeature/llms.txt Illustrates how to integrate various Superfeature components, including plans, features, pricing, and discounts, to construct a comprehensive pricing page. It shows a controller example that sets up base plans, a collection of plans, and applies promotional discounts. ```ruby # Controller class PricingController < ApplicationController def index base_plan = Plans::Free.new(User.new) @plans = Superfeature::Plan::Collection.new(base_plan).to_a @promo = Promotion.new(name: "Launch Special", percent_off: 20) if params[:promo] end end ``` -------------------------------- ### Custom Discount Sources with to_discount Source: https://context7.com/rubymonolith/superfeature/llms.txt Explains how to create custom discount objects by implementing the `to_discount` method. This method should return a `Discount` object, allowing any object (like database records or custom classes) to be used as a discount source. Examples include using a `Coupon` model and a `Promotion` class. ```ruby class Coupon < ApplicationRecord # columns: code, percent_off, fixed_off def to_discount if percent_off.present? Superfeature::Discount::Percent.new(percent_off) else Superfeature::Discount::Fixed.new(fixed_off) end end end class Promotion attr_reader :name, :percent_off def initialize(name:, percent_off:) @name = name @percent_off = percent_off end def to_discount Superfeature::Discount::Percent.new(@percent_off) end end # Usage coupon = Coupon.find_by(code: "SAVE20") # percent_off: 20 price = Price(100).apply_discount(coupon) price.amount # => 80.0 promo = Promotion.new(name: "Launch Special", percent_off: 15) price = Price(100).apply_discount(promo) price.amount # => 85.0 ``` -------------------------------- ### Defining a New Plan in Ruby Source: https://github.com/rubymonolith/superfeature/blob/main/README.md This Ruby code defines a new `Enterprise` plan. It sets the plan's name, price, and description, and shows how to override or conditionally enable/disable features inherited from a base class. It also includes commented-out examples for linking to adjacent plans. ```ruby module Plans class Enterprise < Base def name = "Enterprise" def price = 0 def description = "Description for Enterprise plan" # Override features from Base to enable them # def priority_support = super.enable # # Conditionally enable/disable based on a boolean: # def dark_mode = super.enable(user.premium?) # def legacy_feature = super.disable(user.migrated?) # Link to adjacent plans for navigation # def next = plan NextPlan # def previous = plan PreviousPlan end end ``` -------------------------------- ### Install Superfeature Generator (Bash) Source: https://context7.com/rubymonolith/superfeature/llms.txt This command installs the Superfeature gem and sets up the initial plan structure within a Rails application. It creates essential files for defining base plans, features, and configuration. ```bash # Install Superfeature and create initial plan structure rails generate superfeature:install # Creates: # - app/plans/base.rb # - app/plans/features/base.rb # - app/plans/free.rb # - app/plans/paid.rb # - config/initializers/plans.rb ``` -------------------------------- ### Working with Plan Collections in Ruby Source: https://github.com/rubymonolith/superfeature/blob/main/README.md The `Superfeature::Plan::Collection` class in Ruby allows for navigation and enumeration of plans. It supports creating a collection from a starting plan, finding specific plans by symbol or class, slicing multiple plans, and iterating through all plans using `Enumerable`. ```ruby # Create a collection starting from any plan collection = Superfeature::Plan::Collection.new(Plans::Free.new(current_user)) # Find a specific plan by symbol key collection.find(:paid) # => Paid plan instance # Find a specific plan by class collection.find(Plans::Paid) # => Paid plan instance # Get multiple plans with slice collection.slice(:free, :paid) # => Array of matching plans collection.slice(Plans::Free, Plans::Paid) # => Also works with classes # Iterate through all plans (includes Enumerable) collection.each do |plan| puts "#{plan.name}: $#{plan.price}" end collection.to_a # All plans as an array ``` -------------------------------- ### Preventing Inheritance with `exclusively` in Ruby Source: https://github.com/rubymonolith/superfeature/blob/main/README.md The `exclusively` keyword in Ruby is used to ensure a method applies only to the exact class it's defined in, not to subclasses. This example shows how `exclusively def badge` in `Plans::Pro` is not inherited by `Plans::Enterprise`. ```ruby module Plans class Pro < Basic # Only Pro gets this badge, not Enterprise which inherits from Pro exclusively def badge = "Most Popular" end end module Plans class Enterprise < Pro # badge returns nil here, not "Most Popular" end end ``` -------------------------------- ### Apply Discounts Using Strings (Ruby) Source: https://github.com/rubymonolith/superfeature/blob/main/README.md Shows how to apply discounts to prices by parsing natural language discount strings, such as percentages or fixed dollar amounts. ```ruby price = Price(100) price.apply_discount("20%").amount # => 80.0 price.apply_discount("$15").amount # => 85.0 price.apply_discount(10).amount # => 90.0 (numeric = dollars off) ``` -------------------------------- ### Controller Action to Prepare Pricing Data (Ruby) Source: https://github.com/rubymonolith/superfeature/blob/main/README.md A controller action can be used to set up the necessary data for a pricing page. This involves creating a collection of available plans and potentially a promotion object. The plans are instantiated with a user object, and the promotion is created with its specific details. ```ruby class PricingController < ApplicationController def index @plans = Superfeature::Plan::Collection.new(Plans::Free.new(User.new)).to_a @promo = Promotion.new(name: "Launch Special", percent_off: 20) end end ``` -------------------------------- ### Apply Discounts to Prices in Ruby Source: https://github.com/rubymonolith/superfeature/blob/main/README.md Demonstrates various ways to apply discounts to a `Price` object, including fixed amounts, percentages, and setting a target price. ```ruby price = Price(100) # Fixed dollar amount off price.discount_fixed(20).amount # => 80.0 # Percentage off (25 = 25%) price.discount_percent(25).amount # => 75.0 # Set a target price directly price.discount_to(79).amount # => 79.0 ``` -------------------------------- ### Define a Basic Plan in Ruby Source: https://github.com/rubymonolith/superfeature/blob/main/README.md Demonstrates how to create a simple plan by inheriting from `Superfeature::Plan` and defining its name and description. ```ruby module Plans class Free < Superfeature::Plan def name = "Free" def description = "Get started for free" end end ``` -------------------------------- ### Create and Format Prices in Ruby Source: https://github.com/rubymonolith/superfeature/blob/main/README.md Shows how to create `Price` objects using `BigDecimal` internally and how to format them to different string representations. ```ruby price = Superfeature::Price.new(49.99) price.amount # => BigDecimal("49.99") price.to_f # => 49.99 price.to_i # => 49 price = Price(29) price.to_formatted_s # => "29.00" price.to_formatted_s(decimals: 0) # => "29" ``` -------------------------------- ### Linking Plans with Navigation (Ruby) Source: https://github.com/rubymonolith/superfeature/blob/main/README.md Demonstrates how to define navigation links between plans using `next` and `previous` methods to create a plan hierarchy. ```ruby module Plans class Free < Superfeature::Plan def next = plan Pro end class Pro < Free def previous = plan Free def next = plan Enterprise end class Enterprise < Pro def previous = plan Pro end end ``` -------------------------------- ### Price Queries in Ruby Source: https://github.com/rubymonolith/superfeature/blob/main/README.md This Ruby code demonstrates query methods available for `Price` objects. It shows how to check if a price is zero (`zero?`), free (`free?`), positive (`positive?`), or paid (`paid?`), providing convenient ways to evaluate price conditions. ```ruby Price(0).zero? # => true Price(0).free? # => true (alias) Price(100).positive? # => true Price(100).paid? # => true (alias) ``` -------------------------------- ### Creating a Promotion Object for Discounts (Ruby) Source: https://github.com/rubymonolith/superfeature/blob/main/README.md A `Promotion` class can be created to represent special offers. This class can hold details like the promotion name and percentage off. By implementing the `to_discount` method, a `Promotion` object can be used seamlessly with the `apply_discount` method, returning a `Superfeature::Discount::Percent` object. ```ruby class Promotion attr_reader :name, :percent_off def initialize(name:, percent_off:) @name = name @percent_off = percent_off end def to_discount Superfeature::Discount::Percent.new(@percent_off) end end ``` -------------------------------- ### Wiring Plan Chain in Ruby Source: https://github.com/rubymonolith/superfeature/blob/main/README.md This Ruby code demonstrates how to link plans sequentially by updating the `next` and `previous` methods. It shows the necessary modifications in `paid.rb` to point to `Enterprise` as the next plan, and in `enterprise.rb` to point back to `Paid` as the previous plan. ```ruby # In paid.rb def next = plan Enterprise # In enterprise.rb def previous = plan Paid ``` -------------------------------- ### Creating Prices in Ruby Source: https://github.com/rubymonolith/superfeature/blob/main/README.md This Ruby code illustrates various ways to create `Price` objects. It shows the use of a convenience method `Price(49.99)`, the standard constructor `Price.new(49.99)`, and Rails core extensions like `discounted_by`, `to_price`, and `"$49.99".to_price`. It also mentions opting in for core extensions outside of Rails. ```ruby price = Price(49.99) # convenience method price = Price.new(49.99) # standard constructor # In Rails, you can also use core extensions: 100.discounted_by(20.percent_off) # => Price(80) 100.discounted_by(20) # => Price(80) 100.to_price # => Price(100) "$49.99".to_price # => Price(49.99) # Outside of Rails, opt-in with `require "superfeature/core_ext"`. ``` -------------------------------- ### Assign Prices to Plans (Ruby) Source: https://github.com/rubymonolith/superfeature/blob/main/README.md Illustrates how to assign `Price` objects to different plans, such as Free, Pro, and Enterprise. ```ruby module Plans class Free < Superfeature::Plan def price = Price(0) end class Pro < Free def price = Price(29) end class Enterprise < Pro def price = Price(99) end end ``` -------------------------------- ### Implementing Custom Discount Sources via `to_discount` (Ruby) Source: https://github.com/rubymonolith/superfeature/blob/main/README.md Any object can act as a discount source if it implements the `to_discount` method. This method should return a `Superfeature::Discount` object. This allows for integration with custom discount logic, such as database records representing coupons. ```ruby class Coupon < ApplicationRecord def to_discount Superfeature::Discount::Percent.new(percent_off) end end coupon = Coupon.find_by(code: "SAVE20") price = Price(100).apply_discount(coupon) price.amount # => 80.0 ``` -------------------------------- ### Check Feature Status in Ruby Source: https://github.com/rubymonolith/superfeature/blob/main/README.md Illustrates how to instantiate a plan and check the enabled/disabled status of its features using the `.enabled?` method. ```ruby plan = Plans::Free.new(current_user) plan.priority_support.enabled? # => false plan.api_access.enabled? # => true ``` -------------------------------- ### Plan Navigation with Plan::Collection (Ruby) Source: https://context7.com/rubymonolith/superfeature/llms.txt Explains how to use `Superfeature::Plan::Collection` in Ruby for navigating and discovering plans. It covers finding specific plans by key or class, retrieving multiple plans, and iterating through all available plans to build pricing tables or understand upgrade paths. ```ruby user = current_user collection = Superfeature::Plan::Collection.new(Plans::Free.new(user)) # Find a specific plan by symbol key pro_plan = collection.find(:pro) pro_plan.name # => "Pro" # Find by class enterprise = collection.find(Plans::Enterprise) # Get multiple plans plans = collection.slice(:free, :pro, :enterprise) # Iterate through all plans (via Enumerable) collection.each do |plan| puts "#{plan.name}: $#{plan.price.to_i}/mo" end # Output: # Free: $0/mo # Pro: $29/mo # Enterprise: $99/mo ``` -------------------------------- ### Accessing Discount Information After Application (Ruby) Source: https://github.com/rubymonolith/superfeature/blob/main/README.md After applying a discount to a price, you can access various details about the discount applied. This includes the discounted amount, whether a discount was applied, the original amount, and specific details about the discount itself like fixed value, percentage, and formatted string representations. ```ruby price = Price(100).apply_discount("25%") price.amount # => 75.0 price.discounted? # => true price.original.amount # => 100.0 price.discount.fixed # => 25.0 (dollars saved this step) price.discount.percent # => 25.0 (percent of original this step) price.discount.to_fixed_s # => "25.00" price.discount.to_percent_s # => "25%" price.discount.to_formatted_s # => "25%" (natural format) ``` -------------------------------- ### Render Pricing Table with Promotions (ERB) Source: https://github.com/rubymonolith/superfeature/blob/main/README.md This ERB template renders a dynamic pricing table. It displays original and discounted prices when a promotion is active, shows a 'Free' label for zero-price plans, and includes a feature list with checkmarks. It depends on the `@promo` and `@plans` instance variables. ```erb
$<%= price.to_formatted_s(decimals: 0) %> $<%= discounted.to_formatted_s(decimals: 0) %> Save <%= discounted.discount.to_percent_s %>
<% else %><% if price.free? %> Free <% else %> $<%= price.to_formatted_s(decimals: 0) %>/mo <% end %>
<% end %>$<%= price.to_formatted_s(decimals: 0) %> $<%= discounted.to_formatted_s(decimals: 0) %> Save <%= discounted.discount.to_percent_s %>
<% else %><% if price.free? %> Free <% else %> $<%= price.to_formatted_s(decimals: 0) %>/mo <% end %>
<% end %>Upgrade to Pro to access the API
<% end %> # Feature type checks plan.projects.hard_limit? # => true plan.projects.soft_limit? # => false plan.projects.unlimited? # => false plan.api_access.boolean? # => true ``` -------------------------------- ### Chaining Price Operations and Traversing the Chain (Ruby) Source: https://github.com/rubymonolith/superfeature/blob/main/README.md Multiple pricing operations like discounts and rounding can be chained together. Each operation returns a new `Price` object, maintaining a reference to the previous state. This allows for easy traversal back through the chain using the `previous` attribute. ```ruby Price(100) .discount_percent(20) # => Price(80) .discount_fixed(10) # => Price(70) .round_up(9) # => Price(79) final = Price(100).discount_percent(20).round_up(9) final.amount # => 89 final.previous.amount # => 80 final.original.amount # => 100 final.discounted? # => true ``` -------------------------------- ### Round Prices to Specific Endings in Ruby Source: https://context7.com/rubymonolith/superfeature/llms.txt Demonstrates how to round Price objects to specific marketing-friendly endings, such as $X.99 or $X9. Includes options for rounding to the nearest, rounding up, or rounding down. ```ruby include Superfeature::Pricing price = Price(50) # Round to nearest ending price.round(9) # => Price(49) (nearest ending in 9) price.round_up(9) # => Price(59) (round up to ending in 9) price.round_down(9) # => Price(49) (round down to ending in 9) # Decimal endings (e.g., $X.99) Price(2.50).round(0.99) # => Price(2.99) Price(2.50).round_up(0.99) # => Price(2.99) Price(2.50).round_down(0.99) # => Price(1.99) # Larger endings ($99, $199, $299...) Price(150).round_up(99) # => Price(199) Price(250).round_down(99) # => Price(199) ``` -------------------------------- ### Define Base Plan Features with Limits (Ruby) Source: https://github.com/rubymonolith/superfeature/blob/main/README.md This Ruby code defines a base plan class using Superfeature. It sets up various feature types: boolean flags (priority_support, phone_support), hard limits (api_calls), soft limits with overages (storage_gb), and unlimited features (projects). It requires a `user` object for quantity-based features and uses helper methods for feature creation. ```ruby module Plans class Base < Superfeature::Plan attr_reader :user def initialize(user) @user = user end # Boolean features - simple on/off flags feature def priority_support = disable("Priority support", group: "Support") feature def phone_support = disable("Phone support", group: "Support") # Hard limits - strict maximum that cannot be exceeded feature def api_calls = hard_limit("API calls", group: "Limits", quantity: user.api_calls_count, maximum: 1000) # Soft limits - has a soft and hard boundary for overages feature def storage_gb = soft_limit("Storage", group: "Limits", quantity: user.storage_used_gb, soft_limit: 100, hard_limit: 150) # Unlimited - no restrictions feature def projects = unlimited("Projects", group: "Limits", quantity: user.projects_count) protected def feature(name, **options) Features::Base.new(name, **options) end end end ``` -------------------------------- ### Check Feature Access in Controller (Ruby) Source: https://github.com/rubymonolith/superfeature/blob/main/README.md This Ruby controller action demonstrates how to check if a user has a specific feature enabled using their plan. If the 'moderation' feature is enabled, it renders a 'moderation' view; otherwise, it redirects the user to an upgrade path. It relies on a `current_plan` helper method. ```ruby class ModerationController < ApplicationController def show if current_plan.moderation.enabled? render "moderation" else redirect_to upgrade_path end end private def current_plan @current_plan ||= current_user.plan end helper_method :current_plan end ``` -------------------------------- ### Rounding Prices to Specific Endings (Ruby) Source: https://github.com/rubymonolith/superfeature/blob/main/README.md The `Price` object supports rounding to specific numerical endings. Methods like `round`, `round_up`, and `round_down` allow you to adjust the price to the nearest, next highest, or next lowest value ending in a specified digit or decimal. ```ruby price = Price(50) price.round(9) # => Price(49) - nearest ending in 9 price.round_up(9) # => Price(59) - round up to ending in 9 price.round_down(9) # => Price(49) - round down to ending in 9 price.round(0.99) # => Price(49.99) - nearest ending in .99 ``` -------------------------------- ### 422 Error Message (Plain Text) Source: https://github.com/rubymonolith/superfeature/blob/main/spec/dummy/public/422.html This is the user-facing plain text message displayed for a 422 Unprocessable Entity error. It informs the user that their requested change was rejected and suggests checking logs if they are the application owner. No specific dependencies are mentioned. ```text The change you wanted was rejected. =================================== Maybe you tried to change something you didn't have access to. If you are the application owner check the logs for more information. ``` -------------------------------- ### Define Feature Base Class with Name and Group (Ruby) Source: https://github.com/rubymonolith/superfeature/blob/main/README.md This Ruby code defines a base feature class that extends `Superfeature::Feature`. It adds `name` and `group` attributes for display purposes, which are initialized during object creation. This class serves as a foundation for more specific feature definitions. ```ruby module Plans module Features class Base < Superfeature::Feature attr_reader :name, :group def initialize(name = nil, group: nil, **) super(**) @name = name @group = group end end end end ```