method error on joining table - ruby-on-rails

Hey I hope you can give me advise on my problem. I got a Course Model, a CourseEnrollment Model and a User Model. In the Course index view it lists all the courses. I got a link_to to join a course. I want to create a new entry on the course_enrollments table.
link_to 'join', current_user.course_enrollments.create(:course_id => course,:user_id => current_user)
In my course_enrollments_controller I have:
def create
#course_enrollment = CourseEnrollment.new(params[:course_id, :user_id])
end
thx for your time

Seems like you are really mixing things up here. You are linking to the return value of the create method of a model but I guess you want to link to the "create" action of a controller.
Maybe you should (re-?) read a guide or introduction on Rails first.

Related

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

Rails forms - Should I build `accepts_nested_attributes_for` associations in the Controller, Model, or View?

The Question
I have a parent that accepts_nested_attributes_for a child. So, when I have a form for the parent, I need to build the child so I can display form fields for it as well. What I want to know is: where should I build the child? In the Model, View, or Controller?
Why I Am Asking This
You may be shaking your head and thinking I'm a madman for asking a question like this, but here's the line of thinking that got me here.
I have a Customer model that accepts_nested_attributes_for a billing_address, like so:
class Customer
belongs_to :billing_address, class_name: 'Address'
accepts_nested_attributes_for :billing_address
end
When I present a form for a new Customer to the user, I want to make sure there is a blank billing_address, so that the user actually sees fields for the billing_address. So I have something like this in my controller:
def new
#customer = Customer.new
#customer.build_billing_address
end
However, if the user doesn't fill out any of the billing_address fields, but tries to submit an invalid form, they will be presented with a form that no longer has fields for the billing_address, unless I put something like this in the create action of my controller:
def create
#customer = Customer.new(params[:customer])
#customer.build_billing_address if #customer.billing_address.nil?
end
There is another issue, which is that if a user tries to edit a Customer, but that Customer doesn't have an associated billing_address already, they won't see fields for the billing_address. So I have to add somethign like this to the controller:
def edit
#customer = Customer.find(params[:id])
#customer.build_billing_address if #customer.billing_address.nil?
end
And something similar needs to happen in the controller's update method.
Anyway, this is highly repetitive, so I thought about doing something in the model. My initial thinking was to add a callback to the model's after_initialize event, like so:
class CustomerModel
after_initialize :build_billing_address, if: 'billing_address.nil?'
end
But my spidey sense started tingling. Who's to say I won't instantiate a Customer in some other part of my code in the future and have this wreak havoc in some unexpected ways.
So my current thinking is that the best place to do this is in the form view itself, since what I'm trying to accomplish is to have a blank billing_address for the form and the form itself is the only place in the code where I know for sure that I'm about to show a form for the billing_address.
But, you know, I'm just some guy on the Internet. Where should I build_billing_address?
Though this advice by Xavier Shay is from 2011, he suggests putting it in the view, "since this is a view problem (do we display fields or not?)":
app/helpers/form_helper.rb:
module FormHelper
def setup_user(user)
user.address ||= Address.new
user
end
end
app/views/users/_form.html.erb:
<%= form_for setup_user(#user) do |f| %>
Note that I had to change the helper method to the following:
def setup_user(user)
user.addresses.build if user.addresses.empty?
user
end
The controller remains completely unchanged.
If you know your model should always have a billing address, you can override the getter for this attribute in your model class as described in the docs:
def billing_address
super || build_billing_address
end
Optionally pass in any attributes to build_billing_address as required by your particular needs.
You would use build if you want to build up something and save it later. I would say, use it in nested routes.
def create
#address = #customer.billing_addresses.build(params[:billing_address])
if #address.save
redirect_to #customer.billing_addresses
else
render "create"
end
end
Something like that. I also use the build when I'm in the console.
You have to remember the principles of MVC, which is to create DRY(don't repeat yourself) code, which is efficiently distributed between the various moving parts of the app
accepts_nested_attributes_for Is Great For Keeping Things DRY
accepts_nested_attributes_for is a model function which allows you to pass data through an association to another model. The reason why it exists is to give you the ability to populate another model's data based on a single form, and is excellent for extending functionality without too much extra code
The problem you're citing is that if you want to use the code in other areas of the app, you'll end up having all sorts of problems
My rebuttal to that is in order to create as efficient an application as possible, you want to write as little code as possible - letting Rails handle everything. The accepts_nested_attributes_for function does allow you to do this, but obviously has a cost, in that you have to accommodate it every time you want to use it
My recommendation is to use what you feel is the most efficient code you can, but also keep to conventions; as this will ensure speed & efficiency
You should handle all these scenarios in controller, since it is not a responsibility of model.
Just in terms of keeping things DRY, you can write a method,
def build_customer(customer)
customer.build_billing_address if customer.billing_address.nil?
#add more code if needed
end
And inside controller you can call this method wherever it is needed. e.g.
def create
#customer = Customer.new(params[:customer])
if #customer.save
redirect_to #customer.billing_addresses
else
build_customer(#customer)
render "new"
end
end

Rails associate record counting inside view

I have two models, story & vote. I have a list of articles which a user can up vote which is stored as a separate vote in the vote model.
The story model has many votes and the story_id is stored with the vote record.
At the moment in my index action on the story view I'm just trying to do a simple count to see how many votes a given story has, I'm achieving this with the following code.
<%= #total_votes = Vote.where(:story_id => story.id).count %>
However I'd like to remove this from my story index action view but not sure were to store it? Is there a more efficient way of doing this?
Seems the relationship is story has many votes. So, you can simply do:
<%= story.votes.count %>
If you are worried about database performance you may want to add a counter cache.
You said you'd like to store the total votes value:
$~ rails g migration add_total_votes_to_stories total_votes:integer
class Story
after_save, :update_total_votes
private
def update_total_votes
write_attribute :total_votes, votes.count
end
Inside the model you could define a method and use it in the controller.
def total_votes(sid)
where(:story_id => sid ).count
end
Then in the controller:
#total_votes = Vote.total_votes(story_id)
this should go in controller
def index
#total_votes = Vote.where(:story_id => story.id).count
end

Eager loading associated models in ActiveAdmin sql query

I've got an ActiveAdmin index page
ActiveAdmin.register Bill
And I am trying to display links to associated models
index do
column "User" do |bill|
link_to bill.user.name, admin_user_path(bill.user)
end
end
But I run into the N+1 query problem - there's a query to fetch each user.
Is there a way of eager loading the bills' users?
The way to do this is to override the scoped_collection method (as noted in Jeff Ancel's answer) but call super to retain the existing scope. This way you retain any pagination/filtering which has been applied by ActiveAdmin, rather than starting from scratch.
ActiveAdmin.register Bill do
controller do
def scoped_collection
super.includes :user
end
end
index do
column "User" do |bill|
link_to bill.user.name, admin_user_path(bill.user)
end
end
end
As noted in official documentation at http://activeadmin.info/docs/2-resource-customization.html
There is an answer on a different post, but it describes well what you need to do here.
controller do
def scoped_collection
Bill.includes(:user)
end
end
Here, you will need to make sure you follow scope. So if your controller is scope_to'ed, then you will want to replace the model name above with the scope_to'ed param.
The existing answers were right at the time, but ActiveAdmin supports eager loading with a much more convenient syntax now:
ActiveAdmin.register Bill do
includes :user
end
See the docs for resource customization
IMPORTANT EDIT NOTE : what follows is actually false, see the comments for an explanation. However I leave this answer where it stands because it seems I'm not the only one to get confused by the guides, so maybe someone else will find it useful.
i assume that
class Bill < ActiveRecord::Base
belongs_to :user
end
so according to RoR guides it is already eager-loaded :
There’s no need to use :include for immediate associations – that is,
if you have Order belongs_to :customer, then the customer is
eager-loaded automatically when it’s needed.
you should check your SQL log if it's true (didn't know that myself, i was just verifying something about :include to answer you when i saw this... let me know)
I've found scoped_collection loads all the entries, instead of just the ones for the page you are displaying. I think a better option is apply_collection_decorator that will only preload the items you are effectively displaying.
controller do
def apply_collection_decorator(collection)
collection.includes(:user)
end
end

current_user.user_type_id = #employer ID

I am building a system with a User model (authenticated using AuthLogic) and three user types in three models: one of these models is Employer. Each of these three models has_many :users, :as => :authenticable.
I start by having a new visitor to the site create their own 'User' record with username, password, which user type they are, etc.
Upon creation, the user is sent to the 'new' action for one of the three models. So, if they tell us they are an employer, we redirect_to :controller => "employers, :action => "new".
Question: When the employer has submitted, I want to set the current_user.user_type_id equal to the employer ID. This should be simple... but it's not working.
# Employers Controller / new
def new
#employer = Employer.new
1.times {#employer.addresses.build}
render :layout => 'forms'
end
# Employers Controller / create
def create
#employer = Employer.new(params[:employer])
if #employer.save
if current_user.blank?
redirect_to :controller => "users", :action => "new"
else
current_user.user_type_id = #employer.id
current_user.user_type = "Employer"
redirect_to :action => "home", :id => current_user.user_type_id
end
else
render :action => "new"
end
end
------UPDATE------
Hi guys. In response:
I am using this table structure because each of my three user type models have lots of different fields and each has different relationships to the other models, which is why I've avoided STI.
By 1.times (#employer.addresses.build) I'm connecting the employer model to the address polymorphic table in one form, so I'm asking the controller to build a new address to go along with the new employer.
Averell: you mentioned encapsulating... something in the model using a 'setter' method. I have no idea what you mean by this - could you please explain how this works (or direct me to an example elsewhere)? With tsdbrown's answer I have managed to create the behavior I want... if there's a more elegant way to accomplish the same thing I'd love to learn how.
Thanks very much.
Thanks to tsdbrown for answering the current_user.save problem!
You are setting it on the current_user object then redirecting. On a redirect I presume your app will reload the current_user in which case it will be gone as you didn't save it. (call current_user.save before redirecting if you want to save it) You are the passing the employer.id/current_user.user_type_id into the home action so it should be there for you to pick up on with params[:id]
Does that help? If not maybe I'm not understanding your question.
As has been said, your immediate problem is that you don't save the record. That said, your model looks a bit awkward, linking to different tables depending on a type field.
My spontaneous ideas would be to consider
Having "User" as an STI model
Having the "UserType" as an STI model
Having an explicit relation (that is, using a separate field in the user model) between it and each of the user types
If you really want to stick with your approach , it should at least be encapsulated into the model, so you pass in the user type (e.g. employee) object and all the fields are set automatically in the setter method. you should use Rails' polymorphic associations.
Also, I am not quite sure what you want to accomplish here:
1.times {#employer.addresses.build}
Edited - Polymorphic associations
Rails actually provides the feature you want out of the box. Check the documentation for the features here.. Before we post any more examples, you may have a look at this question which looks much like what you want to do.

Resources