Using Rails built-in validator to validate hash with dynamic options - ruby-on-rails

I have two models, CharacterSheet and Sheet, both of which have the column properties. Sheet's properties column is used to store key-value pairs of property names and their validation (which are in idiomatically correct Ruby validates syntax).
Sheet.create!(name: 'A Test Sheet',
properties: {
prop1: { numericality: { only_integer: true }, length: { maximum: 3 } },
prop2: { numericality: { only_integer: true }, length: { minimum: 1 } },
prop3: { numericality: { only_integer: true } }
})
CharacterSheet stores key-value pairs of property names and their actual values, validated by the appropriate Sheet key-value pair.
CharacterSheet.create(sheet_id: 1,
name: 'A Test CharacterSheet',
properties: {
prop1: 123,
prop2: 234,
prop3: 345
})
Ideally, I want to use the validation requirements stored in Sheet.properties to use Rails' built-in validation methods, so I don't have to rewrite all of that validation myself. However, I can't find an appropriate method to hook into. self.validates doesn't seem to serve my purposes since it seems like (from what I could tell) it requires a valid column name, which it then uses to pull the value, instead of allowing a value to be passed in directly.

So the first problem I see is that you'll need also to convert what it means, numericality and how to fetch that property, and only_integer and what does that mean.
I would advise you to use something like
prop_name: { type_of_validation: 'regexp', validator: 'Regex-representation' }
prop_name2: { type_of_validation: 'type', validator: 'Hash' }
the regex representation will need to be double-escaped.
because then you can do
loaded_sheet = Sheet.find(csheet.sheet_id).properties.as_json.with_indifferent_access
csheet.properties.as_json.with_indifferent_access.each do |k,v|
if loaded_sheet[k.to_sym][:type_of_validation] == 'regexp'
regex = Regexp.new loaded_sheet[k.to_sym][:validator]
return false unless v =~ regex
elsif loaded_sheet[k.to_sym[:type_of_validation] == 'type'
return false unless v.is_a?(loaded_sheet[k.to_sym][:validator].constantize)
end
end

Related

Rspec object validation failing... unclear on `:foo` vs `"foo"`

I've written a test and it seems so close but not quite right.
it "adds the correct permissions" do
subject
expect(policy.permissions).to have_key("users")
expect(policy.permissions["users"]).to eq({ "all_users" => {
can_view: false,
can_manage: false,
} })
end
Output:
expected: {"all_users"=>{:can_manage=>false, :can_view=>false}}
got: {"all_users"=>{"can_manage"=>false, "can_view"=>false}}
Obviously I'm close but I'm not sure what the deal is in terms of the : notation versus the "got" output from the test itself. How do I get this to pass?
String keys are not equal to symbol keys in Hash class. Here is the example code below:
hash = {}
hash[:a] = 1
hash['b'] = 2
hash[:a] # returns 1
hash['a'] # returns nil
hash[:b] # returns nil
hash['b'] #returns 2
so you should expect the result like this:
expect(policy.permissions["users"]).to eq({ 'all_users' => { 'can_view' => false, 'can_manage' => false, } })

Rails - REGEX - validating length of non whitespace / special characters

I have a few fields with a length validation, which can be bypassed by entering n number of white spaces. I'm trying to write a method that validates the number of alpha numeric characters only (not whitespace or special characters).
I've gotten as far as the following:
validates :title,
presence: true,
length: { minimum: 4, maximum: 140 },
format: { with: /([A-z0-9])/ }
What I can't get is how to validate the length of the title that matches the format. For example I want to allow a title of 'The Beast', but only count 'TheBeast' in the character count. This will allow 'The Beast' and include the space in the length validation
Is there something built into rails that allows me to do this? Or if not what's the best way go about writing a custom method?
Thanks in advance
if you would have aux column like 'filtered_title' you could do:
before_save :filter_title
def filter_title
self.filtered_title = title.gsub(/[^0-9a-zA-Z]/, '') // strip unneeded chars
end
and your validator, but on a the new column
validates :filtered_title,
presence: true,
length: { minimum: 4, maximum: 140 },
format: { with: /([A-z0-9])/ }
To expand on #NeverBe's answer, I went with:
class AlphanumericLengthValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
minimum_length = options.fetch(:length, 100)
stripped_value = value ? value.gsub(/[^0-9a-zA-Z]/, '') : nil
message = "must be at least #{minimum_length} alphanumeric characters in length"
return if stripped_value&.length && stripped_value.length >= minimum_length
record.errors.add(attribute, message) if !stripped_value || stripped_value.length < minimum_length
end
end
Which allowed me to do:
validates :title, alphanumeric_length: { length: 8 }

How can hash.values.all? == false return true when all the values are not false?

How can #filters.values.all? == false return true when `#filters.values' clearly shows three true values?
EDIT: And how do you check to see if every value is false?
EDIT2: Because people want to copy and paste an arbitrary code snippet:
f = {
self: true,
clear: true,
something1: false,
something2: true
}
f.all? == false
==> false
f.values.all? == false
==> true
f.values
==> [true, true, false, true]
Enumerable#all?, from the docs:
Passes each element of the collection to the given block. The method
returns true if the block never returns false or nil. If the block is
not given, Ruby adds an implicit block of { |obj| obj } which will
cause all? to return true when none of the collection members are
false or nil.
That means that #filters.values.all? will return false unless every attribute is set to true (or truthy, to be more accurate) so, if you want to know when every item is false, then you will have to pass a block to all? and check each value, like this:
#filters.values.all? { |value| value == false } #=> true
UPDATE
Previous answer stated that also !#filters.values.all? will return true when all values are truthy, and that's true, but it will also return true if only one is set to false; in fact, it will always return true unless all values are set to true.
I guess the all? method will call the block with each
For eaxmple enumerable.all? will be executed like this:
enumerable.each {|x| x }
hash_enumerable.each {|k, _v| k }
So when the enumerable is a hash the block firt params will be the key....
To check if all the values in your hash is false, try this:
temp = {self: false, clear: false, lorem: false, ipsum: false}
temp.values.uniq == [false]
#=> true
temp2 = {self: true, clear: true, lorem: false, ipsum: true}
temp2.values.uniq == [false]
#=> false
A shorter, though slightly less clear, way to determine if all of the values are false is to simply ask if any of them are true and negate the response:
#filters = {foo: false, bar: true, baz: false}
!#filters.values.any? #=> false
#filters = {foo: false, bar: false, baz: false}
!#filters.values.any? #=> true

How to filter Groovy/Grails closure from web app

I have a web app that makes a call to a grails app for its db calls. The db is filled with products that are returned through groovy calls. An example object that I would get from the db is as follows:
class Product{
Boolean is_blue;
Boolean is_round;
Boolean is_alive;
Boolean is_active;
String type;
String name;
}
I want to make a call to the grails app to filter on these boolean values but I am not sure how to do it via a closure, this what my closure currently looks like.
def productXML =
Product.findAll("from Product as p where p.is_active = 1 and p.type = :type
ORDER BY p.${params.sort} ${params.order}",
[type: type], [max: params.max, offset: params.offset])
What I'm most confused on is how I can pass these parameters to the closure. Any help would be greatly appreciated. Thanks.
Something like
def productXML =
Product.findAll("from Product as p where p.is_active is :active \
and p.type = :type \
ORDER BY p.${params.sort} ${params.order}",
[type: type, active: true],
[max: params.max, offset: params.offset])
OR
def productXML = Product.findAll(params){
type == type && is_active == active
}
is what you are looking for? Refer findAll for details.
Always use substitution variables
To make parameters optional, use this trick (using type as an example):
def productXML = Product.findAll("from Product as p where p.is_active is :active
and (p.type = :type or :type == null)
ORDER BY p.:sort :order",
[type: type, active: true, sort:params.sort, order:params.order], [max: params.max, offset: params.offset])
I ended up making a query builder where if in in the query string it had is_blue=1, I would add that to the query.
if(params.is_blue){
query +=" and p.is_blue = ${params.is_blue}"
}

How to retrieve instance attribute names for which the value is true?

I am using Ruby on Rails 3.2.9 and Ruby 1.9.3. I have an array of symbols and a model class (ActiveModel) having those symbols (more others) as boolean attributes. Given a class instance I would like to retrieve all its attribute names for which the value is true. That is:
# Given an array of symbols
[:attr_1, :attr_2, :attr_3]
# Given a class instance
<#Model attr_1: true, attr_2: false, attr_3: false, attr_4: true, ... attr_N: true>
# Then I would like to return
[:attr_1, :attr_4, ..., :attr_N]
How can I make that?
attrs = [:attr_1, :attr_2, :attr_3]
class Model
def are_true?(attr_names)
eval(attr_names.map {|a| "send(:#{a})"}.join(" && "))
end
def which_true?(attr_names)
attr_names.map {|a| a if send(a) == true}.compact
end
end
m = <#Model attr_1: true, attr_2: false, attr_3: false, attr_4: true, ... attr_N: true>
m.are_true?(attrs) # evals if all given attr_names are true
m.which_true?(attrs) # returns array of attr_names which are true
If you have the list of symbols you can iterate through them and select the ones that are true
symbols = [:attr_1, :attr_2, :attr_3]
symbols.select {|sym| object.send(sym) == true }
If you don't have the list of symbols you want you can simply iterate through all the attributes of the model
symbols = object.attributes
symbols.select {|sym| object.send(sym) == true }

Resources