For now, I've got Three Models
# town.rb
class Town < ActiveRecord::Base
has_many :buildings
end
# building.rb
class Building < ActiveRecord::Base
belongs_to :town
end
# building_default.rb
class BuildingDefault < ActiveRecord::Base
end
I want the following to happen when a User creates a Town :
Populate the user's Building model with records based upon the
information contained in the BuildingDefault model.
Set each building.town_id correctly.
For example, lets assume Building and BuildingDefault have the attribute :name in common with each other. And BuildingDefault contains two records (it will actually contain ~ 125):
BuildingDefault.all
# => <ActiveRecord::Relation [#<BuildingDefault id: 1, name: "cannon">, #<BuildingDefault id: 2, name: "archer">]>
Then a User fills out a form that creates a new Town. I want to do an after_create method which copies everything from BuildingDefault to Building. In this case Building would end up with:
Building.find_by_town_id(1)
# => <ActiveRecord::Relation [#<Building id: 69, town_id: 1, name: "cannon">, #<Building id: 70, town_id: 1, name: "archer">]>
What's a possible way to facilitate this behavior?
I think this should be enough :
after_create :set_buildings
private
def set_buildings
BuildingDefault.all.each do |default_b|
buildings.create(id: default_b.id, name: default_b.name)
end
end
I am not sure if you are confusing with user_id and town_id or you really want user model also to be associated with building/town. If it's the latter, please update your code to include with a 'belongs_to' user association.
For now, I am assuming you want to associate towns and buildings only. It is a case of many_to_many relationships. In that case, there are two options I see.
If all your buildings have no special attribute related to individual town,(i.e, building of type "cannon" has the same values for Town A or Town B), you can just associate has_and_belongs_to_many relationship between the two creating a dummy join table.Then you can add all buildings to each town when it is created by following code:
#town.buildings = Building.all
#town.save!
If there can be different values related to buildings for individual town, then you can set up a has_many buildings, through: :building_town relationship in Town model and puts those differing attributes on the intermediate model which in this case is BuildingTown.
I don't see the need for keeping a DefaultBuilding model if all you do is copy all over to the actual Building model.
Related
I'm working on a project that has two models: user and city.
class User < ActiveRecord::Base
belongs_to :city
end
class City < ActiveRecord::Base
has_many :users
end
I'm saving the cities to the user as an integer (the primary key of the city). A user can only have one city and the cities table has pre-populated values:
id | name
1 | New York
2 | Chicago
3 | Boston
What kind of association would I need (and is it possible) to have so that I could call something like the below and get the city instead of the id?
user = User.find(id)
user.city
>> New York
With an instance of a user, you would simply call user.city.name to get the name of the user's city:
user = User.find(1)
puts user.city.name
Recognizing this has already been answered, I'd also add that if you have a lot of logic that requires this data that isn't contained within the model itself, it can make sense to add an instance method that calls the relationship's method. This allows for easier refactoring if the nature of that relationship changes.
I have a following model structure.
I have an Itinerary that has many itinerary nodes. Each itinerary node is a wrapper around either a place, hotel, activity etc. So for example.
Itinerary = "Trip to Paris"
Itinerary.itinerary_nodes = [Node1, Node2, Node3] where
Node1 = "JFK Airport"
Node2 = "CDG Airport"
Node3 = "Eiffel Tower"
So essentially nodes represents the places you will visit in your itinerary. In my model structure; lets assume that my airports are modeled different from monuments or hotels. Now I want to create a association such that;
class ItineraryNode
include Mongoid::Document
has_one :stopover
end
Where each stopover can be a different object. It's type and id is stored by default and is later inflated using that.
So how do I declare multiple models to be associated to ItineraryMode? I can implement this specifically by ensuring that I set these attributes manually in initializer; but curious if something like this is supported by default.
Cheers
This is not a "has_one", it is a "belongs_to" (polymorphic)
class ItineraryNode
include Mongoid::Document
belongs_to :stopover, :polymorphic => true
belongs_to :itinerary
end
class Airport
include Mongoid::Document
has_many :itinerary_nodes, :as => :stopover
end
class Place
include Mongoid::Document
has_many :itinerary_nodes, :as => :stopover
end
So now you can get:
#itinerary.itinerary_nodes.each do |node|
if node.stopover.is_a? Airport
puts "Welcome to #{note.stopover.name}"
elsif node.stopover.is_a? Event
puts "Bienvenue, would you like a drink?"
elsif node.stepover.is_a? Place
puts "The ticket line is over there"
end
end
(I used an if construct just to show better the polymorphism, you would use a case construct here...)
You see that node.stepover can be of many classes.
EDIT (after the comment, I understand that the ItineraryNodemodel is an attempt to a handcrafted polymorphism for a many-to-many association.
From the Mongoid documentation:
Polymorhic behavior is allowed on all relations with the exception of has_and_belongs_to_many.
So you need to use an intermediate model (ItineraryNode). The provided solution is the simplest one I can think of.
I'm trying to set up a proper database-design, but I'm stuck.
Here is what I'm trying to save.
Every user can define a vote history list from imdb looking like this.
Two users can define the same list.
First I want to be able to save each list as an imdb_vote_history_list - list.
class ImdbVoteHistoryList < ActiveRecord::Base
has_and_belongs_to_many :vote_history_list
has_and_belongs_to_many :movies
# Fields
# id (Integer) - defined by the user
end
Each list should be unique and is being defined by it's ID (given in the link).
Each list has and belongs to many movies, as in the code above.
Each user should be able to pick a name for every list.
So instead of saying
Each imdb_vote_history_list belongs_to user
I create a new relation called vote_history_list.
class VoteHistoryList < ActiveRecord::Base
has_and_belongs_to_many :imdb_vote_history_lists
belongs_to :user
# Fields
# name (String)
end
Here the user can pick any name for the list, without interference with other user's names.
Is this a good way to store the data?
From the theoretical database design view this is the right approach.
For example the entity relationship model describes it this way. You can have relationships between entities and attributes at those relationships. If you map those to a relational model (database tables) you get a table for the relationship containing references to both entites and all additional information.
This is what theory can tell us about it :)
I have a controller/model hypothetically named Pets. Pets has the following declarations:
belongs_to :owner
has_many :dogs
has_many :cats
Not the best example, but again, it demonstrates what I'm trying to solve. Now when a request comes in as an HTTP POST to http://127.0.0.1/pets, I want to create an instance of Pets. The restriction here is, if the user doesn't submit at least one dog or one cat, it should fail validation. It can have both, but it can't be missing both.
How does one handle this in Ruby on Rails? Dogs don't care if cats exists and the inverse is also true. Can anyone show some example code of what the Pets model would look like to ensure that one or the other exists, or fail otherwise? Remember that dogs and cats are not attributes of the Pets model. I'm not sure how to avoid Pets from being created if its children resources are not available though.
errors.add also takes an attribute, in this case, there is no particular attribute that's failing. It's almost a 'virtual' combination that's missing. Parameters could come in the form of cat_name="bob" and dog_name="stew", based on the attribute, I should be able to create a new cat or dog, but I need to know at least one of them exists.
You're looking for errors.add_to_base. This should do the trick:
class Pet < ActiveRecord::Base
belongs_to :owner
has_many :dogs
has_many :cats
validate :has_cats_or_dogs
def has_cats_or_dogs
if dogs.empty? and cats.empty?
errors.add_to_base("At least one dog or cat required")
end
end
end
If you want to pass cat_name or dog_name to the controller action, it may look like this:
class PetsController < ApplicationController
# ...
def create
#pet = Pet.new(params[:pet])
#pet.cats.build(:name => params[:cat_name]) if params[:cat_name]
#pet.dogs.build(:name => params[:dog_name]) if params[:dog_name]
if #pet.save
# success
else
# (validation) failure
end
end
end
Alternatively, for some more flexibility you can use nested attributes to create new cats and dogs in your controller.
I was writing some tests and I ran into something I'm trying to understand.
What is the difference underneath when calling:
.update_attributes(:group_ids, [group1.id, group2.id])
vs
.update_attributes(:groups, [group1, group2])
These 2 models in question:
group.rb
class Group
include Mongoid::Document
has_and_belongs_to_many :users, class_name: "Users", inverse_of: :groups
end
user.rb
class User
include Mongoid::Document
has_and_belongs_to_many :groups, class_name: "Group", inverse_of: :users
end
test code in question:
g1 = create(:group)
u1 = create(:user, groups: [g1])
g1.update_attribute(:users, [u1])
# at this point all the associations look good
u1.update_attribute(:group_ids, [g1.id])
# associations looks good on both sides when i do u1.reload and g1.reload
u1.update_attribute(:groups, [g1])
# g1.reload, this is when g1.users is empty and u1 still has the association
Hope I made sense, thanks
Are all your attributes white listed properly?
Without schema for the models, your join object, and the actual tests I'm grasping at straws, but based purely on that example my guess would be that the first model contains an attribute that is mapping to an unintended field on your second model, and overwriting it when you pass an entire object, but not when you specify the attribute you want updated. Here's an example: (I'm not assuming you forgot your join table, I'm just using that because its the first thing that comes to mind)
so we create 2 models, each that have a field that maps to user_id
group.create(id:1, user_id:null)
group_user.create(id:1, group_id: 1, user_id:null)
group.update_attributes(user_id: (group_user.id))
So at this point, when you call group.users, it checks for a user with the id of 1, because that's the id of the group_user you just created & passed it, and assuming you have a User with that ID in your database, the test passes.
group_user.update_attributes(group_id: group.id)
In this case the method ONLY updates group_id, so everything still works.
group_user.update_attributes(group_id: group, user_id: group)
In this case you pass an entire object through, and leave it up to the method to decide what fields get updated. My guess is that some attribute from your group model is overwriting the relevant attribute from your user model, causing it to break ONLY when NO user_ids match whatever the new value is.
Or an attribute isn't white listed, or your test is wonky.