Destroying "has n, :things, :through => Resource" associations in DataMapper - ruby

I have a collection class called MySet:
class MySet
include DataMapper::Resource
property :id, Serial
has n, :my_elements, :through => Resource
def add integer
unless my_elements.first(:integer => integer)
my_element = MyElement.create :integer => integer
my_elements << my_element
my_elements.save
end
self
end
def has_integer? integer
!my_elements.first(:integer => integer).nil?
end
def delete integer
if has_integer? integer
my_elements.first(:integer => integer).destroy
my_elements.save
end
self
end
def size
my_elements.size
end
end
and an element class called MyElement:
class MyElement
include DataMapper::Resource
property :id, Serial
property :integer, Integer
end
I want to be able to add and delete elements to and from MySet. However, the following spec:
describe MySet do
subject do
MySet.create
end
it "adds and deletes" do
subject.add 1
subject.delete 1
subject.size.should == 0
end
end
fails with:
Failure/Error: subject.size.should == 0
expected: 0
got: 1 (using ==)
This is similar to the problem described in DataMapper has n through Resource DELETE (Remove from association) not working except that MyElement does not specify an association with MySet. However, I think the solution in that post is what I am using and it does not appear to work.

Take a look at the Collection documentation:
http://rubydoc.info/github/datamapper/dm-core/master/DataMapper/Collection
http://rubydoc.info/github/datamapper/dm-core/master/DataMapper/Associations/ManyToMany/Collection
I think some of the things you're doing are extraneous, and MyElement.create should probably be MyElement.first_or_create but more importantly, the part where you destroy one of the MyElement's is not quite right. It seems like what you're trying to do is remove it from the "my_elements" collection, so you should find it, and then "delete" it from the collection:
def delete integer
element = my_elements.first(:integer => integer)
my_elements.delete(element)
self
end
(destroying it may also be part of what you're trying to do, and you can do that separately).
Just destroying it like you were doing may actually work (I'm not sure), but you'd probably have to reload the resource in your test to see the updated size:
subject.reload
subject.size.should == 0

Related

Rails 3 serialized model field form_for and field_for not generating correct name

I have this model:
class CompanyCrawler < ActiveRecord::Base
....
serialize :entry_pages, Array
def entry_page_objects
entry_pages.map { |url| EntryPage.new(url) }
end
def entry_page_objects_attributes=(attributes)
# ...
end
....
end
This form to render the model:
.....
%p
%p
= crawler_form.label 'Entry pages'
= crawler_form.text_area :entry_pages_text, size: '80x6'
%ul.entry-pages
= crawler_form.fields_for :entry_page_objects do |entry_page_field|
%li=entry_page_field.text_field :url, size: 80
%a{href: '#', class: 'add-button'} Add Entry Page
The problem I have is that the form renders the entry_page_object input names incorrectly(e.g. company_crawler[entry_page_objects_attributes][0][url] instead of company_crawler[entry_page_objects][0][url]). I am really not sure what to do, I have read the documentation and the example says that just by defining attr_attributes=(attributes) and persisted? I will be able to use fields_for for collections just if they were associations defined with accept_nested_fields.
I have seen different solutions like just giving String 'entry_page_objects[]' to fields_for but I want to be consistent with rails naming convention and I know I can use form_tag instead of form_for but I want to make fields_for work as expected.
Here is some information for all that have not understood properly how nested_attributes works, like me.
What I have reported as issue is actually how it is supposed to work. When we have, let say, this model:
class Foo < ActiveRecord::Base # it has name attribute
has_many :larodis
accepts_nested_attributes_for :larodi
end
class Larodi < ActiveRecord::Base # it has name attribute
belongs_to :foo
end
This definition gives me the possibility to create Foo with many Larodi's just by giving a hash of parameters. For example:
x = Foo.create(name: 'Josh', larodi_attributes: [ {name: 'Wayne'} ]
x.larodis.map(&:name) # ['Wayne']
Now comes the part where #field_for understands if we have nested attribute to work with. We check this by looking for name_attributes= method. If it is defined #fields_for generates form of the type <input ... name=object[name][INDEX][method]>... where index is just an integer.
Keep in mind that when implementing custom name_attibutes(attributes) you must check attributes type - it can be Array like the example, it can be Hash of this type:
{ 1 => { ... } , 2 => { ... } }
Just like a hash representing array, where the key is index and value is the value for this index.
The answear looks like this:
_form.html.haml
....
= crawler_form.fields_for :entry_pages do |entry_page_field|
%li
=entry_page_field.text_field :url, size: 80
...
company_crawler.rb
class CompanyCrawler < ActiveRecord::Base
....
serialize :entry_pages, Array
def entry_pages_attributes=(attributes)
self.entry_pages = attributes_collection(attributes).map do |attribute|
EntryPage.new(attribute[:url])
end
end
def entry_pages=(entry_pages)
entry_pages = entry_pages.map do |entry_page|
cast_entry_page_to_entry_page_object(entry_page)
end
write_attribute(:entry_pages, entry_pages)
end
...
private
def attributes_collection(attributes)
case attributes
when Array
attributes
when Hash
attributes.values
end
end
def cast_entry_page_to_entry_page_object(entry_page)
case entry_page
when String
EntryPage.new(entry_page)
when EntryPage
entry_page
end
end
end
For clarity I have removed entry_page_objects and use only entry_pages.

FactoryGirl in Rails - Associations w/ Unique Constraints

This question is an extension to the one raised here:
Using factory_girl in Rails with associations that have unique constraints. Getting duplicate errors
The answer offered has worked perfectly for me. Here's what it looks like:
# Creates a class variable for factories that should be only created once.
module FactoryGirl
class Singleton
##singletons = {}
def self.execute(factory_key)
begin
##singletons[factory_key] = FactoryGirl.create(factory_key)
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
# already in DB so return nil
end
##singletons[factory_key]
end
end
end
The issue that has come up for me is when I need to manually build an association to support a polymorphic association with a uniqueness constraint in a hook. For example:
class Matchup < ActiveRecord::Base
belongs_to :event
belongs_to :matchupable, :polymorphic => true
validates :event_id, :uniqueness => { :scope => [:matchupable_id, :matchupable_type] }
end
class BaseballMatchup < ActiveRecord::Base
has_one :matchup, :as => :matchupable
end
FactoryGirl.define do
factory :matchup do
event { FactoryGirl::Singleton.execute(:event) }
matchupable { FactoryGirl::Singleton.execute(:baseball_matchup) }
home_team_record '10-5'
away_team_record '9-6'
end
factory :baseball_matchup do
home_pitcher 'Joe Bloe'
home_pitcher_record '21-0'
home_pitcher_era 1.92
home_pitcher_arm 'R'
away_pitcher 'Jack John'
away_pitcher_record '0-21'
away_pitcher_era 9.92
away_pitcher_arm 'R'
after_build do |bm|
bm.matchup = Factory.create(:matchup, :matchupable => bm)
end
end
end
My current singleton implementation doesn't support calling FactoryGirl::Singleton.execute(:matchup, :matchupable => bm), only FactoryGirl::Singleton.execute(:matchup).
How would you recommend modifying the singleton factory to support a call such as FactoryGirl::Singleton.execute(:matchup, :matchupable => bm) OR FactoryGirl::Singleton.execute(:matchup)?
Because right now, the above code will throw uniqueness validation error ("Event is already taken") everytime the hook is run on factory :baseball_matchup. Ultimately, this is what needs to be fixed so that there isn't more than one matchup or baseball_matchup in the DB.
As zetetic has mentioned, you can define a second parameter on your execute function to send the attributes to be used during the call to FactoryGirl.create, with a default value of an empty hash so it didn't override any of them in the case you don't use it (you don't need to check in this particular case if the attributes hash is empty).
Also notice that you don't need to define a begin..end block in this case, because there isn't anything to be done after your rescue, so you can simplify your method by defining the rescue as part of the method definition. The assignation on the case that the initialization was fine will also return the assigned value, so there is no need to explicitly access the hash again to return it. With all these changes, the code will end like:
# Creates a class variable for factories that should be only created once.
module FactoryGirl
class Singleton
##singletons = {}
def self.execute(factory_key, attrs = {})
##singletons[factory_key] = FactoryGirl.create(factory_key, attrs)
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
# already in DB so return nil
end
end
end
You need to do two things to make this work:
Accept attributes as an argument your execute method.
Key off of both the factory name and the attributes when creating the singleton factories.
Note that step 1 isn't sufficient to solve your problem. Even if you allow execute to accept attributes, the first call to execute(:matchup, attributes) will cache that result and return it any time you execute(:matchup), even if you attempt to pass different attributes to execute. That's why you also need to change what you're using as the hash key for your ##singletons hash.
Here's an implementation I tested out:
module FactoryGirl
class Singleton
##singletons = {}
def self.execute(factory_key, attributes = {})
# form a unique key for this factory and set of attributes
key = [factory_key.to_s, '?', attributes.to_query].join
begin
##singletons[key] = FactoryGirl.create(factory_key, attributes)
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
# already in DB so return nil
end
##singletons[key]
end
end
end
The key is a string consisting of the factory name and a query string representation of the attributes hash (something like "matchup?event=6&matchupable=2"). I was able to create multiple different matchups with different attributes, but it respected the uniqueness of the event/matchupable combination.
> e = FactoryGirl.create(:event)
> bm = FactoryGirl.create(:baseball_matchup)
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm)
> m.id
2
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm)
> m.id
2
> f = FactoryGirl.create(:event)
> m = FactoryGirl::Singleton.execute(:matchup, :event => f, :matchupable => bm)
> m.id
3
Let me know if that doesn't work for you.
Ruby methods can have default values for arguments, so define your singleton method with an empty default options hash:
def self.execute(factory_key, options={})
Now you can call it both ways:
FactoryGirl::Singleton.execute(:matchup)
FactoryGirl::Singleton.execute(:matchup, :matchupable => bm)
within the method, test the options argument hash to see if anything hase been passed in:
if options.empty?
# no options specified
else
# options were specified
end

DataMapper - create new value for relational DB

I have a relational DB defined as follows. How can I enter a new value, where B belongs to A. The code given below doesn't seem to work.
Thanks
class A
include DataMapper::Resource
property :id, Serial, :key => true
property :name, String
belongs_to :b
end
class B
include DataMapper::Resource
property :id, Serial, :key => true
property :name, String
has n, :as
end
Create new value
# Create new value
post '/create' do
a = A.new
b = B.new
b.attributes = params
b.belongs_to = a #problem is here
b.save
redirect("/info/#{a.id}")
end
#belongs_to is a model (class) method and you use it to declare ManyToOne relationship.
In your example you should use "<<" method like this:
b.as << a
That will add "a" instance to "as" collection and associate both resources.
[...] How can I enter a new value, where B belongs to A. The code given below doesn't seem to work.
Your code implies you're after A belonging to B, but your question is the reverse so I'll show how to do that, i.e., B belongs to A.
class A
include DataMapper::Resource
property :id, Serial, :key => true
property :name, String
has n, :bs # A has many B's
end
class B
include DataMapper::Resource
property :id, Serial, :key => true
property :name, String
belongs_to :a, :required => false # B has only 1 A
end
Note your has and belongs_to are reversed here. I also added required => false to the belongs_to side because DataMapper will silently refuse to save your model if ever don't have b.a before calling saveā€”once you're comfortable with it you can remove the required false if you desire.
Here are two ways you can use that model:
# Create new value
post '/create' do
a = A.new
a.save
b = B.new
b.attributes = params
b.a = a
b.save
redirect("/info/#{a.id}")
end
This example is generally the same as yours, but I added a save call for A. Note this may not be necessary, I'm not in a good place to test this particular case; in the past I've found DataMapper will save some related objects automatically but not others so I've developed the habit of always saving explicitly to prevent confusion.
# Create new value
post '/create' do
a = A.create
b = a.bs.create(params)
redirect("/info/#{a.id}")
end
In the second example I call create on the many-side of the relationship, this makes a new B, associates it with "a", sets the params given, and saves it immediately. The result is the same as the previous example.
If you're just getting familiar with DataMapper, you may find it helpful to add the following to your app:
DataMapper::Model.raise_on_save_failure = true
This will cause DataMapper to give you errors and backtraces in cases like the above, more info here.

Rails3: Nested model - child validates_with method results in "NameError - uninitialized constant [parent]::[child]"

Consider the following parent/child relationship where Parent is 1..n with Kids (only the relevant stuff here)...
class Parent < ActiveRecord::Base
# !EDIT! - was missing this require originally -- was the root cause!
require "Kid"
has_many :kids, :dependent => :destroy, :validate => true
accepts_nested_attributes_for :kids
validates_associated :kids
end
class Kid < ActiveRecord::Base
belongs_to :parent
# for simplicity, assume a single field: #item
validates_presence_of :item, :message => "is expected"
end
The validates_presence_of methods on the Kid model works as expected on validation failure, generating a final string of Item is expected per the custom message attribute supplied.
But if try validates_with, instead...
class Kid < ActiveRecord::Base
belongs_to :parent
validates_with TrivialValidator
end
class TrivialValidator
def validate
if record.item != "good"
record.errors[:base] << "Bad item!"
end
end
end
...Rails returns a NameError - uninitialized constant Parent::Kid error following not only an attempt to create (initial persist) user data, but also when even attempting to build the initial form. Relevant bits from the controller:
def new
#parent = Parent.new
#parent.kids.new # NameError, validates_* methods called within
end
def create
#parent = Parent.new(params[:parent])
#parent.save # NameError, validates_* methods called within
end
The error suggests that somewhere during model name (and perhaps field name?) resolution for error message construction, something has run afoul. But why would it happen for some validates_* methods and not others?
Anybody else hit a wall with this? Is there some ceremony needed here that I've left out in order to make this work, particularly regarding model names?
After a few hours away, and returning fresh -- Was missing require "Kid" in Parent class. Will edit.

How to extend DataMapper::Resource with custom method

I have following code:
module DataMapper
module Resource
##page_size = 25
attr_accessor :current_page
attr_accessor :next_page
attr_accessor :prev_page
def first_page?
#prev_page
end
def last_page?
#next_page
end
def self.paginate(page)
if(page && page.to_i > 0)
#current_page = page.to_i - 1
else
#current_page = 0
end
entites = self.all(:offset => #current_page * ##page_size, :limit => ##page_size + 1)
if #current_page > 0
#prev_page = #current_page
end
if entites.size == ##page_size + 1
entites.pop
#next_page = (#current_page || 1) + 2
end
entites
end
end
end
Then I have call of #paginate:
#photos = Photo.paginate(params[:page])
And getting following error:
application error
NoMethodError at /dashboard/photos/
undefined method `paginate' for Photo:Class
In Active record this concept works fine for me... I'am using JRuby for notice. What I'am doing wrong?
Andrew,
You can think of DataMapper::Resource as the instance (a row) and of DataMapper::Model as the class (a table). Now to alter the default capabilities at either the resource or the model level, you can either append inclusions or extensions to your model.
First you will need to wrap your #paginate method in a module. I've also added a probably useless #page method to show how to append to a resource in case you ever need to.
module Pagination
module ClassMethods
def paginate(page)
# ...
end
end
module InstanceMethods
def page
# ...
end
end
end
In your case, you want #paginate to be available on the model, so you would do:
DataMapper::Model.append_extensions(Pagination::ClassMethods)
If you find yourself in need of adding default capabilities to every resource, do:
DataMapper::Model.append_inclusions(Pagination::InstanceMethods)
Hope that helps!

Resources