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
Related
I would like to write a custom validator for a given validates call:
class Worker
include ActiveModel::Validations
def initialize(graph_object)
#graph_object = graph_object
end
attr_accessor :graph_object
validates :graph_object, graph_object_type: {inclusion: [:ready, :active]}
end
class GraphObject
attr_accessor :state
end
I would like to validate Worker#graph_object based on a GraphObject#state. So the Worker is valid when the passed in GrapObject is in a :ready or :active state. I would like to reuse as much of the ActiveModel as possible.
Validations documentation describes the process of setting up the custom validator but I can't figure out how to do it.
I think I have to start with this:
class GraphObjectTypeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
end
end
options[:inclusion] = [:ready, :active]
record is the instance of the Worker(i think...)
value I have no idea (is value = record.graph_object ?)
attribute same as for value - no idea
Maybe validates :graph_object, graph_object_type: {inclusion: [:ready, :active]} isn't defined right?
OK I think I figured it out - I love puts debugging! Who needs pry!
One way of doing it would be:
class GraphObjectTypeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if options.key?(:inclusion) && not_included?(value.type)
record.errors.add(attribute, "wrong graph object type")
end
end
private
def not_included?(type)
!options[:inclusion].include?(type)
end
end
options[:inclusion]: [:ready, :active] array
record: instance of the Worker
value: instance of the GraphObject
attribute: :graph_object symbol
I'm using ActiveRecord 4.1.8 in a Ruby (not Rails) application. I have a table and a corresponding model that looks like the following:
create_table 'people', :force => true do |t|
t.string 'name'
end
class Person < ActiveRecord::Base
def name=(name)
puts "Attribute setter for name called with #{name}"
write_attribute(:name, name)
end
end
When I create a new instance of Person, I see the Attribute setter for name called with... written to STDOUT. However, when I reload the model instance, I do not see the message written to STDOUT.
p = Person.create(name: 'foobar')
--> Attribute setter for name called with foobar
p.reload
--> <nothing>
The model is getting persisted to the database, so this makes me think name= isn't used when data is loaded into a model from the database. I need to modify certain data attributes when they're read in from the database, so does anyone know what other method I need to override?
From the active_record/persistence.rb source:
def reload(options = nil)
clear_aggregation_cache
clear_association_cache
fresh_object =
if options && options[:lock]
self.class.unscoped { self.class.lock(options[:lock]).find(id) }
else
self.class.unscoped { self.class.find(id) }
end
#attributes = fresh_object.instance_variable_get('#attributes')
#new_record = false
self
end
It just replaces the attributes hash directly. Seems like the easiest way to handle this is to override reload and patch things up after its called.
I would like to be able to define a lightweight resource with let's say 3 parameters, two of them being basic/elementary parameters and the third being a combination of these two. I would also like to provide a possibility of customization of the third parameter. For example:
How to modify following code to achieve above behaviour for the full_name attribute:
resource definition:
actions :install
attribute :name, :kind_of => String, :name_attribute => true
attribute :version, :kind_of => String
attribute :full_name, :kind_of => String
provider definition:
action :install do
Chef::Log.info "#{new_resource.full_name}"
end
I would like to see different outputs for different resource directives, e.g.:
resource "abc" do
version "1.0.1"
end
will result in abc-1.0.1, but:
resource "def" do
version "0.1.3"
full_name "completely_irrelevant"
end
will result in completely_irrelevant.
Is there a possibility to define this behaviour in the resource definition (probably through the default parameter) or I am able to do it in provider definition only? If the second is true, then can I store the calculated value in the new_resource object's full_name attribute (the class seems to miss the full_name= method definition) or I have to store it in a local variable?
Update
Thanks to Draco's hint, I realized that I can create an accessor method in the resource file and calculate the full_name value on the fly when requested. I would prefer a cleaner solution but it's much better than calculating it in action implementation.
Chef version
Chef: 10.16.4
Setting #full_name in constructor, similar to providing default action in chef < 0.10.10, as written in wiki, does not work, because #version is not set at that point yet.
def initialize( name, run_context=nil )
super
#full_name ||= "%s-%s" % [name, version]
end
So we have to overwrite full_name method in resource by adding
def full_name( arg=nil )
if arg.nil? and #full_name.nil?
"%s-%s" % [name, version]
else
set_or_return( :full_name, arg, :kind_of => String )
end
end
into resource definition. That works. Tested.
attribute :full_name, :kind_of => String, default => lazy {|r| "#{r.name}-#{r.version}" }
After fighting this for some time, I found this to work cleanly.
attribute :eman, String, default: lazy {|r| r.name.reverse }
The part that was missing for me was the |r| parameter to the lazy block.
https://docs.chef.io/resource_common.html#lazy-evaluation
I have a SchoolDay class that represents a school day: it can tell you the date, the semester, the term, the week, and the day. It can generate a string like "Sem1 13A Fri". To store these objects in the database, I want them serialized as a string.
Here is my DataMapper custom type code. I've sort of scraped ideas from the code in dm-types because (disappointingly) there is no real documentation for creating custom types. Sorry it's long.
module DataMapper
class Property
class SchoolDay < DataMapper::Property::String
#load_as ::SchoolRecord::DomainObjects::SchoolDay
# Commented out: the 'load_as' method is not found
def load(value)
# Take a string from the database and load it. We need a calendar!
val = case value
when ::String then calendar.schoolday(value)
when ::SR::DO::SchoolDay then value
else
# fail
end
end
def dump(value)
# Store a SchoolDay value into the database as a string.
case value
when SR::DO::SchoolDay
sd = value
"Sem#{sd.semester} #{sd.weekstr} #{sd.day}"
when ::String
value
else
# fail
end
end
def typecast(value)
# I don't know what this is supposed to do -- that is, when and why it
# is called -- but I am aping the behaviour of the Regexp custom type,
# which, like this one, stores as a String and loads as something else.
load(value)
end
# private methods calendar() and error_message() omitted
end
end
end
This code works for reading from the (SQLite) database, but not for creating new rows. The error message is:
Schoolday must be of type String
The code that defines the DataMapper resource and tries to create the record is:
class LessonDescription
include DataMapper::Resource
property :id, Serial
property :schoolday, SchoolDay # "Sem1 3A Fri"
property :class_label, String # "10"
property :period, Integer # (0..6), 0 being before school
property :description, Text # "Completed yesterday's worksheet. hw:(4-07)"
end
# ...
ld = LessonDescription.create(
schoolday: #schoolday,
class_label: #class_label,
period: #period,
description: description
)
Here is the code for the Regexp datamapper type in the dm-types library. It's so simple!
module DataMapper
class Property
class Regexp < String
load_as ::Regexp # NOTE THIS LINE
def load(value)
::Regexp.new(value) unless value.nil?
end
def dump(value)
value.source unless value.nil?
end
def typecast(value)
load(value)
end
end
end
end
For some reason, I cannot use the load_as line in my code.
To summarise: I am trying to create a custom type that translates between a SchoolDay (domain object) and a String (database representation). The translation is easy, and I've copied the code structure primarily from the DataMapper Regexp type. But when I try to save a SchoolDay, it complains that I'm not giving it a string. Frustratingly, I can't use the "load_as" method that the built-in and custom types all use, even though I have the latest gem. I can't find the "load_as" method defined anywhere in the source code for DataMapper, either. But it's called!
Sorry for the ridiculous length. Any help would be greatly appreciated, as would a pointer to a guide for creating these things that I have somehow missed.
It seems that the current code of dm-types at github hasn't made it to any official release -- that's why load_as doesn't work in your example. But try to add this method:
module DataMapper
class Property
class SchoolDay < DataMapper::Property::String
def custom?
true
end
end
end
end
That's working here.
class Order
include Datamapper::Resource
property :birthday_day, String
property :birthday_month, String
property :birthday_year, String
property :birthday, Date
before :save do
#birthday = Date.new(#birthday_year.to_i, #birthday_month.to_i, #birthday_day.to_i)
end
end
It's a part of model, but it's enouth.
When save field (from irb or from sinatra application) :birthday not save in DB. But in irb, i see object, where :birthday exist and it Date format.
When change field manual (from irb):
f.birthday = Date.new
f.save
In object and in DB result appear (in obj as Date obj, in DB as "2010-2-3")
Help me please to understand, what wrong with before in model.
Sorry for my not good enlish.
You should use property mutator method. Setting the ivar doesn't trigger dirty tracking. Just do this:
self.birthday = Date.new(birthday_year.to_i, birthday_month.to_i, birthday_day.to_i)
Also it would be better to use Integer as the property type for year, month and day.
DataMapper documentation recommends #attribute_set
Sets the value of the attribute and marks the attribute as dirty if it has been changed so that it may be saved. Do not set from instance variables directly, but use this method.
In your case:
before :save do
set_attribute(:birthday => Date.new(self.birthday_year.to_i, self.birthday_month.to_i, self.birthday_day.to_i))
end
For what it's worth, unless I needed to use all the fields in SELECT criteria, I would save either the integer fields or the date, not both:
class Order
include Datamapper::Resource
property :birthday, Date
end
# change month
o = Order.create(:birthday => Date.new(...))
o.update(:birthday => Date.new(o.birthday.year, new_month, o.birthday.mday))
Or
class Order
include Datamapper::Resource
property :birthday_day, String
property :birthday_month, String
property :birthday_year, String
def birthday
if self.birthday_day && self.birthday_month && self.birthday_year
Date.new(self.birthday_day, ...)
end
end
end