How to ignore duplicate inserts with datamapper - ruby-on-rails

I have a datamapper model that has a unique index on a property called name. I want to create new records when a name doesn't already exist and silently ignore attempts to create records with duplicate names. What's the "right" way to do this in datamapper?

The best approach is to use the dm-validations gem, and ensure that your name property is specified as being unique, eg:
class Committer
include DataMapper::Resource
# ... other properties ...
property :name, String, :length => 1..100, :required => true, :unique => true
end
The dm-validations gem will introspect your model and automatically setup validations for your properties. In this case it won't allow more than one Committer to have the same name.

I think the best answer is to use first_or_create, which as Dan points out above, is already built into datamapper and therefore doesn't need to be declared.
require 'dm-core'
require 'dm-validations'
class Committer
include DataMapper::Resource
property :id, Serial
property :name, String, :unique_index => true
validates_present :name
validates_is_unique :name
end
committer = "George"
record = Committer.first_or_create(:name => committer)

One solution I came up with is simply to ignore the exception:
begin
Committer.create!(:name => committer)
rescue DataObjects::IntegrityError => e # ignore duplicate inserts
end
If you have a better (more idiomatic) way of doing it, please let me know.

Here is another solution I came up with:
require 'dm-core'
require 'dm-validations'
require 'dm-more'
record = Committer.find_or_create(:name => committer)
If you're using this in sinatra, requiring dm-more seems to cause
other problems. My solution was to require my own file that only
contained the following code:
module DataMapper
module Model
def first_or_create(conditions = {}, attributes = {})
first(conditions) || create(conditions.merge(attributes))
end
alias find_or_create first_or_create
end
end

Related

Don't understand the difference between variable declarations, symbols, methods, instance variables, class variables

