I have followed tutorial from Simply Rails 2, but I got NoMethodError
this is my story.rb model :
class Story < ApplicationRecord
validates :name, :link, presence: true
has_many :votes do
def latest
find :all, :order => 'id DECS', :limit => 3
end
end
end
this is my show.html.haml view :
%h2
%span#vote_score
Score: #{#story.votes.size}
= #story.name
#vote_form
= form_for #story, method: "post", :url => story_votes_path(#story) do
= submit_tag 'shove it'
%ul#vote_history
- if #story.votes.empty?
%em No shoves yet!
- else
= render :partial => 'votes/vote', :collection => #story.votes.latest
%p
= link_to #story.link, #story.link
Full Error desciption :
undefined method `latest' for #<Vote::ActiveRecord_Associations_CollectionProxy:0x00007f4234aea9c0>
Did you mean? last
Extracted source (around line #15):
%em No shoves yet!
- else
= render :partial => 'votes/vote', :collection => #story.votes.latest ##this is line 15
%p
= link_to #story.link, #story.link
Rails.root: /home/kevin/shovell2
Can anyone help me to solve this ? Thank You.
Are you sure you want to pass that block to has_many? I think what you want is declaring that latest method in the Vote model, maybe a scope
scope :latest, -> { order('id DESC').limit(3) }
or the same with a method
def self.latest
order('id DESC').limit(3)
end
A note about the version of rails you're using: you are making your model subclassing ApplicationRecord, and that exists in the latest versions of rails (probably from rails 5 upward). On the other hand this method seems from a very old version of rails, probably 2 find :all, :order => 'id DECS', :limit => 3
You need to add latest to your Vote model not to your Story model. And it can be like this instead of method.
class Vote < ApplicationRecord
...
scope :latest, -> { order('id DESC').limit(3) }
...
end
Related
How to use model's properties at Rubi on Rails' views?
My model:
class EntryItems < ActiveRecord::Base
attr_accessible :created_on, :updated_on, :activity_type, :date_from, :date_to
attr_protected :user_id, :from_tyear, :from_tmonth, :from_tday
##date_from = nil
def date_from
##date_from = Date.new(read_attribute(:from_tyear), read_attribute(:from_tmonth), read_attribute(:from_tday))
end
def date_from=(date_from_value)
if date_from_value.is_a?(Time)
##date_from = date_from_value.to_date
end
write_attribute(:from_tyear, date_from_value.year)
write_attribute(:from_tmonth, date_from_value.mon)
write_attribute(:from_tday, date_from_value.mday)
end
end
My controller:
class ItemEntriesSetupController < ApplicationController
unloadable
def index
#item_entry = DayoffEntries.new(:user => User.current, :created_on => Time.now, :updated_on => Time.now)
#item_entry_post_url = url_for(:controller => 'item_entries_setup', :action => 'update')
end
def update
#code is skipped
end
end
My view:
<%= form_tag(#item_entry_post_url) do %>
<dl>
<label>Issues:</label>
<%=date_field_tag 'date_from', true, #item_entry.date_from %>
</dl>
<%= submit_tag(l(:button_create)) %>
<% end %>
It fails with underfined method 'div' for nil:NilClass.
What do I do wrong?
(I'm really new in Ruby on Rails, so I still have problems with finding right practicies, because there're plenty of code examples written n different style).
I got 2 Tables/Models: Paths and Questions. Each question belongs to a path
My question.rb:
class Question < ActiveRecord::Base
belongs_to :path
end
My path.rb
class Path < ActiveRecord::Base
has_many :questions
end
Everything works fine like
p = Path.last
Path.questions
returns everything I need but I'm returning a json response like this:
#path = Path.find_by_id(params[:id])
render :status=>200, :json => {:status => "success", :path => #path, :message => "Showing path"}
That answer doesn't include the questions for the path of course. What do I have to change to include all questions belonging to that path? I know I could just add :path_questions => #path.questions but is there no way to include the questions without a new return variable? I hope it's clear what I mean.
I do it like that in a Rails 5 API app:
BooksController
def index
#books = Book.limit(params[:limit])
render json: #books, include: ['author'], meta: { total: Book.count }
end
In the above situation, a Book belongs_to Author.
This is quite hacky, but should work:
:path => #path.as_json.merge(:questions => #path.questions.as_json)
Eventually you can override as_json inside your model:
def as_json(options={})
includes = [*options.delete(:include)]
hash = super(options)
includes.each do |association|
hash[self.class.name.underscore][association.to_s] = self.send(association).as_json
end
hash
end
And then just call: :path => #path.as_json(:include => :questions)
Note it will also add :include option to to_json method.
Background:
I followed the tutorial here to setup a polymorphic User favorites data model in my application. This allows me to let a User make pretty much any Entity in the system which I add 'has_many :favorites, :as => :favorable' line to its model a favorite. I plan on using this to implement a Facebook style 'Like' system as well as several other similar systems.
To start off I added the favoritability to a Post model (each user can create status updates like on Facebook). I have it all done and unit tested so I know the data model is sound and functioning from either side of the relationship (User and Post).
Details:
I have a Home controller with a single index method and view.
on the index view I render out the posts for the user and the user's friends
I want the user to be able to like posts from their friends
The Posts controller has only a create and a destroy method with associated routes (not a full fledged resource) and through the Post method via AJAX posts are created and deleted without issue
Where I am stuck
How do I add the link or button to add the post to the user's Favorites?
According to the tutorial the way to create a new Favorite through the polymorphic association is to do it from the Post.favorites.build(:user_id => current_user.id). From this direction the build handles pulling out the Post's ID and TYPE and all I have to do is pass in the user's id
Do I use an AJAX form post to a Favorites controller with a Create and Destroy method similar to the Post controller?
I am still struggling to uncross the wires in my brain from ASP.Net N-Tier web application development over to Rails MVC. Hasn't been too bad until now ;)
I bet there are Gems out there that might do this but I need to learn and the best way is to suffer through it. Maybe a tutorial or sample code from someone who has implemented liking functionality within their application would be helpful.
Thanks in advance for the assistance!
Jaap, I appreciate your comment on my question. After writing the question I pretty much didn't want to wait because the real learning takes place through trial and error, so I errored it up ;)
It turns out that what you suggested was pretty much in line with exactly what I ended up doing myself (it's always nice to find out that what you decide to do is what others would do as well, I love the sanity check value of it all).
So here is what I did and it is all working through post-backs. Now I just need to implement AJAX and style it:
My favorite model because my Polymorphic Favorites model requires that an Entity can only be favorited once by a user I added to the validations 'Scopes' which indicate that for each attribute it has to be unique in the scope of the other 2 required attributes. This solves the issue of multiple favorites by the same user.
class Favorite < ActiveRecord::Base
before_save :associate_user
belongs_to :favorable
belongs_to :user
# Validations
validates :user_id, :presence => true,
:uniqueness => {:scope => [:favorable_id, :favorable_type], :message => "item is already in favorites list."}
validates :favorable_id, :presence => true,
:uniqueness => {:scope => [:user_id, :favorable_type], :message => "item is already in favorites list."}
validates :favorable_type, :presence => true,
:uniqueness => {:scope => [:favorable_id, :user_id], :message => "item is already in favorites list."}
# Callbacks
protected
def associate_user
unless self.user_id
return self.user_id = session[:user_id] if session[:user_id]
return false
end
end
end
My User Model (that which is relevant): I added 2 methods, the get_favorites which is the same as favorable one from the tutorial and a Favorite? method which checks to see if the Entity in question has already been added to the user's favorites.
class User < ActiveRecord::Base
# Relationships
has_many :microposts, :dependent => :destroy
has_many :favorites
# Methods
def favorite?(id, type)
if get_favorites({:id => id, :type => type}).length > 0
return true
end
return false
end
def get_favorites(opts={})
# Polymorphic Favoritability: allows any model in the
# application to be favorited by the user.
# favorable_type
type = opts[:type] ? opts[:type] : :topic
type = type.to_s.capitalize
# add favorable_id to condition if id is provided
con = ["user_id = ? AND favorable_type = ?", self.id, type]
# append favorable id to the query if an :id is passed as an option into the
# function, and then append that id as a string to the "con" Array
if opts[:id]
con[0] += " AND favorable_id = ?"
con << opts[:id].to_s
end
# Return all Favorite objects matching the above conditions
favs = Favorite.all(:conditions => con)
case opts[:delve]
when nil, false, :false
return favs
when true, :true
# get a list of all favorited object ids
fav_ids = favs.collect{|f| f.favorable_id.to_s}
if fav_ids.size > 0
# turn the Capitalized favorable_type into an actual class Constant
type_class = type.constantize
# build a query that only selects
query = []
fav_ids.size.times do
query << "id = ?"
end
type_conditions = [query.join(" AND ")] + fav_ids
return type_class.all(:conditions => type_conditions)
else
return []
end
end
end
end
My Micropost Model (that which is relevant): note the Polymorphic association in the has_many relationship titled :favorites.
class Micropost < ActiveRecord::Base
attr_accessible :content
# Scopes
default_scope :order => 'microposts.created_at DESC'
# Relationships
belongs_to :user
has_many :favorites, :as => :favorable # Polymorphic Association
# Validations
validates :content, :presence => true, :length => { :minimum => 1, :maximum => 140 }
validates :user_id, :presence => true
end
My Micropost Form: as you can see I am passing in the entity that will be mapped to the Favorite model as a local variable to the 2 Favorite forms as 'local_entity'. This way I can pull out the ID and the TYPE of the Entity for the Polymorphic association.
<div class="post">
<span class="value">
<%= micropost.content %>
</span>
<span>
<% if current_user.favorite?(micropost.id, micropost.class.to_s) %>
<%= render :partial => 'favorites/remove_favorite', :locals => {:local_entity => micropost} %>
<% else %>
<%= render :partial => 'favorites/make_favorite', :locals => {:local_entity => micropost} %>
<% end %>
</span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
</span>
<div class="clear"></div>
</div>
My Make Favorite Form:
<%= form_for current_user.favorites.build do |f| %>
<div><%= f.hidden_field :favorable_id, :value => local_entity.id %></div>
<div><%= f.hidden_field :favorable_type, :value => local_entity.class.to_s %></div>
<div class="actions"><%= f.submit "make favorite" %></div>
<% end %>
My Remove Favorite Form:
<%= form_for current_user.get_favorites(
{:id => local_entity.id,
:type => local_entity.class.to_s}),
:html => { :method => :delete } do |f| %>
<div class="actions"><%= f.submit "remove favorite" %></div>
<% end %>
If you don't want to call this on the current_user, you would have to have these routes in your config/routes.rb to make nested routes for favorites on a user. I assume you have a Favorite model which belongs_to :user:
resources :users do
resources :favorites
end
Then make sure your favorites controller loads the user in some kind of before_filter:
def load_user
#user = User.load params[:user_id]
end
And then you can render a remote form to create a new favorite for any kind of object (it will only show a button):
<%= remote_form_for [#user, Favorite.new] do |f| -%>
<%= f.hidden_field :favorable_type, object.class.to_s %>
<%= f.hidden_field :favorable_id, object.id %>
<%= f.submit 'Like' %>
<%- end -%>
You would have to render that form as a partial sending along an object (e.g. a Post) and then it will create an AJAX POST call to /users/:id/favorites/ which will create the favorite object and render some kind of javascript response in a create.rjs file.
I hope this helps. The code itself is untested, but it might get you moving.
I am trying to create a unique json data structure, and I have run into a problem that I can't seem to figure out.
In my controller, I am doing:
favorite_ids = Favorites.all.map(&:photo_id)
data = { :albums => PhotoAlbum.all.to_json,
:photos => Photo.all.to_json(:favorite => lambda {|photo| favorite_ids.include?(photo.id)}) }
render :json => data
and in my model:
def as_json(options = {})
{ :name => self.name,
:favorite => options[:favorite].is_a?(Proc) ? options[:favorite].call(self) : options[:favorite] }
end
The problem is, rails encodes the values of 'photos' & 'albums' (in my data hash) as JSON twice, and this breaks everything... The only way I could get this to work is if I call 'as_json' instead of 'to_json':
data = { :albums => PhotoAlbum.all.as_json,
:photos => Photo.all.as_json(:favorite => lambda {|photo| favorite_ids.include?(photo.id)}) }
However, when I do this, my :favorite => lambda option no longer makes it into the model's as_json method.......... So, I either need a way to tell 'render :json' not to encode the values of the hash so I can use 'to_json' on the values myself, or I need a way to get the parameters passed into 'as_json' to actually show up there.......
I hope someone here can help... Thanks!
Ok I gave up... I solved this problem by adding my own array methods to handle performing the operations on collections.
class Array
def to_json_objects(*args)
self.map do |item|
item.respond_to?(:to_json_object) ? item.to_json_object(*args) : item
end
end
end
class Asset < ActiveRecord::Base
def to_json_object(options = {})
{:id => self.id,
:name => self.name,
:is_favorite => options[:favorite].is_a?(Proc) ? options[:favorite].call(self) : !!options[:favorite] }
end
end
class AssetsController < ApplicationController
def index
#favorite_ids = current_user.favorites.map(&:asset_id)
render :json => {:videos => Videos.all.to_json_objects(:favorite => lambda {|v| #favorite_ids.include?(v.id)}),
:photos => Photo.all.to_json_objects(:favorite => lambda {|p| #favorite_ids.include?(p.id)}) }
end
end
I think running this line of code
render :json => {:key => "value"}
is equal to
render :text => {:key => "value"}.to_json
In other words, don't use both to_json and :json.
I'm trying to Factory a Post associated with a Vote. So that Post.votes would generate the Vote's that are associated with it.
Factory.define :voted_post, :parent => :post, :class => Post do |p|
p.association :votes, :factory => :vote
end
And my rspec2 is relatively straightforward :
describe "vote scores" do
it "should show me the total vote score" do
#post = Factory(:voted_post)
#post.vote_score.should == 1
end
end
So why would it return this error :
Failures:
1) Post vote scores should show me the total vote score
Failure/Error: #post = Factory(:voted_post)
undefined method `each' for #<Vote:0x105819948>
ruby 1.8.7 (2009-06-12 patchlevel 174) [universal-darwin10.0]
Rails 3.0.0
Factory.define :voted_post, :parent => :post, :class => Post do |p|
p.association :votes, :factory => :vote
end
Is the same as trying to go
some_voted_post.votes = Factory(:vote)
Basically you're attempting to assign a single vote as an array.
EDIT
You can have an array containing a single vote, but you can't just have a single vote.
It's the difference between:
some_voted_post.votes = Factory(:vote)
and
some_voted_post.votes = [Factory(:vote)]
The former is not an array, and therefore does not work, the latter is an array.
If you want to assign has_many association which expects array and not a single value, you should use the long form:
Factory.define :voted_post, :parent => :post, :class => Post do |p|
p.votes { |vote| [vote.association(:vote)] }
end
And encapsulate the creation of the association with [] to ensure that array would be returned