Having problems with associations in Rails - ruby-on-rails

I'm creating a restaurant app that will list restaurants as a rails starting project. For now, I want the restaurant to have one menu.
class Restaurant < ActiveRecord::Base
has_one :menu
end
class Menu < ActiveRecord::Base
belongs_to :restaurant
end
I've also added a migration for restaurant_id on the menu by running rails generate migration AddRestaurantIDToMenus restaurant_id:integer and ran rake db:migrate
My problem now is how do I associate a new menu to a restaurant? When I create a new menu post it does not automatically associate with a restaurant. Is there anyway I can create a menu directly from the restaurant's show page? What would I need to set up in the restaurant controller area for this?

First, when you do a migration of a model that belongs to another model, don't add the foreign key manually, instead use references for the column type, like this
rails g migration AddRestaurantRefToMenus restaurant:references
Now, when you want to create a menu that belongs to an instance of Restaurant, you can do something like this in your controller:
#menu = #restaurant.menus.create(some_attribute: some_value, another_attibute: another_value)
Definitely read through the rails guides on associations.

Oh my... You have so much to learn...
First of all – it sounds like you're still in the early stages of your app, so unless you have a strong reason to keep any data that's already in your database, there's no real reason to create a whole new migration just to add the restaurant_id to your Menu table. Just rake db:migrate VERSION=0, edit your create_menus migration, and then `rake db:migrate' up again.
As far as actually creating a menu and associating it with a restaurant, you can do it any number of ways:
Restaurant or Menu controller:
def create_menu
#restaurant = Restaurant.find(params[id])
#menu = Menu.create(:restaurant_id => #restaurant.id, :other_attribute => "something")
#Or...
#restaurant.menu = Menu.create(:restaurant_id => #restaurant.id, :other_attribute => "something")
#Or...
#menu = Menu.new(:other_attribute => "something")
if #menu.save #returns true if saved properly
#restaurant.menus << #menu
end
end

