rails: validates_format_of use regex to limit line of numbers - ruby-on-rails

I'm try to use regex to limit line of numbers, I have tried this, does not work like what I thought.
I have a Product model has only info attribute, and it has one validation
validates_format_of :info, with: /(.*\n){,3}
in rails console
> product = Product.new
> product.info = <<-INFO
"> line one
"> line two
"> line three
"> line four
"> INFO
> product.valid?
=> true
I thought this should return false, as info has more than 3 line of numbers

You can use custom validation to a check number of lines in a string. In my opinion it provides better readability than using a regexp.
validate :info_cannot_have_many_lines
def info_cannot_have_many_lines
error << 'Info can not have more than 3 lines' if info.lines.count > 3
end
If you want to stick with a Regexp:
/\A(.*(\r\n|\r|\n)){,3}\z/
You need to use the anchors: \A (beginning of the string) and \z (end of the string)
Edit: I guess it is better to use (\r\n|\r|\n) instead of just \n, so it matches for other OS too.

Related

Rails - Usernames that cannot start or end with characters

Originally asked this question about Regex for Usernames: Usernames that cannot start or end with characters
How can I achieve this with correct syntax for Ruby on Rails?
Here's my current User.rb validation:
validates_format_of :username, with: /\A[\w\._]{3,28}\z/i
This validation allows underscores and periods, but the goal is to not allow them at the start or end of a username.
I'm trying to achieve these rules with Rails regex:
Can contain lowercase and uppercase letters and numbers
Can contain underscores and periods
Cannot contain 2 underscores in a row
Cannot contain 2 periods in a row
Cannot begin or end with an underscore or period
Cannot contain letters with accents
Must be between 3 and 28 letters in length
Valid:
Spicy_Pizza
97Indigos
Infinity.Beyond
Invalid:
_yahoo
powerup.
un__real
no..way
You may use
/\A(?=.{3,28}\z)[a-zA-Z0-9]+(?:[._][a-zA-Z0-9]+)*\z/
See the Rubular demo.
Details
\A - start of string
(?=.{3,28}\z) - 3 to 28 chars other than line break chars up to the end of the string are allowed/required
[a-zA-Z0-9]+ - one or more ASCII letters / digits
(?:[._][a-zA-Z0-9]+)* - 0+ sequences of:
[._] - a . or _
[a-zA-Z0-9]+ - one or more ASCII letters / digits
\z - end of string.
Although totally possible, as Wiktor's answer shows, my recommendation would be to not define this in a single regular expression, since:
The solution is quite confusing to understand, unless you know regular expressions quite well.
Similarly, the solution is quite difficult to update with new requirements, unless you understand regular expressions quite well.
By performing this entire check in one go, if a validation fails then you'll inevitably end up with one generic error message, e.g. "Invalid Format", which does not explain why it's invalid. The exercise is then left to the user to re-read the nontrivial format rules and understand why.
Instead, I would recommend defining a custom validation class, which can perform each of these checks separately (via easy to understand methods), and add a different error message upon each check failing.
Something along the lines of:
# app/models/user.rb
class User < ApplicationRecord
validates :username, presence: true, username: true
end
# app/validators/username_validator.rb
class UsernameValidator < ActiveModel::EachValidator
def validate(record, attribute, value)
validate_length(record, attribute, value)
validate_allowed_chars(record, attribute, value)
validate_sequential_chars(record, attribute, value)
validate_first_and_last_chars(record, attribute, value)
end
private
def validate_length(record, attribute, value)
unless value.length >= 3 && value.length <= 28
record.errors[attribute] << "must be between 3 and 28 characters long"
end
end
def validate_allowed_chars(record, attribute, value)
unless value =~ /\A[._a-zA-Z0-9]*\z/
record.errors[attribute] << "must only contain periods, underscores, a-z, A-Z or 0-9"
end
end
def validate_sequential_chars(record, attribute, value)
if value =~ /[._]{2}/
record.errors[attribute] << "cannot contain two consecutive periods or underscores"
end
end
def validate_first_and_last_chars(record, attribute, value)
if value =~ /\A[._]/ || value =~ /[._]\z/
record.errors[attribute] << "cannot start/end with a period or underscore"
end
end
end
So for instance, you asked above: "What if I needed to extend this to allow lowercase letters only?" I think it's now quite obvious how the code could be updated to accommodate such behaviour, but to be clear - all you'd need to do is:
def validate_allowed_chars(record, attribute, value)
unless value =~ /\A[._a-z0-9]*\z/
record.errors[attribute] << "must only contain periods, underscores, a-z or 0-9"
end
end
You could also now, quite easily, write tests for these validation checks, and assert that the correct validation is being performed by verifying against the contents of the error message; something that is not possible when all validation failures result in the same error,
Another benefit to this approach is that the code can easily be shared (perhaps with some slight behavioural differences). You could perform the same validation on multiple attributes, or multiple models, perhaps with different allowed lengths or formats.

