I have problem while updating the application from Rails 4 to Rails 5.0.2
When I try I have this error:
/projects/tx/app/api/api_v2/validations.rb:3:in `<module:Validations>': uninitialized constant Grape::Validations::Validator (NameError)
from /projects/tx/app/api/api_v2/validations.rb:2:in `<module:APIv2>'
from /projects/tx/app/api/api_v2/validations.rb:1:in `<top (required)>'
from /projects/tx/app/api/api_v2/deposits.rb:1:in `require_relative'
from /projects/tx/app/api/api_v2/deposits.rb:1:in `<top (required)>'
Try to find solution for this but not success at all. Maybe Grape change some naming.
The code inside validations.rb seems like this:
module APIv2
module Validations
class Range < ::Grape::Validations::Validator
def initialize(attrs, options, required, scope)
#range = options
#required = required
super
end
def validate_param!(attr_name, params)
if (params[attr_name] || #required) && !#range.cover?(params[attr_name])
raise Grape::Exceptions::Validation, param: #scope.full_name(attr_name), message: "must be in range: #{#range}"
end
end
end
end
end
File deposits.rb is like this:
require_relative 'validations'
module APIv2
class Deposits < Grape::API
helpers ::APIv2::NamedParams
before { authenticate! }
desc 'Get your deposits history.'
params do
use :auth
optional :currency, type: String, values: Currency.all.map(&:code), desc: "Currency value contains #{Currency.all.map(&:code).join(',')}"
optional :limit, type: Integer, range: 1..100, default: 3, desc: "Set result limit."
optional :state, type: String, values: Deposit::STATES.map(&:to_s)
end
get "/deposits" do
deposits = current_user.deposits.limit(params[:limit]).recent
deposits = deposits.with_currency(params[:currency]) if params[:currency]
deposits = deposits.with_aasm_state(params[:state]) if params[:state].present?
present deposits, with: APIv2::Entities::Deposit
end
desc 'Get details of specific deposit.'
params do
use :auth
requires :txid
end
get "/deposit" do
deposit = current_user.deposits.find_by(txid: params[:txid])
raise DepositByTxidNotFoundError, params[:txid] unless deposit
present deposit, with: APIv2::Entities::Deposit
end
desc 'Where to deposit. The address field could be empty when a new address is generating (e.g. for bitcoin), you should try again later in that case.'
params do
use :auth
requires :currency, type: String, values: Currency.all.map(&:code), desc: "The account to which you want to deposit. Available values: #{Currency.all.map(&:code).join(', ')}"
end
get "/deposit_address" do
current_user.ac(params[:currency]).payment_address.to_json
end
end
end
You can find the reason here, and you can find initial method signature here.
change validations.rb
module APIv2
module Validations
class Range < ::Grape::Validations::Validator
def initialize(attrs, options, required, scope, opts = {})
#range = options
#required = required
super
end
def validate_param!(attr_name, params)
if (params[attr_name] || #required) && !#range.cover?(params[attr_name])
raise Grape::Exceptions::Validation, param: #scope.full_name(attr_name), message: "must be in range: #{#range}"
end
end
end
end
end
Related
I'm using the graphql-ruby gem and I have a mutation that updates a record. This is all working, but now I want to include the phony_rails gem to validate phone number.
The problem
As a dev with more FE experience, I'm not great with rails or ruby and I'm using this to learn. That said, I have two phone numbers that I want to validate -- home_phone_number and mobile_phone_number. My mutation argument looks like this:
# frozen_string_literal: true
module Mutations
module Person
class UpdatePerson < BaseMutation
visibility_role :introspect_admin
visibility_pundit_class PersonPolicy
argument :id, ID, required: true
argument :email, String, required: false
argument :home_phone_number, String, required: false
argument :mobile_phone_number, String, required: false
field :person, Types::Person::Person, null: true
def call(input = {})
current_user = context[:current_user]
authorize!(current_user)
person = Person.find(input[:id])
person.email = input[:email]
person.home_phone_number = input[:home_phone_number]
person.mobile_phone_number = input[:mobile_phone_number]
person.save!
{ error: nil, person: person }
end
# #param [User|nil] current_user
def authorize!(current_user)
raise Pundit::NotAuthorizedError, 'Not allowed to update person.' unless
PersonPolicy.new(current_user, nil).update?
end
end
end
end
Now I want to add validation to home_phone_number and mobile_phone_number. I have written my tests to look like this:
context 'invalid number home phone number' do
let(:variables) do
{
'input' => {
'id' => person.id,
'homePhoneNumber' => '123'
}
}
end
it 'should return an error if home phone is invalid' do
expect(subject).not_to contain_graphql_errors
expect(data_dig('error')).not_to be_nil
expect(data_dig('error', 'error')).to eq('standard_error')
expect(data_dig('error', 'description')).to eq('Home phone number must be valid')
end
end
context 'invalid number mobile phone number' do
let(:variables) do
{
'input' => {
'id' => person.id,
'mobilePhoneNumber' => '123'
}
}
end
it 'should return an error if mobile phone is invalid' do
expect(subject).not_to contain_graphql_errors
expect(data_dig('error')).not_to be_nil
expect(data_dig('error', 'error')).to eq('standard_error')
expect(data_dig('error', 'description')).to eq('Mobile phone number must be valid')
end
end
What I've tried
What I can get working is this, but not necessarily passing my tests:
def call(input = {})
current_user = context[:current_user]
authorize!(current_user)
validate_phone_numbers(input[:home_phone_number], input[:mobile_phone_number])
# ....
def validate_phone_numbers(home_phone_number, mobile_phone_number)
phone_numbers = [home_phone_number, mobile_phone_number]
phone_numbers.each do |contact|
raise StandardError, 'Phone Number must be valid' if !PhonyRails.plausible_number?(contact) #would this stop execution too?
end
end
As you can see, in doing this, I wouldn't be able to specify which is a home phone number vs mobile phone number.
I've also tried doing this one-by-one:
def validate_phone_numbers(home_phone_number, mobile_phone_number)
home_phone_number_valid = PhonyRails.plausible_number?(home_phone_number)
mobile_phone_number_valid = PhonyRails.plausible_number?(mobile_phone_number)
raise StandardError, 'Home phone number must be valid' if !home_phone_number_valid
raise StandardError, 'Mobile phone number must be valid' if !mobile_phone_number_valid
# Stop execution
return if !home_phone_number_valid || !mobile_phone_number_valid
end
The lines above also do not exactly work.
Some guidance would be immensely appreciated. Thank you!
Instead of returning { error: nil, person: person } in your resolver, try returning { error: errors_array, person: person } where errors_array contains any validation error messages from phony.
eg.
def call
...
errors_array = []
errors_array << 'Home Phone Number must be valid' if !PhonyRails.plausible_number?(input[:home_phone_number])
errors_array << 'Mobile Phone Number must be valid' if !PhonyRails.plausible_number?(input[:mobile_phone_number])
...
{ error: errors_array, person: person }
end
Also make sure cover the case where both the mobile and home phone numbers are invalid in your specs.
I hope this helps!
Related/Fixed: Ruby on Rails: Validations on Form Object are not working
I have the below validation..
validates :age, numericality: { greater_than_or_equal_to: 0,
only_integer: true,
:allow_blank => true
}
It is not required, if entered needs to be a number. I have noticed that if someone types in a word instead of a number, the field value changes to 0 after submit and passes validation. I would prefer it to be blank or the entered value.
Update:
Still no solution, but here is more information.
rspec test
it "returns error when age is not a number" do
params[:age] = "string"
profile = Registration::Profile.new(user, params)
expect(profile.valid?).to eql false
expect(profile.errors[:age]).to include("is not a number")
end
Failing Rspec Test:
Registration::Profile Validations when not a number returns error when age is not a number
Failure/Error: expect(profile.errors[:age]).to include("is not a number")
expected [] to include "is not a number"
2.6.5 :011 > p=Registration::Profile.new(User.first,{age:"string"})
2.6.5 :013 > p.profile.attributes_before_type_cast["age"]
=> "string"
2.6.5 :014 > p.age
=> 0
2.6.5 :015 > p.errors[:age]
=> []
2.6.5 :016 > p.valid?
=> true
#Form Object Registration:Profile:
module Registration
class Profile
include ActiveModel::Model
validates :age, numericality: { greater_than_or_equal_to: 0,
only_integer: true,
:allow_blank => true
}
attr_reader :user
delegate :age , :age=, to: :profile
def validate!
raise ArgumentError, "user cant be nil" if #user.blank?
end
def persisted?
false
end
def user
#user ||= User.new
end
def teacher
#teacher ||= user.build_teacher
end
def profile
#profile ||= teacher.build_profile
end
def submit(params)
profile.attributes = params.slice(:age)
if valid?
profile.save!
true
else
false
end
end
def self.model_name
ActiveModel::Name.new(self, nil, "User")
end
def initialize(user=nil, attributes={})
validate!
#user = user
end
end
end
#Profile Model:
class Profile < ApplicationRecord
belongs_to :profileable, polymorphic: true
strip_commas_fields = %i[age]
strip_commas_fields.each do |field|
define_method("#{field}=".intern) do |value|
value = value.gsub(/[\,]/, "") if value.is_a?(String) # remove ,
self[field.intern] = value
end
end
end
The interesting thing is that if move the validation to the profile model and check p.profile.errors, I see the expected result, but not on my form object. I need to keep my validations on my form object.
If the underlying column in the DB is a numeric type, then Rails castes the value. I assume this is done in [ActiveRecord::Type::Integer#cast_value][1]
def cast_value(value)
value.to_i rescue nil
end
Assuming model is a ActiveRecord model where age is a integer column:
irb(main):008:0> model.age = "something"
=> "something"
irb(main):009:0> model.age
=> 0
irb(main):010:0>
This is because submitting a form will always submit key value pairs, where the keys values are strings.
No matter if your DB column is a number, boolean, date, ...
It has nothing to do with the validation itself.
You can access the value before the type cast like so:
irb(main):012:0> model.attributes_before_type_cast["age"]
=> "something"
If your requirements dictate another behaviour you could do something like this:
def age_as_string=(value)
#age_as_string = value
self.age = value
end
def age_as_string
#age_as_string
end
And then use age_as_string in your form (or whatever). You can also add validations for this attribute, e.g.:
validates :age_as_string, format: {with: /\d+/, message: "Only numbers"}
You could also add a custom type:
class StrictIntegerType < ActiveRecord::Type::Integer
def cast(value)
return super(value) if value.kind_of?(Numeric)
return super(value) if value && value.match?(/\d+/)
end
end
And use it in your ActiveRecord class through the "Attributes API":
attribute :age, :strict_integer
This will keep the age attribute nil if the value you are trying to assign is invalid.
ActiveRecord::Type.register(:strict_integer, StrictIntegerType)
[1]: https://github.com/rails/rails/blob/fbe2433be6e052a1acac63c7faf287c52ed3c5ba/activemodel/lib/active_model/type/integer.rb#L34
Why don't you add validations in frontend? You can use <input type="number" /> instead of <input type="text" />, which will only accept number from the user. The way I see you explaining the issue, this is a problem to be resolved in the frontend rather than backend.
You can read more about it here: Number Type Input
Please let me know if this doesn't work for you, I will be glad to help you.
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 2 years ago.
Improve this question
In our project, we are currently using Policy classes to allow authorization for users. This is present for every model, so I want to add the policy_generator and policy_spec_generator to the existing rails g scaffold command so it will create the relevant models, controllers, views and my new policy files (both the Policy and spec) all in a single command.
How do I go about doing this? My initial thought would be to look into Railties and edit the files using the lib folder, but I can't seem to figure out what or where to add the code. Thanks!
UPDATE
So I've been trial and testing for a good part of the day and came up with a solution. What I did was to copy the code for rails' scaffold_generator.rb into my lib BUT it must be with the correct namespacing (in my case, lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb). I'm sure it'd be the same path in any rails project but give it a few go to make sure.
Also, I notice that the patterns for modifying generator files have been like so lib/rails/generators/rails/<generator_folder>/<generator_file>_generator.rb and it's template in this path lib/templates/rails/<generator_folder>/<generator_file>.rb. It's pretty confusing at first since it doesn't match the lib path in Railties or other gems.
As for the actual method itself, here's a copy of the scaffold_controller_generator.rb. I've added a comment where I added my create_policy_file method. The rest are unchanged.
module Rails
module Generators
class ScaffoldControllerGenerator < NamedBase # :nodoc:
include ResourceHelpers
check_class_collision suffix: "Controller"
class_option :helper, type: :boolean
class_option :orm, banner: "NAME", type: :string, required: true,
desc: "ORM to generate the controller for"
class_option :api, type: :boolean,
desc: "Generates API controller"
class_option :skip_routes, type: :boolean, desc: "Don't add routes to config/routes.rb."
argument :attributes, type: :array, default: [], banner: "field:type field:type"
def create_controller_files
template_file = options.api? ? "api_controller.rb" : "controller.rb"
template template_file, File.join("app/controllers", controller_class_path, "#{controller_file_name}_controller.rb")
end
# My new method to generate policy files
def create_policy_files
template "policy.rb", File.join("app/policies", controller_class_path, "#{singular_name}_policy.rb")
end
hook_for :template_engine, as: :scaffold do |template_engine|
invoke template_engine unless options.api?
end
hook_for :resource_route, required: true do |route|
invoke route unless options.skip_routes?
end
hook_for :test_framework, as: :scaffold
# Invoke the helper using the controller name (pluralized)
hook_for :helper, as: :scaffold do |invoked|
invoke invoked, [ controller_name ]
end
private
def permitted_params
attachments, others = attributes_names.partition { |name| attachments?(name) }
params = others.map { |name| ":#{name}" }
params += attachments.map { |name| "#{name}: []" }
params.join(", ")
end
def attachments?(name)
attribute = attributes.find { |attr| attr.name == name }
attribute&.attachments?
end
end
end
end
For generatig the spec files, you can just add it inside the file above in the same method even but it's not that pretty. What I did then is to add it inside rspec's scaffold_generator.rb.
Here's the scaffold_generator.rb. Namespacing it is important! Mine's at lib/rails/generatots/rspec/scaffold/scaffold_generator.rb. To find out where I get this path, look into RSpec's lib folder in its repo and follow the same pattern to add it to your project.
require 'generators/rspec'
require 'rails/generators/resource_helpers'
module Rspec
module Generators
# #private
class ScaffoldGenerator < Base
include ::Rails::Generators::ResourceHelpers
source_paths << File.expand_path('../helper/templates', __dir__)
argument :attributes, type: :array, default: [], banner: "field:type field:type"
class_option :orm, desc: "ORM used to generate the controller"
class_option :template_engine, desc: "Template engine to generate view files"
class_option :singleton, type: :boolean, desc: "Supply to create a singleton controller"
class_option :api, type: :boolean, desc: "Skip specs unnecessary for API-only apps"
class_option :controller_specs, type: :boolean, default: false, desc: "Generate controller specs"
class_option :request_specs, type: :boolean, default: true, desc: "Generate request specs"
class_option :view_specs, type: :boolean, default: true, desc: "Generate view specs"
class_option :helper_specs, type: :boolean, default: true, desc: "Generate helper specs"
class_option :routing_specs, type: :boolean, default: true, desc: "Generate routing specs"
class_option :policy_specs, type: :boolean, default: true, desc: "Generate policy specs"
def initialize(*args, &blk)
#generator_args = args.first
super(*args, &blk)
end
def generate_controller_spec
return unless options[:controller_specs]
if options[:api]
template 'api_controller_spec.rb', template_file(folder: 'controllers', suffix: '_controller')
else
template 'controller_spec.rb', template_file(folder: 'controllers', suffix: '_controller')
end
end
def generate_request_spec
return unless options[:request_specs]
if options[:api]
template 'api_request_spec.rb', template_file(folder: 'requests')
else
template 'request_spec.rb', template_file(folder: 'requests')
end
end
def generate_view_specs
return if options[:api]
return unless options[:view_specs] && options[:template_engine]
copy_view :edit
copy_view :index unless options[:singleton]
copy_view :new
copy_view :show
end
def generate_routing_spec
return unless options[:routing_specs]
template_file = File.join(
'spec/routing',
controller_class_path,
"#{controller_file_name}_routing_spec.rb"
)
template 'routing_spec.rb', template_file
end
# My new method to generate policy spec files
def generate_policy_spec
return unless options[:policy_specs]
template_file = File.join(
'spec/policies',
controller_class_path,
"#{singular_name}_policy_spec.rb"
)
template 'policy_spec.rb', template_file
end
protected
attr_reader :generator_args
def copy_view(view)
template "#{view}_spec.rb",
File.join("spec/views", controller_file_path, "#{view}.html.#{options[:template_engine]}_spec.rb")
end
# support for namespaced-resources
def ns_file_name
return file_name if ns_parts.empty?
"#{ns_prefix.map(&:underscore).join('/')}_#{ns_suffix.singularize.underscore}"
end
# support for namespaced-resources
def ns_table_name
return table_name if ns_parts.empty?
"#{ns_prefix.map(&:underscore).join('/')}/#{ns_suffix.tableize}"
end
def ns_parts
#ns_parts ||= begin
parts = generator_args[0].split(/\/|::/)
parts.size > 1 ? parts : []
end
end
def ns_prefix
#ns_prefix ||= ns_parts[0..-2]
end
def ns_suffix
#ns_suffix ||= ns_parts[-1]
end
def value_for(attribute)
raw_value_for(attribute).inspect
end
def raw_value_for(attribute)
case attribute.type
when :string
attribute.name.titleize
when :integer, :float
#attribute_id_map ||= {}
#attribute_id_map[attribute] ||= #attribute_id_map.keys.size.next + attribute.default
else
attribute.default
end
end
def template_file(folder:, suffix: '')
File.join('spec', folder, controller_class_path, "#{controller_file_name}#{suffix}_spec.rb")
end
def banner
self.class.banner
end
end
end
end
Hope this helps others who's battling code generation too!
Please have a look into the official guide here https://guides.rubyonrails.org/generators.html#creating-your-first-generator.
Short summary, create a file in lib/generators/initializer_generator.rb with this content:
class InitializerGenerator < Rails::Generators::Base
def create_initializer_file
create_file "config/initializers/initializer.rb", "# Add initialization content here"
end
end
and execute it with rails generate initializer.
There is even a generator which can do this for you https://guides.rubyonrails.org/generators.html#creating-generators-with-generators.
I'm writing an API server with grape and i choose to use grape-entity because it has the capability to auto generate the documentation for swagger.
But now i have a problem when i set a param as required. Because grape don't validate that the param is present. It looks like grape ignores the required: true of the entity's params.
app.rb
module Smart
module Version1
class App < BaseApi
resource :app do
# POST /app
desc 'Creates a new app' do
detail 'It is used to re gister a new app on the server and get the app_id'
params Entities::OSEntity.documentation
success Entities::AppEntity
failure [[401, 'Unauthorized', Entities::ErrorEntity]]
named 'My named route'
end
post do
app = ::App.create params
present app, with: Entities::AppEntity
end
end
end
end
end
os_entity.rb
module Smart
module Entities
class OSEntity < Grape::Entity
expose :os, documentation: { type: String, desc: 'Operative system name', values: App::OS_LIST, required: true }
end
end
end
app_entity.rb
module Smart
module Entities
class AppEntity < OSEntity
expose :id, documentation: { type: 'integer', desc: 'Id of the created app', required: true }
expose :customer_id, documentation: { type: 'integer', desc: 'Id of the customer', required: true }
end
end
end
Everything else is working great now, but i don't know how to use the entities in a DRY way, and make grape validating the requirement of the parameter.
After some work, I was able to make grape work as I think it should be working. Because I don't want to repeat the code for both of the validation and the documentation. You just have to add this to the initializers (if you are in rails, of course). I also was able to support nested associations. As you can see, the API code looks so simple and the swagger looks perfect.
Here are the API and all the needed entities:
app/api/smart/entities/characteristics_params_entity.rb
module Smart
module Entities
class CharacteristicsParamsEntity < Grape::Entity
root :characteristics, :characteristic
expose :id, documentation: { type: Integer, desc: 'Id of the characteristic' }
end
end
end
app/api/smart/entities/characterisitcs_entity.rb
module Smart
module Entities
class CharacteristicsEntity < CharacteristicsParamsEntity
expose :id, documentation: { type: Integer, desc: 'Id of the characteristic' }
expose :name, documentation: { type: String, desc: 'Name of the characteristic' }
expose :description, documentation: { type: String, desc: 'Description of the characteristic' }
expose :characteristic_type, documentation: { type: String, desc: 'Type of the characteristic' }
expose :updated_at, documentation: { type: Date, desc: 'Last updated time of the characteristic' }
end
end
end
app/api/smart/entities/apps_params_entity.rb
module Smart
module Entities
class AppsParamsEntity < Grape::Entity
expose :os, documentation: { type: String, desc: 'Operative system name', values: App::OS_LIST, required: true }
expose :characteristic_ids, using: CharacteristicsParamsEntity, documentation: { type: CharacteristicsParamsEntity, desc: 'List of characteristic_id that the customer has', is_array: true }
end
end
end
app/api/smart/entities/apps_entity.rb
module Smart
module Entities
class AppsEntity < AppsParamsEntity
unexpose :characteristic_ids
expose :id, documentation: { type: 'integer', desc: 'Id of the created app', required: true }
expose :customer_id, documentation: { type: 'integer', desc: 'Id of the customer', required: true }
expose :characteristics, using: CharacteristicsEntity, documentation: { is_array: true, desc: 'List of characteristics that the customer has' }
end
end
end
app/api/smart/version1/apps.rb
module Smart
module Version1
class Apps < Version1::BaseAPI
resource :apps do
# POST /apps
desc 'Creates a new app' do
detail 'It is used to register a new app on the server and get the app_id'
params Entities::AppsParamsEntity.documentation
success Entities::AppsEntity
failure [[400, 'Bad Request', Entities::ErrorEntity]]
named 'create app'
end
post do
app = ::App.create! params
present app, with: Entities::AppsEntity
end
end
end
end
end
And this is the code that do the magic to make it work:
config/initializers/grape_extensions.rb
class Evaluator
def initialize(instance)
#instance = instance
end
def params parameters
evaluator = self
#instance.normal_params do
evaluator.list_parameters(parameters, self)
end
end
def method_missing(name, *args, &block)
end
def list_parameters(parameters, grape)
evaluator = self
parameters.each do |name, description|
description_filtered = description.reject { |k| [:required, :is_array].include?(k) }
if description.present? && description[:required]
if description[:type] < Grape::Entity
grape.requires name, description_filtered.merge(type: Array) do
evaluator.list_parameters description[:type].documentation, self
end
else
grape.requires name, description_filtered
end
else
if description[:type] < Grape::Entity
grape.optional name, description_filtered.merge(type: Array) do
evaluator.list_parameters description[:type].documentation, self
end
else
grape.optional name, description_filtered
end
end
end
end
end
module GrapeExtension
def desc name, options = {}, &block
Evaluator.new(self).instance_eval &block if block
super name, options do
def params *args
end
instance_eval &block if block
end
end
end
class Grape::API
class << self
prepend GrapeExtension
end
end
This is the result of the example:
I love the grape/grape-swagger/grape-entity combination for building API's. I generally use the grape entities for building the result, and not at all for documenting/validating the API. According to the documentation (for grape-entity) it should work, but I am guessing just to build the documentation.
According to the grape documentation on parameter validation and coercion it requires a block to enforce any validation/coercion.
[EDIT: mixing up params]
You can define the params in the desc using an entity, but for validation you have to supply the params block, on the same level as the desc block, so for example:
# POST /app
desc 'Creates a new app' do
detail 'It is used to re gister a new app on the server and get the app_id'
params Entities::OSEntity.documentation
success Entities::AppEntity
failure [[401, 'Unauthorized', Entities::ErrorEntity]]
named 'My named route'
end
params do
requires :name, String
optional :description, String
end
post do
app = ::App.create params
present app, with: Entities::AppEntity
end
They are both called params but located quite differently and with a different function.
I am not sure if the desc block has any use other than documentation (and how to extract this documentation is a bit of a mystery to me).
The grape-swagger gem does not use it, my typical desc looks like this:
desc "list of batches", {
:notes => <<-NOTE
Show a list of all available batches.
## Request properties
* _Safe:_ Yes
* _Idempotent:_ Yes
* _Can be retried:_ Yes
NOTE
}
params do
optional :page, desc: 'paginated per 25'
end
get do
present Batch.page(params[:page]), with: API::Entities::Batch
end
where the :notes are rendered using markdown. How this looks in swagger-ui
I need to create an enumeration that I will need to initialize from a value from the querystring.
Example of what I have and what I need to do:
class UserType
NONE = 0
MEMBER = 1
ADMIN = 2
SUPER = 3
end
Now in my querystring I will have:
/users/load_by_type?type=2
Now in my controller I will get the value 2 from the querystring, I then need to have a UserType object which has the value 'MEMBER'.
How can I do this?
If my class isn't really a good enumeration hack, please advise.
How about something like this.
require 'active_record'
# set up db
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
# define schema
ActiveRecord::Schema.define do
suppress_messages do
create_table :users do |t|
t.string :name
t.string :role
end
end
end
# define class
class User < ActiveRecord::Base
Roles = %w[none member admin super].map(&:freeze)
validates_inclusion_of :role, in: Roles
end
# specification
describe User do
before { User.delete_all }
let(:valid_role) { User::Roles.first }
let(:invalid_role) { valid_role.reverse }
it 'is valid if its role is in the Roles list' do
User.new.should_not be_valid
User.new(role: valid_role).should be_valid
User.new(role: invalid_role).should_not be_valid
end
let(:role) { User::Roles.first }
let(:other_role) { User::Roles.last }
it 'can find users by role' do
user_with_role = User.create! role: role
user_with_other_role = User.create! role: other_role
User.find_all_by_role(role).should == [user_with_role]
end
end
It does have the disadvantage of using an entire string (255 chars) for the enumeration method, but it also has the advantage of readability and ease of use (it would probably come in as "/users/load_by_role?role=admin"). Besides, if at some point it winds up costing too much, it should be easy to update to use a small integer.
I think I'd rather use hashes for this kind of thing, but just for fun:
class Foo
BAR = 1
STAN = 2
class << self
def [](digit)
constants.find { |const| const_get(const) == digit }
end
end
end
puts Foo[1] # BAR
puts Foo[2] # STAN