With regards to how to do both together, you could say in your Restaurants model
accepts_nested_attributes_for :menu
Then in the new of your controller you would have something like
#restaurant = Restaurant.new
#restaurant.menu = Menu.new
Your new view would now look something like
form_for (#restaurant) do |r|
<%= r... %> #whatever they can set for the restaurant (like a name maybe)
#
r.fields_for :menu do |m|
<%= m... %> #whatever they can set for the menu
end
r.submit "button"
end
Lastly create in your controller would look something like
#restaurant = Restaurant.new(params[:restaurant])
if (#restaurant.save)
...
else
...
end
You can do this with edit/update almost the exact same way (the main difference there being that your restaurant, and perhaps its menu, already exist) so your edit would look something like
#restaurant.find(params[:id])
then in your update it would look like
#restaurant.find(params[:id])
if #restaurant.update_attributes(params[:id])
...
else
...
end
Hope that helps answer some questions you have.
p.s. Here is a good place to start if you have questions about form_for and the different inputs.

Related

Nested ajax form within a ruby form

Thanks in advance for whoever tries to resolve my issue.
I am new to rails so please help, I have
rails g scaffold City name:string
rails g scaffold Area name:string city:references
rails g scaffold User name:string brief:text
rails g migration CreateAreasUsers id:false area:references user:references
rake db:migrate
CreateAreasUsers is a table join bw area and user
City and Areas are pre-populated
Two issues
1) How to add functionality in user form so that :areas_users (join table) is also updated.
2) City and Area are separate models so how can I change select tag for city with select tag for area (however area depends on city)
like:
<div id="foo">
<select>
<% #cities.each do |city|%>
<option value="<%=city.id%>"> <%=city.name%> </option>
<%end%>
<select/>
<div id="bar">
<select>
<% #areas.each do |area|%>
<option value="<%=area.id%>"> <%=area.name%> </option>
<%end%>
<select/>
</div>
div foo should be interchanged with div bar within the new user form
Please help.
Okay so since you're a Rails newbie, I'll explain what you need to know.
Association
Firstly, you're dealing with ActiveRecord associations. I guess you have some idea as to how they work, as you've asked pertinent questions.
If you need to know the specifics, you've been making a has_and_belongs_to_many relationship:
This does not require any extra models - it gives you the ability to invoke associated data directly with the join table itself.
--
To answer your first question:
How to add functionality in user form so that :areas_users (join
table) is also updated.
The answer is to pass the appropriate data through your habtm association:
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :areas
end
#app/models/area.rb
class Area < ActiveRecord::Base
has_and_belongs_to_many :users
end
This allows you to reference #user.areas and #area.users respectively, including forms:
#app/views/users/new.html.erb
<%= form_for #user do |f| %>
<%= f.select :areas, Area.all, multiple: true %>
<% end %>
Normally you'd use accepts_nested_attributes_for for this type of functionality. This is a slightly different matter which I'd have to explain with another post.
You can then alter your #user object with the following:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
#user = User.new user_params
end
private
def user_params
params.require(:user).permit(:user, :params, :areas)
end
end
For your second question:
City and Area are separate models so how can I change select tag for
city with select tag for area (however area depends on city) like
This is a little more involved.
First of all, you need to have your associations set up correctly. I recommended the above based on what you posted; I am not a mind reader, so if it's wrong, it's because you didn't explain yourself properly.
The bottom line is that your City and Area models need to have the following associations:
#app/models/city.rb
class City < ActiveRecord::Base
belongs_to :area
end
#app/models/area.rb
class Area < ActiveRecord::Base
has_many :cities
end
This means that you'll be able to call #city.area and #area.cities. As such, you'll be able to load a form as follows:
What you need to do is create a way for the selectbox to pull the associated values from your server. Whilst there is likely a gem or simpler way to do this, I'll just write it for you whilst I'm in the mood:
#config/routes.rb
resources :areas do
get ":id/cities", to: "areas#show" # -> we'll be using the 'show' action
end
#app/controllers/areas_controller.rb
class AreasController < ApplicationController
def show
#area = Area.find params[:id]
respond_to do |format|
format.html
format.json { render json: #area.cities }
end
end
end
This gives us a hook with which we can use Ajax to pull the relevant cities when an area is selected:
#app/assets/javascripts/application.js
$("select.areas").on("change", function(){
id = $(this).val();
$.ajax({
url: id + "/cities"
success: function(data) {
cities = JSON.Parse(data);
$('select.cities').find('option').remove();
$.each(cities, function(index, item) {
$("select#cities").append( // Append an object to the inside of the select box
$("<option></option>") // Yes you can do this.
.text(item.description)
.val(item.id)
);
});
}
});
});

Ruby on Rails 4 find object by id

I have the following show-view, where i display basic information about Product and display other User's Products.
<h1>Book <%= #product.name %></h1>
<% #products.each do |product| %>
<ul>
<%= product.name %>
<%= link_to "Make offer", {controller: "offers", :action => 'create', id: product.id } %>
</ul>
Controller
def show
#product = current_user.products.find(params[:id])
#products = Product.all
end
My goal is to make Offer between two Products.
I created Offer model and methods for making Offers:
class Offer < ActiveRecord::Base
belongs_to :product
belongs_to :exchanger, class_name: "Product", foreign_key: "exchanger_id"
validates :product_id, :exchanger_id, presence: true
def self.request(product, exchanger)
unless product == exchanger or Offer.exists?(product, exchanger)
transaction do
create(product: product, exchanger: exchanger, status: "oczekujace")
create(product: exchanger, exchanger: product, status: "oferta")
end
end
#other methods
end
Making offers is working, because I checked it in Console.
My problem is in OffersController:
class OffersController < ApplicationController
before_filter :setup_products
def create
Offer.request(#prod, #exchanger)
redirect_to root_path
end
private
def setup_products
#prod = current_user.products.find(1)
#exchanger = Product.find_by_id(params[:id])
end
end
Problem is with a following line (using link in show-page for products with different id's than 1 works):
#prod = current_user.products.find(1)
But I don't know how to find object in Db for actual product which my show-page shows. Not only for id = 1.
I don't know how to find this object in database.
I don't know the specific answer to your question, but perhaps if I explain what you need to look at, your solution will arise:
Find
Rails isn't magic - it uses ActiveRecord (which is an ORM - Object-Relation Mapper), which means every time you fire a query (through find), your ORM (ActiveRecord) will search the relevant database data for you
The problem you have is that although you're using the correct syntax for your lookup, you may not have a record with an id of 1 in your db.
current_user.products.find(1) tells ActiveRecord to scope the query around the current user, and their products. So you'll get something like like this:
SELECT * FROM 'products' WHERE user_id = '15' AND id = '1'
Objects
Further, you have to remember that Ruby (and Rails by virtue of being built on Ruby) is an object orientated language. This means that everything you load / interact with in the language should be based on an object
The problem you have is you're not associating your object to your Rails framework correctly. What I mean here is described below, but essentially, if you build your Rails framework correctly, it will give you the ability to associate your objects with each other, allowing you to call the various products you need from your offer
This is a simplistic way of looking at it, of course. You'll want to look at this diagram to see how it works:
Bottom line - try treating your application like a series of objects, rather than a logical flow. This will help you appreciate the various associations etc that you need to get it moving forward
Resources
You mention you can't show the product on your show page for an id other than one. I think the problem is really about how to get your show action to work.
If this is the case, let me explain...
Rails is resource-based, meaning that everything you do / create needs to be centred around a resource (object) of some sort. The problem is many people don't know this, and consequently complicate their controller structure for no reason:
Above is the typical "CRUD" routing structure for Rails-based resources. This should demonstrate the way that Rails will typically be constructed -- around resources
--
Further, Rails is built on the MVC programming pattern - meaning you need to use your controller to populate a series of data objects for use in your application.
To this end, if you load a resource, and want to populate it with resourceful information of another object - you need to make sure you have set up the data objects in a way to ensure you can look them up correctly, which either means passing the data through your routes or using a persistent data-type, such as cookies or sessions
The problem you have is you need to pass the product id to your controller somehow. How I'd do that is as follows (using nested resources):
#config/routes.rb
resources :offers do
resources :products #-> domain.com/offers/2/products
end
This will give you the ability to load the products controller with the variables params[:id] for the product, and params[:offer_id] for your Offer made available:
#app/controllers/products_controller.rb
Class ProductsController < ApplicationController
def show
#offer = Offer.find params[:offer_id]
#product = Product.find params[:id]
end
end

How create record in HABTM Rails Association How make this?

I'm having some trouble creating a new relation in my has_and_belongs_to_many model. I defined the models like this:
journals model
has_and_belongs_to_many :posts
post model
has_and_belongs_to_many :journal
I don't know how create the association , I made a button but I don't know how it works. I created the action add_post
def add_post
#journal_post = JournalsPosts.new
end
I make this link to create the association but I don't know what I have to do now:
<%= link_to "Add to Journal",:controller => "journals",:action => "add_post" %>
The redirect works correctly but I don't know how to proceed now ? Do you know about some guide to HABTM associations? I have already tried this, but it didn't help.
After researching this myself, you should be able to do
def add_post
j = Journal.first # or find_by, etc
p = Post.first # or find_by, etc
j.posts << p # creates a record in journals_posts table
j.save!
end
(1) The accepted answer made it sound as though the association could only be made directly. Also you wouldn't have a "JournalsPosts" class if you're using the habtm association, since it specifically avoids using a model for the intermediary table.
(2) Note that this association will not be unique. If you call this multiple times, you'll get multiple entries in the journals_posts table with the same two integer pairs.
You should highly consider using has_many, :through as that's the preferred way to do these kinds of relationships now in Rails.
That said, if you want to continue with has_and_belongs_to_many, you need to somehow get the journal and post ids that you want to associate so you can correctly create the association.
In your routes:
resources :journals do
member do
put :add_post
end
end
In your view (make sure you set #journal and #post somewhere):
<%= link_to "Add to Journal", add_post_journal_path(#journal, :post_id => #post.id), :method => :put %>
In your controller:
def add_post
#journals_posts = JournalsPosts.new(:journal_id => params[:id], :post_id => params[:post_id])
if #journals_posts.save
...
else
...
end
end

How to pass object id?

I have a relationship between two models
Category Model
class Category < ActiveRecord::Base
belongs_to :product
end
Product Model
class Product < ActiveRecord::Base
has_many :categories
end
I have category_id in products table but When I create a new product in my products table
category_id is null. I am new to rails can anyone help please?
First off, a thought - most of the time, products have many categories, but each category also contains many products. Perhaps your association should be a many-to-many? On to your actual question.
If I understand correctly, your question is really about how to create categories and products that are related to each other in the database, i.e. how to set the value of product_id when building a new category.
For clarity in case you need it, product_id would only be set for a category. The category, after all, belongs to that product, so it has to hold its owner's ID.
So, let's say you want to build a new category that belongs to an existing product - you can do this:
# in your view, where you link from products/show.html.erb to category/new.html.erb
<%= link_to "Build new category for this product", new_category_url(:id => #product.id) %>
# you must have #product defined, and you also must have
# 'resources :categories' in your routes file
# in your controller action categories/new, set the new category's product id:
def new
#category = Category.new(:product_id => params[:id])
end
# include a hidden field to contain the product_id in your new form
<%= form_for #category do |f| %>
<%= f.hidden_field :product_id %>
... other fields, labels, etc.
<% end %>
# save the record as you normally would (analogous to the code in your comment to #Chowlett).
#category = Category.new(params[:category])
if #category.save
redirect_to :action => "list", :notice => "Category saved successfully."
else
render :action => "new"
end
The above code allows you to build a product, then each category one-by-one. So, we are building your product first, then including a link from the product/show page to your category/new form, passing in the ID of the product you want that category to be part of.
If you want to build a product and some categories at the same time, it is a bit more complicated. For more information on this, take a look at http://railscasts.com/episodes/196-nested-model-form-part-1 (this is the first of a three-part series) and https://github.com/ryanb/nested_form. I don't suggest this course of action unless you are very comfortable with the above basics. I once got mired in this code for a week when I was new to Rails!
First off, you have the _id field in the wrong table. If Category belongs_to :product, then your categories table needs a field product_id.
Look at it this way: each Product can have many Categories - so what single value would you expect to find in a category_id field?
If you still have problems after correcting that, let me know.
Edit: Once you've got your tables set up, you still need to tell Rails what the link should be. You've got a few options. Assuming you've got a Category in hand, your best bet is new_prod = my_cat.create_product(). Alternatively, you can use new_prod = Product.create(:category => my_cat).
Later on, you can associate the models together like this:
my_prod.category = my_cat
my_prod.save

Best practice: How to split up associations-functions in controllers with equal-access models

I have 2 equal-access models: Users and Categories
Each of these should have the standard-actions: index, new, create, edit, update and destroy
But where do I integrate the associations, when I want to create an association between this two models?
Do I have to write 2 times nearly the same code:
class UsersController << ApplicationController
# blabla
def addCategory
User.find(params[:id]).categories << Category.find(params[:user_id])
end
end
class CategoriessController << ApplicationController
# blabla
def addUser
Category.find(params[:id]).users << User.find(params[:user_id])
end
end
Or should I create a new Controller, named UsersCategoriesController?
Whats the best practice here? The above example doens't look very DRY.... And a new controller is a little bit too much, I think?
Thanks!
EDIT:
I need to have both of these associations-adding-functions, because f.e.
#on the
show_category_path(1)
# I want to see all assigned users (with possibility to assign new users)
and
#on the
show_user_path(1)
#I want to see all assigned categories (with possibility to assign new categories)
EDIT:
I'm taking about a HBTM relationship.
If you have a situation where you need to do this with has_and_belongs_to_many, you could take the approach you are currently using, or you could build this into your existing update actions.
When you add a habtm relationship, you will get an additional method on your classes...
class User < ActiveRecord::Base
has_and_belongs_to_many :categories
end
With this, you can do this:
user = User.find(params[:id])
user.category_ids = [1,3,4,7,10]
user.save
The categories with those ids will be set. If you name your form fields appropriately, the update can take care of this for you if you want to use checkboxes or multiselect controls.
If you need to add them one at a time, then the methods you've built in your original post are reasonable enough. If you think the repetition you have is a code smell, you are correct - this is why you should use the approach I outlined in my previous answer - an additional model and an additional controller.
You didn't mention if you are using has_and_belongs_to_many or if you are using has_many :through. I recommend has_many :through, which forces you to use an actual model for the join, something like UserCategory or Categorization something like that. Then you just make a new controller to handle creation of that.
You will want to pass the user and category as parameters to the create action of this controller.
Your form...
<% form_tag categorizations_path(:category_id => #category.id), :method => :post do %>
<%=text_field_tag "user_id" %>
<%=submit_tag "Add user" %>
<% end %>
Your controller...
class CategorizationsController < ApplicationController
def create
if Categorization.add_user_to_category(params[:user_id], params[:category_id])
...
end
end
then your categorization class...
class Categorization
belongs_to :user
belongs_to :category
def self.add_user_to_category(user_id, category_id)
# might want to validate that this user and category exist somehow
Categorization.new(:user_id => user_id, :category_id => category_id)
Categorization.save
end
end
The problem comes in when you want to send the users back, but that's not terribly hard - detect where they came from and send them back there. Or put the return page into a hidden field on your form.
Hope that helps.

Resources