Updated this post as per post recommendations:
Inconsistent naming conventions in rails is confusing the heck out of me. IT SEEMS LIKE THE SYNTAX IS ALL OVER THE PLACE IN RAILS...
Here are some examples:
Why are there commas in the migration below ? And why is keyword "default" not with a colon before it? What is this default keyword? a method, or a variable, a symbol.. What is that darn thing??:
add_column :zombies, :rotting, :boolean, default: false
Here is another example:
Why age not :age (with a colon)? WHY IS make_rotting CALLED WITH A " : " BEFORE IT??
class Zombie < ActiveRecord::Base
before_save :make_rotting
def make_rotting
if age > 20
self.rotting = true
end
end
end
I am a Java guy, yes java is verbose but at-least its consistent I feel like going back :(
Ruby and Rails can be very confusing in the beginning I agree.
I think you'll get a better answer if you provide code examples. Because you are a beginner and you might be misunderstanding variable for a method or :value for a key/symbol.
One thing I can help you with though:
variable: :value is actually
key: :value (where :value is a Symbol)
Ruby's new hash syntax which used to be:
:key => :value
first of all there are different type of variable:
1.Local variable
foobar
2.Instance variable
#foobar
3.Class variable
##foobar
4.Global variable
$foobar
You can simply differentiate among them by the way they are used.
Now if talk about property of a model,can declare as
property :foobar, :type => FIXNUM
Now validate and validates both are different
validates :foobar ,:numerically => {:greater_than_or_equal_to => 0}
where validates is use to validate properties.
validate :method_name
where validate is to validate some method

Ruby on Rails and NoSQL, adding fields

I'm just diving into Mongodb and MongoID with Rails and I find it awesome. One thing the NoSQL helps is when I can add extra fields to my model without any extra effort whenever I want:
class Page
include Mongoid::Document
include Mongoid::MultiParameterAttributes
field :title, :type => String
field :body, :type => String
field :excerpt, :type => String #Added later
field :location, :type => String #Added later
field :published_at, :type => Time
validates :title, :presence => true
validates :body, :presence => true
validates :excerpt, :presence => true
end
And this works perfectly as it should. But my question is, (sorry if this is trivial) the existing entries are blank and have no defined value for the newly added field. For example, in a sample blog application, after I've published two posts, I decide to add an excerpt and a location field to my database (refer code above). Any blog post that is published after the addition of these new fields can be made sure to have a value filled in for the excerpt field. But the posts published prior to the addition of these two new fields have null values (which is understandable why) which I cannot validate. Is there an elegant solution for this?
Thank you.
There are three basic options:
Update everything inside MongoDB to include the excerpt.
Use an after_initialize hook to add a default excerpt to existing objects when you pull them out of MongoDB.
Kludge your validation logic to only check for the existence of excerpt on new objects.
(1) requires a (possible large) time hit when you make the change but it is just a one time thing and you don't have to worry about it after that. You'd pull every Page out of MongoDB, do page.excerpt = 'some default excerpt', and then save it back to MongoDB. If you have a lot of Pages you'll want to process them in chunks of, say, 100 at a time. If you do this, you'll be able to search on the excerpt without worrying about what you should do with nulls. You can also do this inside MongoDB by sending a JavaScript fragment into MongoDB:
connection.eval(%q{
db.pages.find({}, { _id: true }).forEach(function(p) {
db.pages.update(
{ _id: p._id },
{ $set: { excerpt: 'some default excerpt' } }
);
});
})
(2) would go something like this:
after_initialize :add_default_excerpt, :unless => :new_record?
#...
private
def add_default_excerpt
self.excerpt = 'some default excerpt' unless self.excerpt.present?
end
You could move the unless self.excerpt up to the :unless if you didn't mind using a lambda:
after_initialize :add_default_excerpt, :unless => ->{ |o| o.new_record? || o.excerpt.present? }
#...
private
def add_default_excerpt
self.excerpt = 'some default excerpt'
end
This should be pretty quick and easy to set up but there are downsides. First of all, you'd have a bunch of nulls in your MongoDB that you might have to treat specially during searches. Also, you'd be carrying around a bunch of code and logic to deal with old data but this baggage will be used less and less over time. Furthermore, the after_initialize calls do not come for free.
(3) requires you to skip validating the presence of the excerpt for non-new Pages (:unless => :new_record?) or you'd have to find some way to differentiate new objects from old ones while also properly handling edits of both new and old Pages. You could also force people to supply an excerpt when they change a Page and leave your validation as-is; including a :default => '' on your field :excerpt would take care of any nil issues in views and such.
I'd go with (1) if possible. If the update would take too long and you wanted the site up and running while you were fixing up MongoDB, you could add a :default => '' while updating and then remove the :default option, restart, and manually patch up any strays that got through.

How to restrict values for a column

I want to restrict available values for a field. So the value of the column must be from specified set of values. Is it possible using migration/models? Or I have to do it manually in my DB?
You'll use validations for this. There's a whole Rails guide on the topic. The specific helper you're looking for in this case is :inclusion, e.g.:
class Person < ActiveRecord::Base
validates :relationship_status,
:inclusion => { :in => [ 'Single', 'Married', 'Divorced', 'Other' ],
:message => "%{value} is not a valid relationship status" }
end
Edit Aug. 2015: As of Rails 4.1, you can use the enum class method for this. It requires that your column be an integer type:
class Person < ActiveRecord::Base
enum relationship_status: [ :single, :married, :divorced, :other ]
end
It automatically defines some handy methods for you, too:
p = Person.new(relationship_status: :married)
p.married? # => true
p.single? # => false
p.single!
p.single? # => true
You can read the documentation for enum here: http://api.rubyonrails.org/v4.1.0/classes/ActiveRecord/Enum.html
It depends on the amount of confidence you need. You could just add a validator to your model to restrict it to those values but then you wont be sure that existing data will match (and will cause subsequent saves to fail because of validation) and also that other changes could be made by other apps/raw sql that would get around it.
If you want absolute confidence, use the database.
Here's what you might want to use if you do it in the database (which is quite limited compared to what a rails validator could do: http://www.w3schools.com/sql/sql_check.asp

How to remove validation using instance_eval clause in Rails?

I would like to enhance existing class using instance_eval. There original definition contains validation, which require presence of certain fields, ie:
class Dummy < ActiveRecord::Base
validates :field, :presence => true
end
Now I want to change that to optional using instance_eval (or any other method, really):
Dummy.instance_eval do
...
end
What would be the proper syntax to remove the validation, so the field is optional. I would rather do this directly on the model layer, instead doing weird hacks in controllers or views. The use of instance_eval is not really required, but as far as I know, this is generally the best way to enhance classes in Rails.
Edit #1
In general - the original class is part of the gem and I don't want to fork it, nor tie to specific release. The general cause is not really important. Simply editing the original model has far worse consequences than monkey patching.
I found a solution, not sure how solid it is, but it works well in my case. #aVenger was actually close with his answer. It's just that the _validators accessor contains only information used for reflection, but not the actual validator callbacks! They are contained in the _validate_callbacks accessor, not to be confused with _validations_callbacks.
Dummy.class_eval do
_validators.reject!{ |key, _| key == :field }
_validate_callbacks.reject! do |callback|
callback.raw_filter.attributes == [:field]
end
end
This will remove all validators for :field. If you want to be more precise, you can reject the specific validator for _validators which is the same as the raw_filter accessor of validate callbacks.
I think this is the most actual solution at this moment (I'm using rails 4.1.6):
# Common ninja
class Ninja < ActiveRecord::Base
validates :name, :martial_art, presence: true
end
# Wow! He has no martial skills
Ninja.class_eval do
_validators[:martial_art]
.find { |v| v.is_a? ActiveRecord::Validations::PresenceValidator }
.attributes
.delete(:martial_art)
end
Easest way to remove all validations:
clear_validators!
As I was trying to do this to remove the phone validation from the spree Address model, below is the code I got to work. I added the type check for callback.raw_filter because I only wanted to remove the presence validator on the phone field. I also had to add it because it would fail when trying to run against one of the other validators specified in the Spree::Address model that did not have an 'attributes' key for callback.raw_filter, thus an exception was thrown.
Spree::Address.class_eval do
# Remove the requirement on :phone being present.
_validators.reject!{ |key, _| key == :phone }
_validate_callbacks.each do |callback|
callback.raw_filter.attributes.delete :phone if callback.raw_filter.is_a?(ActiveModel::Validations::PresenceValidator)
end
end
I had a similar problem and was able to get past it using:
class MyModel << Dummy
# erase the validations defined in the plugin/gem because they interfere with our own
Dummy.reset_callbacks(:validate)
...
end
This is under Rails 3.0. The caveat: It does remove ALL validations, so if there are others you want to keep you could try Dummy.skip_callback(...), but I could not figure out the right incantation of arguments to make that work.
One solution is to extend validates :
#no need of instance_eval just open the class
class Dummy < ActiveRecord::Base
#validates :field, :presence => true
def self.validates(*attributes)
if attributes.first == :field #=> add condition on option if necessary
return # don't validate
else
super(*attributes) #let normal behavior take over
end
end
end
And no that's not monkey-patching but extending or decorating a behavior. Rails 3.1 is built on the idea of "multi- inheritance" with module inclusion, specifically to allow this kind agility.
update #2
One caveat is you must load the class with the redefined validates method before the gem containing the call to validates. To do so, require the file in config/application.rb after require "rails/all" as suggested in the railsguides. Something like that :
require File.expand_path('../boot', __FILE__)
require 'rails/all' # this where rails (including active_record) is loaded
require File.expand_path('../dummy' __FILE__) #or wherever you want it
#this is where the gems are loaded...
# the most important is that active_record is loaded before dummy but...
# not after the gem containing the call to validate :field
if defined?(Bundler)
Bundler.require *Rails.groups(:assets => %w(development test))
end
Hope it works now!
Answer by aVenger has problems when you declare validations of more than one attribute in a line:
validates :name, :message, :presence => true
That's because this line creates a raw_filter with more than one attribute in attributes filter:
Model.send(:_validate_callbacks)
=> [#<ActiveSupport::Callbacks::Callback:0xa350da4 #klass=Model(...), ... , #raw_filter=#<ActiveModel::Validations::PresenceValidator:0x9da7470 #attributes=[:name, :message], #options={}>, #filter="_callback_before_75", #compiled_options="true", #callback_id=76>]
We have to delete the desired attribute from that array and reject the callbacks without attributes
Dummy.class_eval do
_validators.reject!{ |key, _| key == :field }
_validate_callbacks.each do |callback|
callback.raw_filter.attributes.delete :field
end
_validate_callbacks.reject! do |callback|
callback.raw_filter.attributes.empty? ||
callback.raw_filter.attributes == [:field]
end
end
I have this working on a Rails 3.2.11 app.
For rails 4.2 (~ 5.0) it can be used the following module with a method:
module ValidationCancel
def cancel_validates *attributes
attributes.select {|v| Symbol === v }.each do |attr|
self._validators.delete( attr )
self._validate_callbacks.select do |callback|
callback.raw_filter.try( :attributes ) == [ attr ] ;end
.each do |vc|
self._validate_callbacks.delete( vc ) ;end ;end ;end ;end
Note: Since the filtern can be a symbol of an association, or a specific validator, so we have to use #try.
Then we can use rails-friendly form in a class declaration:
class Dummy
extend ValidationCancel
cancel_validates :field ;end
Note: since removal of the validator is affecting to the whole class and its descendants globally, it is not recommended to use it to remove validations in such way, instead add if clause for the specific rule as follows:
module ValidationCancel
def cancel_validates *attributes
this = self
attributes.select {|v| Symbol === v }.each do |attr|
self._validate_callbacks.select do |callback|
callback.raw_filter.try( :attributes ) == [ attr ] ;end
.each do |vc|
ifs = vc.instance_variable_get( :#if )
ifs << proc { ! self.is_a?( this ) } ;end ;end ;end ;end
This restricts execution of the validation callback for the specified class and its descendants.
If you doesn't want to make any changes in Parent class then first clear all validations in child class and copy all required validation from parent class to child class
class Dummy < ActiveRecord::Base
validates :property, presence: true
validates :value, length: { maximum: 255 }
end
And override it in child class
Dummy.class_eval do
clear_validators!
validates :property, presence: true
end
If you really want to do this then here would be a good place to start digging: https://github.com/rails/rails/blob/ed7614aa7de2eaeba16c9af11cf09b4fd7ed6819/activemodel/lib/active_model/validations/validates.rb#L82
However, to be honest, inside of ActiveModel is not where I'd be poking with a stick.
If you can edit the constraint on the original model to put an :if => :some_function on it, you can easily change the behavior of the function it calls to return false. I tested this and it works pretty easily:
class Foo < ActiveRecord::Base
validates :field, :presence => true, :if => :stuff
attr_accessor :field
def stuff
return true;
end
end
and then somewhere else:
Foo.class_eval {
def stuff
false
end
}
Why not use #dummy.save_without_validation method to skip validations altogether? I prefer do something like this:
if #dummy.valid?
#dummy.save # no problem saving a valid record
else
if #dummy.errors.size == 1 and #dummy.errors.on(:field)
# skip validations b/c we have exactly one error and it is the validation that we want to skip
#dummy.save_without_validation
end
end
You could put this code in your model or in the controller, depending on your needs.
In Rails 4.1,
I was able to do _validate_callbacks.clear. In my case, I wanted all the validations for a gem removed, so I could create my own. I did this in a module that was patched into the class.
Module #Name
extend ActiveSupport::Concern
included do
_validate_callbacks.clear
#add your own validations now
end
end
Wanted to add that, if you're trying to clear validations on a instance of your Model (not the entire model class), don't do my_dummy._validate_callbacks.clear, as that will clear validations on every instance (and future instance) of your Dummy model class.
For just the instance (and if you wanted to reinstate the validations later), try the following:
Create a copy of the validate callbacks (if you want to reinstate later):
my_dummy_validate_callbacks = my_dummy._validate_callbacks.clone
Set the validate callbacks on your instance to empty:
my_dummy._validate_callbacks = {}
Do what you want on my_dummy validation free!
Reinstate the callbacks: my_dummy._validate_callbacks = my_dummy_validate_callbacks
I'd have to look more into the code and help, but I'm thining it might be possible to inspect the list of validators of the class, and then modify the entry for the validation you want to change to add in an :if => :some_function conditional to it.
You'll need to do it only once for production (so it can be put inside an initializer, but for development you'll need to put it in the model, or somewhere else that will get loaded each time the corresponding model is (perhaps an observer?).
(I'll edit the answer with more information as I come to research it.)
Every Rails validator, pre-defined or custom, is an object, and is expected to respond to #validate(record) method. You can monkey patch or stub this method.
# MyModel.validators_on(:attr1, :attr2, ...) is also useful
validator = MyModel.validators.detect do |v|
validator_i_am_looking_for?(v)
end
def validator.validate(*_)
true
end
# In RSpec you can also consider:
allow(validator).to receive(:validate).and_return(true)
Tested in Rails 5.1.
Don't do this unless you understand what you're doing ;)
This does not directly answer the question but here's an option you should consider in such a situation: instead of disabling validation, you could set the required fields in a before_validation hook.
Since you don't need those required fields, set them with some dummy data that satisfies the validation and forget about them.
No ugly monkey patching.
Assuming the original implementation of Dummy is defined in an engine there is a nasty hack that will do what you want. Define Dummy in your application to keep the original implementation of Dummy from being auto-loaded. Then load the source to Dummy and remove the line that does the validation. Eval the modified source.
Put the following in your app/models/dummy.rb
class Dummy < ActiveRecord::Base
end
# Replace DummyPlugin with name of engine
engine = Rails::Application::Railties.engines.find { |e| e.class == DummyPlugin::Engine }
dummy_source = File.read File.join(engine.config.root, "app", "models", "dummy.rb")
dummy_source = dummy_source.gsub(/validates :field, :presence => true.*/, "")
eval dummy_source
If it is regular gem instead of an engine the same concept would apply, just would need to load the source for Dummy from the gem root instead of the engine root.

Rails: Ignoring non-existant attributes passed to create()

I have the following Rails model:
class CreateFoo < ActiveRecord::Migration
def self.up
create_table :foo do |t|
t.string :a
t.string :b
t.string :c
t.timestamps
end
end
def self.down
drop_table :foo
end
end
If I try and create a new record with an additional non-existent attribute, this produces an error:
Foo.create(a: 'some', b: 'string', c: 'foo', d: 'bar')
ActiveRecord::UnknownAttributeError: unknown attribute: d
Is there a way I can get create() to ignore attributes that don't exist in the model? Alternatively, what is the best way to remove non-existent attributes prior to creating the new record?
Many thanks
Trying to think of a potentially more efficient way, but for now:
hash = { :a => 'some', :b => 'string', :c => 'foo', :d => 'bar' }
#something = Something.new
#something.attributes = hash.reject{|k,v| !#something.attributes.keys.member?(k.to_s) }
#something.save
I use this frequently (simplified):
params.select!{|x| Model.attribute_names.index(x)}
Model.update_attributes(params)
I just had this exact problem upgrading to Rails 3.2, when I set:
config.active_record.mass_assignment_sanitizer = :strict
It caused some of my create! calls to fail, since fields that were previously ignored are now causing mass assignment errors. I worked around it by faking the fields in the model as follows:
attr_accessor :field_to_exclude
attr_accessible :field_to_exclude
Re: Is there a way I can get create() to ignore attributes that don't exist in the model? -- No, and this is by design.
You can create an attr_setter that will be used by create --
attr_setter :a # will silently absorb additional parameter 'a' from the form.
Re: Alternatively, what is the best way to remove non-existent attributes prior to creating the new record?
You can remove them explicitly:
params[:Foo].delete(:a) # delete the extra param :a
But the best is to not put them there in the first place. Modify your form to omit them.
Added:
Given the updated info (incoming data), I think I'd create a new hash:
incoming_data_array.each{|rec|
Foo.create {:a => rec['a'], :b => rec['b'], :c => rec['c']} # create new
# rec from specific
# fields
}
Added more
# Another way:
keepers = ['a', 'b', 'c'] # fields used by the Foo class.
incoming_data_array.each{|rec|
Foo.create rec.delete_if{|key, value| !keepers.include?(key)} # create new rec
} # from kept
# fields
You can use Hash#slice and column_names method exists also as class method.
hash = {a: 'some', b: 'string', c: 'foo', d: 'bar'}
Foo.create(hash.slice(*Foo.column_names.map(&:to_sym)))
I came up with a solution that looks like this, you might find it helpful:
def self.create_from_hash(hash)
hash.select! {|k, v| self.column_names.include? k }
self.create(hash)
end
This was an ideal solution for me, because in my case hash was coming from an ideal data source which mirrored my schema (except there were additional fields).
I think using the attr_accessible method in the model class for Foo would achieve what you want, e.g.,:
class Foo < ActiveRecord::Base
attr_accessible :a, :b, :c
...
end
This would allow the setting/updating of only those attributes listed with attr_accessible.
I found a solution that works fairly well, and is ultimately a combination of the above options. It allows for invalid params to be passed (and ignored), while valid ones are mapped correctly to the object.
def self.initialize(params={})
User.new(params.reject { |k| !User.attribute_method?(k) })
end
Now rather than calling User.new(), call User.initialize(). This will "filter" the correct params fairly elegantly.

Resources