RoR - Resource to learning - ruby-on-rails

I'm having the following problem: i have a lot of tables, some of then are nested and other aren't. I divided my app in some areas that i think that is the correct way. One area should manipulate some tables at the same time, at the same action, like i said: some models are nested others not. What is the best solution in Rails world?
What should i read to get the point?
I tried to use
accepts_nested_attributes_for
and i tried build the objects to use at the same form with fields_for. But it's going to be a complex form because some objects contains foreign keys and unfortunately i can't get the correct builds with more than 2 objects.
I'll keep trying.
Thanks!
----edit-----
Class Country < ActiveRecord::Base
has_many :states
attr_accessible :nome
# i tried # accepts_nested_attributes_for :state
end
Class State < ActiveRecord::Base
belongs_to :country
has_many :cities
attr_accessible :nome, :country_id
# i tried # accepts_nested_attributes_for :city
# i tried # accepts_nested_attributes_for :country # too
end
The models continues until we get the adress model:
Class Adress < ActiveRecord::Base
has_many :bairros_logradouros # we name streets, avenues, parks as logradouros
# here in Brasil, the others models are translated
# to EN here
has_many :logradouros, :trough => :bairros_logradouros # many-to-many
attr_accessible :number, :complement, :other, :another
# i tried # accepts_nested_attributes_for :logradouro
end
The setup: Country -> State -> City -> District(bairro, here) -> :Logradouro <-> Adress.
I tried to build the chain in both direction, but i only get 2 objects, the third brings a nil problem with the build method.
These tables are about adress, i should manipulate the User model tha has_one Person that, at the end has an adress, i would like to point the adress at the :addres_id inside Person.
All this must be manipulated at the custom Data controller, all CRUDs here.
I couldn't build the entire chain:
#addres = #addres.new
#other = #addres.logradouros.build
#another = #other.build_district
#even_more = #another.build_city
....
I learned to use objects.build and build_object, but i couldn't build more than 2 nested objects.
I'm a newbie.
Thanks again!

Here is a great place to start:
http://www.tutorialspoint.com/ruby-on-rails/index.htm

If you are using mass assignment whitelisting in Rails 3.1+ (up to but not including Rails 4) then if you use accepts_nested_attributes_for :logradouros you need to have attr_accessible :logradouros_attributes (notice the plural logradouros and the _attributes in the name). But, you may have more problems than that. Here is a related question.
A good reference when starting out is to read everything in the Rails guide.

Related

Rails 4 Accept nested attributes with has_one association

I have a question about Rails Nested Attributes.
I'm using Rails 4 and have this model:
model Location
has_one parking_photo
has_many cod_photos
accepts_nested_attributes_for :parking_photo
accepts_nested_attributes_for :cod_photos
end
When I use for example:
Location.find(100).update(cod_photo_ids: [1,2,3]) it works.
But Location.find(100).update(parking_photo_id: 1) doesn't works.
I don't know what difference between nested attributes has_one and has_many.
Or do we have any solution for my case, when I already have child object and want to link the parent to the child and don't want to use child update.
Thank you.
The problem has nothing to do with nested attributes. In fact you're not even using nested attributes at all in these examples.
In this example:
Location.find(100).update(cod_photo_ids: [1,2,3])
This will work even if you comment out accepts_nested_attributes_for :cod_photos as the cod_photo_ids= setter is created by has_many :cod_photos.
In the other example you're using has_one where you should be using belongs_to or are just generally confused about how you should be modeling the association. has_one places the foreign key on the parking_photos table.
If you want to place the parking_photo_id on the locations table you would use belongs_to:
class Location < ActiveRecord::Base
belongs_to :parking_photo
# ...
end
class ParkingPhoto < ActiveRecord::Base
has_one :location # references locations.parking_photo_id
end
Of course you also need a migration to actually add the locations.parking_photo_id column. I would really suggest you forget about nested attributes for the moment and just figure out the basics of how assocations work in Rails.
If you really want to have the inverse relationship and put location_id on parking_photos you would set it up like so:
class Location < ActiveRecord::Base
has_one :parking_photo
# ...
end
class ParkingPhoto < ActiveRecord::Base
belongs_to :location
validates_uniqueness_of :location_id
end
And you could reassign a photo by:
Location.find(100).parking_photo.update(location_id: 1)