How do I extract just a specific portion of a code snippet from multiple files, that may be different in different files

So what I am doing is iterating over various versions of snippet of code (for e.g. Associations.rb in Rails).
What I want to do is just extract one snippet of the code, for example the has_many method:
def has_many(name, scope = nil, options = {}, &extension)
reflection = Builder::HasMany.build(self, name, scope, options, &extension)
Reflection.add_reflection self, name, reflection
end
At first I was thinking of just searching this entire file for the string def has_many and then saving everything between that string and end. The obvious issue with this, is that different versions of this file can have multiple end strings within the method.
For instance, whatever I come up with for the above snippet, should also work for this one too:
def has_many(association_id, options = {})
validate_options([ :foreign_key, :class_name, :exclusively_dependent, :dependent, :conditions, :order, :finder_sql ], options.keys)
association_name, association_class_name, association_class_primary_key_name =
associate_identification(association_id, options[:class_name], options[:foreign_key])
require_association_class(association_class_name)
if options[:dependent] and options[:exclusively_dependent]
raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options. You may specify one or the other.' # ' ruby-mode
elsif options[:dependent]
module_eval "before_destroy '#{association_name}.each { |o| o.destroy }'"
elsif options[:exclusively_dependent]
module_eval "before_destroy { |record| #{association_class_name}.delete_all(%(#{association_class_primary_key_name} = '\#{record.id}')) }"
end
define_method(association_name) do |*params|
force_reload = params.first unless params.empty?
association = instance_variable_get("##{association_name}")
if association.nil?
association = HasManyAssociation.new(self,
association_name, association_class_name,
association_class_primary_key_name, options)
instance_variable_set("##{association_name}", association)
end
association.reload if force_reload
association
end
# deprecated api
deprecated_collection_count_method(association_name)
deprecated_add_association_relation(association_name)
deprecated_remove_association_relation(association_name)
deprecated_has_collection_method(association_name)
deprecated_find_in_collection_method(association_name)
deprecated_find_all_in_collection_method(association_name)
deprecated_create_method(association_name)
deprecated_build_method(association_name)
end
Assuming that each value is stored as text in some column in my db.
How do I approach this, using Ruby's string methods or should I be approaching this another way?
Edit 1
Please note that this question relates specifically to string manipulation via using a Regex, without a parser.
As discussed, this should be done with a parser like Ripper.
However, to answer if it can be done with string methods, I will match the syntax with a regex, provided:
You can rely on indentation i.e. the string has the exact same characters before "def" and before "end".
There are no multiline strings in between that could simulate an "end" with the same indentation. That includes multine strings, HEREDOC, %{ }, etc.
Code
regex = /^
(\s*) # matches the indentation (we'll backreference later)
def\ +has_many\b # literal "def has_many" with a word boundary
(?:.*+\n)*? # match whole lines - as few as possible
\1 # matches the same indentation as the def line
end\b # literal "end"
/x
subject = %q|
def has_many(name, scope = nil, options = {}, &extension)
if association.nil?
instance_variable_set("##{association_name}", association)
end
end|
#Print matched text
puts subject.to_enum(:scan,regex).map {$&}
ideone demo
The regex relies on:
Capturing the whitespace (indentation) with the group (\s*),
followed by the literal def has_many.
It then consumes as few lines as it can with (?:.*+\n)*?.
Notice that .*+\n matches a whole line
and (?:..)*? repeats it 0 or more times. Also, the last ? makes the repetition lazy (as few as possible).
It will consume lines until it matches the following condition...
\1 is a backreference, storing the text matched in (1), i.e. the exact same indentation as the first line.
Followed by end obviously.
Test in Rubular

Rails regex validation failing in rspec

