I want to narrow down a collection using params accessible through association
class PostsController < ApplicationController
load_and_authorize_resource
def index
if params[:event_id] then
#event = Event.find(params[:event_id])
if params[:category_id] then
#category = Category.find(params[:category_id])
#posts = Post.where(:event_id => #event.id,
:product => {:category_id => #category.id })
end
end
end
Gives me the error
No attribute named 'category_id' exists for table 'product'
But the column 'category_id' does exist in the table 'product'. Searching for this error hasn't shown me anything helpful yet. I've also tried using 'delegate' to make the attribute accessible but that hasn't worked either. I'm stumped.
Here is the schema
create_table "posts", :force => true do |t|
t.float "quantity"
t.datetime "deadline"
t.integer "product_id"
t.integer "event_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "products", :force => true do |t|
t.string "title"
t.text "desc"
t.text "ingredients"
t.float "deposit"
t.float "cost"
t.string "units"
t.float "quantity"
t.float "deadline_hours"
t.boolean "presell_option"
t.integer "user_id"
t.integer "category_id"
t.integer "club_id"
t.datetime "created_at"
t.datetime "updated_at"
end
Edit:
When I correct ':product' to ':products' I get this related error
SQLite3::SQLException: no such column: products.category_id: SELECT "posts".* FROM "posts" WHERE (("products"."category_id" = 2 AND "posts"."event_id" = 10))
This puzzles me further, the schema says I do have the category_id in the products table
You have to use the attribute name products instead of product. This is one one Rails exceptions to the rule.
#posts = Post.joins(:product).where(:event_id => #event.id,
:products => {:category_id => #category.id })
Try
#posts = Post.where(:event_id => #event.id,
:products => {:category_id => #category.id })
You can reduce your code and make your life easier by using MetaSearch gem. It's very nice tool! Video tutorial: here. Documentation: here.
Related
My schema is as follows:
create_table "ingredients", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "recipe_ingredients", force: :cascade do |t|
t.integer "recipe_id"
t.integer "ingredient_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "recipes", force: :cascade do |t|
t.string "name"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
On my index page, I would like to list all the recipes and then either have link or button that sorts the recipes by the amount of ingredients each recipe has. I've been toying with this for a couple hours now and getting closer but being a newb, my brain isn't quite making the leap. Right now I have some code in the RecipesController in #index that takes in some params from the view file:
<%= button_to "sort by ingredient amount", {:controller => "index", :action => "update", :order => true}, :method=>:post %>
the code in the controller so far is something like this:
def index
if params[:order] = true
Recipe.all.each do |r|
r.ingredients.each do |i|
???
end
end
else
#recipes = Recipe.all
end
end
I'm not sure how to use .order when accessing an association such as the recipes_ingredients. This could also be the totally bassackwards way to do it.
Yes you can find the data based on ingredients like these
def index
if params[:order] = true
Recipe.left_joins(:ingredients).group(:id).order('COUNT(ingredients.id) DESC')
else
#recipes = Recipe.all
end
end
I am using the PublicActivity gem and this was created in my database
schema.db
create_table "activities", :force => true do |t|
t.integer "trackable_id"
t.string "trackable_type"
t.integer "owner_id"
t.string "owner_type"
t.string "key"
t.text "parameters"
t.integer "recipient_id"
t.string "recipient_type"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
I am able to track all posts that are happening inside my website by using this inside the post.rb model
class Post < ActiveRecord::Base
include PublicActivity::Model
tracked except: :destroy, owner: ->(controller, model) { controller && controller.current_user }
end
By doing this, I can get the owner_id. What do I need to set recipient: to make it grab the value? Right now recipient_id is nil.
I want to ultimately use something like this in my view <%= link_to activity.recipient_id.name %> to get the name of the recipient where the activity was made to
I'm trying to set it to either the post_id or user_id but I'm getting an undefined local variable or method error.
Here's the table of the model that I'm tracking
create_table "postcomments", :force => true do |t|
t.text "content"
t.integer "user_id"
t.integer "post_id"
t.timestamp "created_at", :null => false
t.timestamp "updated_at", :null => false
t.text "comment_content"
end
I put this in the Model
assuming recipient_id is the user of tracked object. Otherwise change model.user as per your application
tracked recipient: ->(controller, model) { model && model.user }
I have a Post, Reply and a Vote model (polymorphic):
create_table "posts", :force => true do |t|
t.text "content", :limit => 255
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "title"
t.integer "replies_count", :default => 0, :null => false
t.integer "category_id"
end
create_table "replies", :force => true do |t|
t.text "content"
t.integer "post_id"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "votes", :force => true do |t|
t.integer "votable_id"
t.string "votable_type"
t.integer "user_id"
t.integer "polarity"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
I needed the total_votes of posts and replies so I created an instance method:
post.rb and reply.rb:
def total_votes
self.votes.map {|v| v.polarity }.sum
end
So I can use it to sort posts and replies:
homepage:
default_order = "created_at DESC"
params[:order_by] ||= default_order
#feed_items = #post.replies.paginate(page: params[:page],
per_page: 10).order(params[:order_by])
So now, I'm not very sure what do add after order_by in the view:
<span><%= link_to 'total_votes DESC', root_path(order_by: HERE) %></span>
I tried &:total_votes DESC and total_votes DESC but didn't work.
What's the right way of doing this? (I thought it was a bit of unnecessary to add a total_votes column to both posts and replies tables, but not sure if that is better for performance?)
I would start with using the database to do the summing for you, but this is a little bit tricky because you need to order by a value that is calculated (an "aggregate"), but let's set that aside.
Your view will display items in the order determined when the instance variable is set in the controller. So in the controller, you may detect that there's an order_by parameter (e.g. /feed?order_by=total_votes, and load the #feed_items accordingly, e.g.
def index
if params[:order_by] && params[:order_by] == 'total_votes'
sort_order = "total_votes DESC"
else
sort_order = "created_at DESC"
end
#feed_items = #post.replies.paginate(page: params[:page],
per_page: 10).order(sort_order)
end
then in your view, to change the sort order, create a link_to that adds the query string parameter (see the last example in the link_to api doc, e.g.
<%= link_to "Sort by Total Votes", root_path(:order_by => 'total_votes') %><br />
<%= link_to "Sort by Time", root_path %><br />
Your other question is about how to do the sorting by votes. This answer may provide a good start for that, and an answer to your performance question: Rails: Order by sum of two columns
I'm trying to understand the new arel engine in Rails 3 and I've got a question.
I've got two models, User and Task
class User < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :user
end
here is my routes to imply the relation:
resources :users do
resources :tasks
end
and here is my Tasks controller:
class TasksController < ApplicationController
before_filter :load_user
def new
#task = #user.tasks.new
end
private
def load_user
#user = User.where(:id => params[:user_id])
end
end
Problem is, I get the following error when I try to invoke the new action:
NoMethodError: undefined method `tasks' for #<ActiveRecord::Relation:0x3dc2488>
I am sure my problem is with the new arel engine, does anybody understand what I'm doing wrong?
Sorry guys, here is my schema.db file:
ActiveRecord::Schema.define(:version => 20100525021007) do
create_table "tasks", :force => true do |t|
t.string "name"
t.integer "estimated_time"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
end
create_table "users", :force => true do |t|
t.string "email", :default => "", :null => false
t.string "encrypted_password", :limit => 128, :default => "", :null => false
t.string "password_salt", :default => "", :null => false
t.string "reset_password_token"
t.string "remember_token"
t.datetime "remember_created_at"
t.integer "sign_in_count", :default => 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.string "username"
end
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
add_index "users", ["username"], :name => "index_users_on_username", :unique => true
end
I believe you want:
def load_user
#user = User.where(:id => params[:user_id]).first
end
Until you ask for a record it will stay a relation.
But find(params[:user_id]) will still work and return the record.
Does it work if you change your load_user method as shown below?
def load_user
#user = User.find(params[:user_id])
end
Also, I think you might need to change your new action to:
def new
#task = #user.tasks.build
end
Don't confuse the Arel gem's interface with the new ActiveRecord query interface. The syntax described here will not work: http://github.com/brynary/arel
ActiveRecord uses Arel under the hood but creates its own Arel-like API. Here's a brief look at the new ActiveRecord query interface: http://m.onkey.org/2010/1/22/active-record-query-interface
It's actually pretty simple. Here is one method...
def new
#task = #user.tasks.new
end
private
def load_user
# you must call a non-deferred operator to actually return
# the tuple that is connected to your tasks
#user = User.where(:id => params[:user_id]).first
end
Be sure to take a look at the seven part learning series I am doing on Active Relation. The first episode will help you understand what your error was more clearly. http://Innovative-Studios.com/#pilot
find() IS NOT deprecated in some instances as stated before. I would limit the use of find() for atomic values (where you are searching for a specific item/index) Anything that could possibly be collection based I would stick to the Where (restriction) clause wrapper in ActiveRecord for Arel.
I'm creating a Ruby on Rails application, and I'm trying to create/login/logout users.
This is the schema for Users:
create_table "users", :force => true do |t|
t.string "first_name"
t.string "last_name"
t.text "reputation"
t.integer "questions_asked"
t.integer "answers_given"
t.string "request"
t.datetime "created_at"
t.datetime "updated_at"
t.string "email_hash"
t.string "username"
t.string "hashed_password"
t.string "salt"
end
The user's personal information (username, first/last names, email) is populated through a POST. Other things such as questions_asked, reputation, etc. are set by the application, so should be initialized when we create new users. Right now, I'm just setting each of those manually in the create method for UsersController:
def create
#user = User.new(params[:user])
#user.reputation = 0
#user.questions_asked = 0
#user.answers_given = 0
#user.request = nil
...
end
Is there a more elegant/efficient way of doing this?
params[:user] is just a hash, you could create a hash and merge it with the params like
def create
params[:user].merge(
{
:reputation => 0,
:questions_asked => 0,
:answers_given => 0
...
}
)
#user = User.new(params[:user])
end
You could move this to your model if you wanted to remove that code from your controller, and just add an after_create filter..
but really if its just setting things to 0, set defaults in the database columns and you wont even have to handle it in your code..
create_table "users", :force => true do |t|
t.string "first_name"
t.string "last_name"
t.text "reputation", :default => 0
t.integer "questions_asked", :default => 0
t.integer "answers_given", :default => 0
t.string "request"
t.datetime "created_at"
t.datetime "updated_at"
t.string "email_hash"
t.string "username"
t.string "hashed_password"
t.string "salt"
end
If you cannot redo your migration, use change_column_default like
class SetDefaultsOnUsers < ActiveRecord::Migration
def self.up
change_column_default "users", "reputation", 0
end
def self.down
change_column_default "users", "reputation", default
end
end
You can use http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#M001909