Factory Girl with polymorphic association for has_many and has_one

I am currently working on a project and I wanted to create tests using factory girl but I'm unable to make it work with polymorphic has_many association. I've tried many different possibilities mentioned in other articles but it still doesn't work. My model looks like this:
class Restaurant < ActiveRecord::Base
has_one :address, as: :addressable, dependent: :destroy
has_many :contacts, as: :contactable, dependent: :destroy
accepts_nested_attributes_for :contacts, allow_destroy: true
accepts_nested_attributes_for :address, allow_destroy: true
validates :name, presence: true
#validates :address, presence: true
#validates :contacts, presence: true
end
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
# other unimportant validations, address is created valid, the problem is not here
end
class Contact < ActiveRecord::Base
belongs_to :contactable, polymorphic: true
# validations ommitted, contacts are created valid
end
So bassically I want to create factory for Restaurant with address and contacts (with validations on Restaurant for presence, but if it's not possible, even without them) but I'm unable to do so. Final syntax should be like:
let(:restaurant) { FactoryGirl.create(:restaurant) }
Which should also create associated address and contacts. I have read many articles but I always get some sort of error. Currently my factories (sequences are defined correctly) are like this:
factory :restaurant do
name
# address {FactoryGirl.create(:address, addressable: aaa)}
# contacts {FactoryGirl.create_list(:contact,4, contactable: aaa)}
# Validations are off, so this callback is possible
after(:create) do |rest|
# Rest has ID here
rest.address = create(:restaurant_address, addressable: rest)
rest.contacts = create_list(:contact,4, contactable: rest)
end
end
factory :restaurant_address, class: Address do
# other attributes filled from sequences...
association :addressable, factory: :restaurant
# addressable factory: restaurant
# association(:restaurant)
end
factory :contact do
contact_type
value
association :contactable, :factory => :restaurant
end
Is there a way to create restaurant with one command in test with addresses and contacts set? Do I really need to get rid off my validations because of after(:create) callback?
Current state is as fllowing:
Restaurant is created with name and id.
Than the address is being created - all is correcct, it has all the values including addressable_id and addressable_type
After that all contacts are being creaed, again everything is fine, cntacts has the right values.
After that, restaurant doesn't have any ids from associated objects, no association to adddress or contacts
After than, for some reason restaurant is build again (maybe to add those associations?) and it fails: I get ActiveRecord:RecordInvalid.
I'm using factory_girl_rails 4.3.0, ruby 1.9.3 and rails 4.0.1. I will be glad for any help.
UPDATE 1:
Just for clarification of my goal, I want to be able to create restaurant in my spec using one command and be able to access associated address and contacts, which should be created upon creation of restaurant. Let's ignore all validations (I had them commented out in my example from the beginning). When I use after(:build), first restaurant is created, than address is created with restaurant's ID as addressable_id and class name as addressable_type. Same goes to contacts, all are correct. The problem is, that restaurant doesn't know about them (it has no IDs of address or contacts), I can't access them from restaurant which I want to.
After really thorough search I have found an answer here - stackoverflow question.This answer also point to this gist. The main thing is to build associations in after(:build) callback and then save them in after(:create) callback. So it looks like this:
factory :restaurant do
name
trait :confirmed do
state 1
end
after(:build) do |restaurant|
restaurant.address = build(:restaurant_address, addressable: restaurant)
restaurant.contacts = build_list(:contact,4, contactable: restaurant)
end
after(:create) do |restaurant|
restaurant.contacts.each { |contact| contact.save! }
restaurant.address.save!
end
end
I had also a bug in my rspec, because I was using before(:each) callback instead of before(:all). I hope that this solution helps someone.
The Problem
Validating the length of a related list of rows is a difficult problem to frame in SQL, so it's a difficult problem to frame in ActiveRecord as well.
If you're storing a restaurant foreign key on the addresses table, you can't ever actually create a restaurant that has addresses by the time it's saved, because you need to save the restaurant to get its primary key. You can get around this problem in ActiveRecord by building up the associated objects in memory, validating against those, and then committing the entire object graph in one SQL transaction.
How to do what you're asking
You can generally get around this by moving things into an after(:build) hook instead of after(:create). ActiveRecord will save its dependent has_one and has_many associations once it saves itself.
You're getting errors now because you can't modify an object to satisfy validations in an after(:create) block, because validations have already run by the time the callback runs.
You can change your restaurant factory to look something like this:
factory :restaurant do
name
after(:build) do |restaurant|
restaurant.address = build(:restaurant_address, addressable: nil)
restaurant.contacts = build(:contact, 4, contactable: nil)
end
end
The nils there are to break the cyclic relationship between the factories. If you do it this way, you can't have a validation on the addressable_id or contactable_id keys, because they won't be available until the restaurant is saved.
Alternatives
Although you can get both ActiveRecord and FactoryGirl to do what you're asking, it sets up a precarious list of dependencies which are difficult to understand and are likely to result in leaky validations or unexpected errors like the ones you're seeing now.
If you're validating contacts this way from the restaurant model because of a form in which you create both a restaurant and its corresponding contacts, you can save yourself a lot of pain by creating a new ActiveModel object to represent that form. You can collect the attributes you need for each object there, move some of the validations (especially the ones which validate the length of the contacts list), and then create the object graph on that form in a way that's much clearer and less likely to break.
This has the added benefit of making it easy to create lightweight restaurant objects in other tests which don't need to worry about contacts or addresses. If you force your factories to create these dependent objects every time, you'll quickly run into two problems:
Your tests will be painfully slow. Creating five dependent records every time you want to work with a restaurant won't scale very far.
If you ever want to specify different contacts or addresses in your tests, you'll constantly be fighting with your factories.

How does ActiveRecord handle Relationship CamelCasing

I have a question about rails and how its relationships query builder, specifically how camel case is converted for the related calls.
Relevant Code
class CustomerPlan < ActiveRecord::Base
attr_accessible :customer_id, :plan_id, :startDate, :user_id
has_many :planActions
end
class PlanAction < ActiveRecord::Base
attr_accessible :actionType_id, :customerPlan_id, :notes, :timeSpent
belongs_to :customerPlan
belongs_to :actionType
end
The getters and setters work just fine, such as plan_action.actionType.name will correctly pull from the related model. However customer_plan.planActions.each returns the error:
SQLite3::SQLException: no such column: plan_actions.customer_plan_id:
SELECT "plan_actions".*
FROM "plan_actions"
WHERE "plan_actions"."customer_plan_id" = 1
The column is defined in the database as customerPlan_id, was I just wrong to use this? It works for every other call, all my other relationships work fine. Even PlanAction -> CustomerPlan.
I ran through all the docs, and searched about every other source I know of. It would be simple enough to change my columns, I just want to know what's going on here.
Thank you for your time!
A quick fix for this is to just explicitly set the foreign_key.
has_many :planActions, :foreign_key => "customerPlan_id", :class_name => "PlanAction"
Still, I think I am missing some model naming convention somewhere, just can't seem to figure out what.
The Rails convention for DB column names is to use lowercase letters with words separated by an underscore (e.g. author_id, comments_count, updated_at, etc).
I would highly recommend that you stick to the Rails conventions. This would make your life much easier. To change it to the rails convention, simply create a migration to rename the column to the appropriate style.
However, if you do want to use a custom style for the column name, rails provides the :foreign_key option in the has_many relationship to specify the expected foreign column name:
class CustomerPlan < ActiveRecord::Base
has_many :plan_actions, :foreign_key => 'customerPlan_id'
end
You can also use the alias_attribute macro to alias the column name, if you'd like to use a different model attribute name than the actual DB column name. But as I mentioned, I would recommend sticking to the rails convention as much as possible. You'll thank me later.
Rails has 3 basic naming schemes.
One is for constants, and it is ALL_UPPERCASE_SEPARATED_BY_UNDERSCORES.
One is for Classes and it is AllCamelCaseWithNoUnderscores.
One is for variables and method names, and is all_lowercase_separated_by_underscores.
The reason that it is this way is not just for consistency, but also because it freely converts between them using these methods.
So, to make your posted code more rails-y:
class CustomerPlan < ActiveRecord::Base
attr_accessible :customer_id, :plan_id, :start_date, :user_id
has_many :plan_actions
end
class PlanAction < ActiveRecord::Base
attr_accessible :action_type_id, :customer_plan_id, :notes, :time_spent
belongs_to :customer_plan
belongs_to :action_type
end

How do I express a :has_one assocation :through a collection, with a :condition to construct/select "the one"?

I've got some models (Rails 3.2) like this, A->B->C, to model a Manager (A) who covers many Areas (B), each of which has many Addresses (C). One of those addresses is the manager's primary address. I can't figure out the best way to associate a Manager with his primary address.
class AreaManager < ActiveRecord::Base
has_many :areas, :dependent=>:destroy
has_many :addresses, :through=>:areas
...
def primary_address
self.addresses.where(:primary=>true).first
# in fact, there can be only one
end
end
class Area < ActiveRecord::Base
belongs_to :area_manager
has_many :addresses,:dependent=>:destroy
end
class Address < ActiveRecord::Base
belongs_to :area
attr_accessible :primary
# this is the manager's primary address (not the area's)
end
This allows me to say, for example:
a = AreaManager.find(123)
a.primary_address # => her primary address
But what I really want is a constructor/setter as well as a getter:
a = AreaManager.new
a.build_primary_address ...
I can add to Area:
has_one :primary_address,
:class_name=>'Address',
:conditions => {:primary=>true}
and this allows me to do
a1 = Area.new
a1.build_primary_address ... # instantiates an Address with primary = true
but when I try to do something similar to AreaManager, by adding
has_one :primary_address,
:through=>:areas,
:source=>:address,
:conditions=>{:primary=>true}
and then say:
a = AreaManager.new
a.build_primary_address ...
# instead of creating an area and an address, it croaks
I get a ActiveRecord::HasOneThroughCantAssociateThroughCollection exception. I can't figure out what the right incantation might be (or if there is one). Is there a Railsy way to do this?
As rails is all about following conventions and the right chocie from many options, I would recommend:
Don't apply conditions to an association
Look to use a named scope
Look to use .where (rails3) instead of conditions (rails2). See the ActiveRecord api for more
Applying all this will likely change your code a fair bit and you'll get different results.

Rails association with almost all other models

I'm looking for some suggestions on how to deal with "Regions" in my system.
Almost all other models in the system (news, events, projects, and others) need to have a region that they can be sorted on.
So far, I've considered a Region model with has_many :through on a RegionLink table. I've never had a model joined to so many others and wonder if this route has any negatives.
I've also considered using the acts_as_taggable_on gem and just tag regions to models. This seems ok but I'll have to write more cleanup type code to handle the customer renaming or removing a region.
Whatever I choose I need to handle renaming and, more importantly, deleting regions. If a region gets deleted I will probably just give the user a choice on another region to replace the association.
Any advice on handling this is greatly appreciated.
If each News, Event, etc. will belong to only 1 Region, tags don't seem the most natural fit IMO. This leaves you with 2 options:
Add a region_id field to each model
This is simplest, but has the drawback that you will not be able to look at all the "regioned" items at once - you'll have to query the news, events, etc. tables separately (or use a UNION, which ActiveRecord doesn't support).
Use RegionLink model with polymorphic associations
This is only slightly more complicated, and is in fact similar to how acts_as_taggable_on works. Look at the Rails docs on *belongs_to* for a fuller description of polymorphic relationships if you are unfamiliar
class Region < ActiveRecord::Base
has_many :region_links
has_many :things, :through => :region_links
end
# This table with have region_id, thing_id and thing_type
class RegionLink < ActiveRecord::Base
belongs_to :region
belongs_to :thing, :polymorphic => true
end
class Event < ActiveRecord::Base
has_one :region_link, :as => :thing
has_one :region, :through => :region_link
end
# Get all "things" (Events, Projects, etc.) from Region #1
things = Region.find(1).things
Renaming is quite simple - just rename the Region. Deleting/reassigning regions is also simple - just delete the RegionLink record, or replace it's region_id.
If you find yourself duplicating a lot of region-related code in your Event, etc. models, you may want to put it into a module in lib or app/models:
module Regioned
def self.inluded(base)
base.class_eval do
has_one :region_link, :as => :thing
has_one :region, :through => :region_link
...
end
end
end
class Event < ActiveRecord::Base
include Regioned
end
class Project < ActiveRecord::Base
include Regioned
end
Checkout the cast about polymorphic associations. They did change a bit in rails 3 though: http://railscasts.com/episodes/154-polymorphic-association?view=asciicast

Resources