I am trying to write and test a regex valiation which allows only for a sequence of paired integers, in the format
n,n n,n
where n is any integer not beginning with zero and pairs are space separated. There may be a single pair or the field may also be empty.
So with this data, it should give 2 errors
12,2 11,2 aa 111,11,11
error 1: the 'aa'
error 2: the triplet (111,11,11)
In my Rails model I have this
validates_format_of :sequence_excluded_region, :sequence_included_region,
with: /[0-9]*,[0-9] /, allow_blank: true
In my Rspec model test I have this
it 'is invalid with alphanumeric SEQUENCE_INCLUDED_REGION' do
expect(DesignSetting.create!(sequence_included_region: '12,2 11,2 aa 111,11,11')).to have(1).errors_on(:sequence_included_region)
end
The test fails, as the regex does not find the errors, or perhaps I am calling the test incorrectly.
Failures:
1) DesignSetting is invalid with alphanumeric SEQUENCE_INCLUDED_REGION
Failure/Error: expect(DesignSetting.create!(sequence_included_region: '12,2 11,2 aa 111,11,11')).to have(2).errors_on(:sequence_included_region)
expected 2 errors on :sequence_included_region, got 0
# ./spec/models/design_setting_spec.rb:5:in `block (2 levels) in <top (required)>'
Regex
Your regex matches a single pair followed by a space anywhere in the string.
'12,2 11,2 aa 111,11,11 13,3'.scan /[0-9]*,[0-9] /
=> ["12,2 ", "11,2 "]
So any string with one valid pair followed by a space will be valid. Also a single pair would fail 3,4 as there is no space.
A regex that would validate the entire string:
positive_int = /[1-9][0-9]*/
pair = /#{positive_int},#{positive_int}/
re_validate = /
\A # Start of string
#{pair} # Must have one number pair.
(?:\s#{pair})* # Can be followed by any number of pairs with a space delimiter
\z # End of string (no newline)
/x
Validators
I don't use rails much but it seems like you are expecting too much from a simple regex validator for it to parse out the individual error components from a string for you.
If you split the variable up by space and then validated each element of the array you could get that detail for each field.
'12,2 11,2 aa 111,11,11 13,3'.split(' ').reject{|f| f =~ /^[1-9][0-9]*,[1-9][0-9]*$/ }
You can put something like that into a custom validator class using validates_with which you can then have direct control of your errors with...
class RegionValidator < ActiveModel::Validator
def validate(record)
record.sequence_included_region.split(' ').reject{|f| f =~ /^[1-9][0-9]*,[1-9][0-9]*$/ }.each do |err|
record.errors[sequence_included_region] << "bad region field [#{err}]"
end
end
end
(?<=\s|^)\d+,\d+(?=\s|$)
Try this.Replace with empty string.The left string split by are your errors.
See demo.
http://regex101.com/r/rQ6mK9/22

Replacing {phrase} with phrase in rails

I'd like to search and replace any occurrence of {phrase} with with phrase using rails (erb.html file). Multiple phrases will need to be substituted, and the phrases aren't known in advance.
Full Example:
Hi {guys}, I really like {ruby on rails}
Needs to become
Hi guys, ruby on rails
This is for a user-generated content site (GMT)
it's simple regexp, just use
your_string.gsub(/{(.*?)}/, '\\1')
Example:
"{aaa} is not {bbb} you know".gsub(/{(.*?)}/, '\\1')
will produce
aaa is not bbb you know
You can do this using gsub
irb(main):001:0> str = " I have written this phrase statement, I want to replace occurences of all phrase with other statement"
=> " I have written this phrase statement, I want to replace occurences of all phrase with other statement"
irb(main):002:0> str.gsub("phrase",'phrase')
=> " I have written this phrase statement, I want to replace occurences of all phrase with other statement"
A better way to do this will be to use a Markdown output engine (Redcarpet being one of the most robust)
You'd have to create a custom renderer:
#lib/custom_renderer.rb
class AutoLinks < Redcarpet::Render::HTML
def auto_link(phrase) #-> will need to search through content. Can research further
link_to phrase, "/#{phrase}"
end
end
#controller
markdown = Redcarpet::Markdown.new(AutoLinks, auto_link: "ruby on rails")
Just use a helper in your erb. For example:
tag_helper.rb:
module TagHelper
def atag(phrase)
"<a href='/#{phrase}'>#{phrase}</a>"
end
end
some.html.erb:
<%= atag('guys')%>

ActionView::Helpers::TextHelper excerpt helper is not fully functional

I am using module ActionView::Helpers::TextHelper to generate an excerpt from a text. If a word exists more than once, it will just excerpt the first occurrence.
<%= excerpt('Hello, i am a Ruby lover, a Rails lover and would never come back to PHP', 'lover', :radius => 5) %>
"...lover,..."
I was expecting the return string to be something like, becauee there two occurrences of the word 'lover':
"...lover,...lover ..."
How can i get it to work to display multiple occurrences of a keyword?
I am using rails 3.2.11.
excerpt(text, phrase, options = {}) Link:
Extracts an excerpt from text that matches the first instance of phrase. The :radius option expands the excerpt on each side of the first occurrence of phrase
as the documantation states, is only the first instance of the phrase you search, not every instance of it
I've been using a multi_excerpt() method defined in my application_helper.rb
# Returns a summary of +text+ in the form of +phrase+ excerpts
#
# multi_excerpt('This string is is a very long long long string ', 'string', radius: 5)
# # => ...This string is i...long string ...
def multi_excerpt(text, phrase, options = {})
return unless text && phrase
radius = options.fetch(:radius, 10)
omission = options.fetch(:omission, "...")
raise if phrase.is_a? Regexp
regex = /.{,#{radius}}#{Regexp.escape(phrase)}.{,#{radius}}/i
parts = text.scan(regex)
"#{omission}#{parts.join(omission)}#{omission}"
end
Linking here my related post and PR.

Resources