Ruby on Rails: Assign an attribute to nil if validation fails - ruby

My question is how to assign an attribute to nil if it's invalid?
Let's say I have a custom validator
validate :record_format
def record_format
error_messages = CustomValidator.new.errors.full_messages
errors.add(:base, error_messages)
end
CustomValidator is used in a few different places.
So instead of errors.add(:base, error_messages) I want to assign my attribute to nil on a failed validation only in one class. Is there a profound way to do so apart from just setting it directly as attribute_name = nil?
Thanks

Related

Authorization on a field in graphql-ruby

From this guide I don't understand how we can add authorization on a field in graphql-ruby.
I understand how we can deny access to an entire object but I don't see how we can prevent access to just a field of an object.
In my usecase, I want to prevent some queries to access a String field in my UserType.
Is it possible? With the field helper?
You could use scoping in the GraphQL-ruby or use gem graphql-guard.
For scoping to work, you should be either using Pundit scope or CanCan accessible_by.
FYI, I haven't used it yet anywhere but seems like both could solve your problem to me.
I used gem graphql-guard.
GUARD = lambda do |obj, args, ctx|
...some logics...
end
field :some_field, String, null: false, guard: GUARD
And when I wanted to use only some of the arguments like lambda do |_, args, _|
Here's an example:
module Types
class User < Types::BaseObject
field :private_string, String, null: true do
def authorized?(object, args, context)
# Do whatever logic you want and return true if the user is authorized.
object.id == context[:current_user].id
end
end
end
end
class MySchema < GraphQL::Schema
# You'll need this in your schema to handle an unauthorized field.
def self.unauthorized_field(error)
raise GraphQL::ExecutionError, "The field #{error.field.graphql_name} on an object of type #{error.type.graphql_name} was hidden due to permissions"
end
end

Ruby on Rails to_xml nil="True"

I need your help on to_xml function. How can i make all nil="True" value to a default value '' (blank) when exporting to xml from active record.
The #to_xml method Rails adds to ActiveRecord, Array, and Hash uses the builder gem by default. The XML is also passed through ActiveSupport::XmlMini where the addition of the nil="true" attribute is hard coded to always be added for nil attributes.
You should probably look at using builder directly to build your XML if these values are problematic.
Builder::XmlMarkup.new.object{|xml| xml.value "" }
#=> "<object><value></value></object>"
You could also use other XML libraries. I only recommend builder because it is the rails default and likely already installed.
Another option is to convert the object into a Hash first (object.attributes works if object is an ActiveRecord instance). You can then convert any nils into blank strings.
data = object.attributes
data.each_pair{|col, val| data[col] = "" if val.nil? }
data.to_xml
You can add a method to set special default values for XML generation. This method can then be called from an overridden to_xml method which duplicates the record in memory, sets default values and finally generates the xml. Example code:
class Post < ActiveRecord::Base
def set_xml_defaults
blanks = self.attributes.find_all{|k,v| v.nil? }.map{|k,v| [k,''] }
self.attributes = Hash[blanks]
end
alias_method :to_xml_no_defaults, :to_xml
def to_xml(options = {}, &block)
dup = self.dup
dup.set_xml_defaults
dup.to_xml_no_defaults
end
end

Freeze a property in DataMapper Model

Consider I have this following model definition, I want a particular property which should be constant from the moment it has been created
class A
property :a1, String, :freeze => true
end
Is there something like this? or may be using callbacks ?
Try the following:
class YourModel
property :a1, String
def a1=(other)
if a1
raise "A1 is allready bound to a value"
end
attribute_set(:a1, other.dup.freeze)
end
end
The initializer internally delegates to normal attribute writers, so when you initialize the attribute via YourModel.new(:a1 => "Your value") you cannot change it with your_instance.a1 = "your value".. But when you create a fresh instance. instance = YourModel.new you can assign once instance.a1 = "Your Value".
If you don't need to assign the constant, then
property :a1, String, :writer => :private
before :create do
attribute_set :a1, 'some value available at creation time'
end
may suffice

Bypassing writer accessor by manipulating instead of assigning

I wrote a very simple User class. The instance variable email has a reader accessor and my own writer accessor that validates the email address with a regex.
class User
attr_reader :email
def email=(value)
if (value =~ /^[a-z\d\-\_\+\.]+#([a-z\d\-]+\.)+[a-z]+$/)
#email = value
else
# bonus question: is ArgumentError the right error type to use here?
raise ArgumentError, "#{value} is not a valid email address."
end
end
end
I wrote the following test:
require 'test/unit'
require_relative '../lib/user'
class TC_UserTest < Test::Unit::TestCase
def setup
#user = User.new()
end
def test_email
# using the writer accessor
#user.email = 'user#example.com'
# bypassing the writer accessor. evil.
#user.email[4] = '#'
assert_equal('user#example.com', #user.email)
end
end
By using the reference given to me by the reader accessor, I am able to manipulate the email instance variable without going through the writer accessor.
The same principe would apply to any data type that allows manipulation without outright assigning a new value with =
Am I being overzealous? I just want to write robust code. Is there a way to ensure that my email address can only be set using the writer accessor?
I'm new to the language and I'm trying to get a feel for the best practices.
An option to make the test pass (and protect the #email variable) is to expose a duplicate.
def email
#email.dup
end
To do what you're trying to do, my advice is to move the regexp into its own validation method.
Better still, don't write an email regexp unless you really want to do it right.
Use a gem instead: https://github.com/SixArm/sixarm_ruby_email_address_validation
After you set the email, freeze it with http://ruby-doc.org/core-1.9.3/Object.html#method-i-freeze
Bonus answer: yes, ArgumentError is the right error type in general. If you're using Rails, consider using the Rails validation methods.
You can freeze value in writer, that way you'll be able to assign new one via writer, but already assigned would be immutable:
class User
attr_reader :email
def email=(value)
if (value =~ /^[a-z\d\-\_\+\.]+#([a-z\d\-]+\.)+[a-z]+$/)
# make email immutable:
#email = value.freeze
else
# bonus question: is ArgumentError the right error type to use here?
raise ArgumentError, "#{value} is not a valid email address."
end
end
end

Custom validation not triggering b/c my ruby isn't right

This is a painfully noob question, but I have to ask it. I want validation to trip if a particular field, let's call it :token isn't a particular string. So, I call my custom validation:
validate :use_beta_token
And then I define my validation method
def use_beta_token
errors.add(:token, "Incorrect beta token") if token not 'pizza'
end
Whenever I set the token to a string that isn't "pizza", and I test with valid? it's coming back true. What am I messing up here? I've also tried if token !== 'pizza', but that's not working either. I'm sure the answer is painfully obvious, but I can't seem to dig it up.
try
errors.add(:token, "Incorrect beta token") unless token == 'pizza'
the not method works like !, it's a unary boolean operator rather than a binary comparison operator.
as for how to write them, keep it concise. See the rails guide for examples.
One way to use custom validators for Rails 3 is to define your own Validator class that inherits from ActiveModel::Validator then implement the validate method and attach errors like so:
# define my validator
class MyValidator < ActiveModel::Validator
# implement the method where the validation logic must reside
def validate(record)
# do my validations on the record and add errors if necessary
record.errors[:token] << "Incorrect beta token" unless token == 'pizza'
end
end
Once you define your validator, you must then include it into your model so it can be used and apply it with the validates_with method.
class ModelName
# include my validator and validate the record
include ActiveModel::Validations
validates_with MyValidator
end

Resources