Rails: Cannot access column from a join-table in a partial - ruby-on-rails

I want to access a column from a join-table in a partial. When I access this column in a "normal" view (e.g. fakeposts#index) it is working, however if I want to do the same in a partial, it does not work.
Example:
I have a users and a fakeposts table. For each user I want to display the fakeposts in a specific (randomized) order - namely based on a randomized_time column -, which is saved in a join table:
randomized_posts table:
user_id | fakepost_id | randomized_time
My Models look like this:
#user.rb
has_many :randomized_fakeposts
#fakepost.rb
has_many :randomized_fakeposts
# randomized_fakepost.rb
belongs_to :fakepost
belongs_to :user
In my fakepost_controller.rb I want to get the fakeposts for the current_user and add the column "randomized_time" to my selection:
My fakepost_controller.rb
def index
#fakeposts = Fakepost.joins(:randomized_fakeposts).where(randomized_fakeposts: {user_id: current_user.id}).select("fakeposts.*, randomized_fakeposts.randomized_time")
end
This is working: index.html.erb
<% #fakeposts.each do |post| %>
<%= post.randomized_time %>
<% end %>
This is not working: index.html.erb and my partial
#index.html.erb
<% #fakeposts.each do |post| %>
<%= render :partial => "layouts/individual-post", :locals => {:post => post} %>
<% end %>
#layouts/_individual-post.html.erb
<%= post.randomized_time %>
Error message
=> undefined method `randomized_time' for #<Fakepost:0x007f7752eeab58>
However, something like <%= post.created_at %> is working fine in my partial so I guess the way I call my partial is correct?

Ok, I've found the source of the error and for "outsiders" it was impossible to find (since I did not provide the whole code).
In my FakepostsController.rb I also had:
def index
# This line is the same line like in my original post
#fakeposts = Fakepost.joins(:randomized_fakeposts).where(randomized_fakeposts: {user_id: current_user.id}).select("fakeposts.*, randomized_fakeposts.randomized_time")
...
# This was the line which causes the error:
#pinned_fakeposts = Fakepost.where(pinned: true)
end
Because my #pinned_fakeposts did not include my "randomized_time" column (I did not .joins it), the error occured.
So what I did was:
#pinned_fakeposts = Fakepost.where(pinned: true).joins(:randomized_fposts).where(randomized_fakeposts: {user_id: current_user.id}).select("fakeposts.*, randomized_fakeposts.randomized_time")
Conclusion
So everything else was fine and it is possible to get your join column in a partial :).

Related

Prevent upvote model from being called for every comment

I have three models: User, Comment and Upvote. User-to-Comment has a one-to-many relation, Comment-to-Upvote has a one-to-many relation and User-to-Upvote has a one-to-many relation.
I want to do something similar to the upvoting done on Stackoverflow. So when you upvote/downvote the arrow will highlight and remain highlighted even if you refresh the page or come back to the page days/weeks later.
Currently I am doing this:
<% if Upvote.voted?(#user.id, comment.id) %>
<%= link_to '^', ... style: 'color: orange;'%>
<% else %>
<%= link_to '^', ... style: 'color:black;'%>
<% end %>
where the voted? method looks like this:
def self.voted?(user_id, comment_id)
find_by(comment_id: comment_id, user_id: user_id).present?
end
So if I have 10 comments on a page, this will load an upvote from my database 10 times, just to check if it exist!
There has to be a better way to go about doing this, but I think my brain stopped working, so I can't think of any.
Assuming you have properly set relations
# user.rb
class User
has_many :upvotes
end
we can load comments, current user and his upvotes:
# comments_controller.rb
def index
#comments = Comment.limit(10)
#user = current_user
user_upvotes_for_comments = current_user.upvotes.where(comment_id: #comments.map(&:id))
#upvoted_comments_ids = user_upvotes_for_comments.pluck(:comment_id)
end
And then change if condition in view:
# index.html.erb
<% if #upvoted_comments_ids.include?(comment.id) %>
<%= link_to '^', ... style: 'color: orange;'%>
<% else %>
<%= link_to '^', ... style: 'color:black;'%>
<% end %>
It will require only 2 DB queries. Hope it helps.
We can do it the following way if you want it to be handled by a single query.
Lets make sure the relations are proper
# user.rb
class User < ActiveRecord::Base
has_many :comments
has_many :upvotes
end
# comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
has_many :upvotes
end
# upvote.rb
class Upvote < ActiveRecord::Base
belongs_to :user
belongs_to :comment
end
Then in the controller
def index
current_user = User.first # current_user may come from devise or any authentication logic you have.
#comments = Comment.select('comments.*, upvotes.id as upvote').joins("LEFT OUTER JOIN upvotes ON comments.id = upvotes.comment_id AND upvotes.user_id = #{current_user.id}")
end
And in view
# index.html.erb
<% #comment.each do |comment| %>
<% link_color = comment.upvote ? 'orange' : 'black' %>
<%= link_to '^', ...style: "color: #{link_color}" %>
<% end %>
# And all of your logics ;)
If you're limited to N comments per page then you can probably do this in two queries using the limit and offset methods to return the 1st, 2nd, ... ith set of N comments for the ith page, something like (syntax may be off, Ruby isn't my primary language)
comment_ids =
Comments.select("comment_id")
.where(user_id: user_id)
.order(post_date/comment_id/whatever)
.offset(per_page * (page_number - 1)) // assumes 1-based page index
.limit(per_page)
This gives you a list of comment_ids which you can use to query Upvote:
upvoted_comments =
Upvotes.select("comment_id")
.where(user_id: user_id, comment_id: comment_ids)
If you're sorting the comment_ids by a column that also exists in Upvote (e.g. if you're sorting by comment_id) then you can replace the Upvote set query with a range query.
Put upvoted_comments in a hash and you're good to go - if the comment_id is in the hash then it's been upvoted, else not.
I'm not sure this will avoid excess queries in this state but maybe you could include the upvotes when you fetch comments:
#comments = Comment.includes(:upvotes).where(foo: 'bar').limit(10)
Then in the view:
<%=
link_color = comment.upvotes.map(&:user_id).include?(#user.id) ? 'orange' : 'red'
link_to '^', ...style: "color: #{link_color}"
%>

Rails 4 HABTM stops working when I add "multiple true" to collection_select

I've googling and trying everything I could think of for the past couple of days to solve a relatively simple (I presume) issue with has_and_belongs_to_many relation.
I managed to successful use the HABTM relation to submit a single relation value. Here's the sample code:
Model:
class Livre < ActiveRecord::Base
has_and_belongs_to_many : auteurs
end
class Auteur < ActiveRecord::Base
has_and_belongs_to_many :livres
end
Controller:
def new
#livre = Livre.new
#auteurs = Auteur.all
end
def create
#livre = Livre.new(livre_params)
if #livre.save
redirect_to [:admin, #livre]
else
render 'new'
end
end
private
def livre_params
params.require(:livre).permit(:name, :auteur_ids)
end
View:
<% f.label :auteur %><br>
<% f.collection_select(:auteur_ids, #auteurs, :id, :name) %>
Posted Params:
{"utf8"=>"✓",
"authenticity_token"=>"mAXUm7MRDgJgCH00VPb9bpgC+y/iOfxBjXSazcthWYs=",
"livre"=>{"name"=>"sdfsdfd",
"auteur_ids"=>"3"},
"commit"=>"Create Livre"}
But when I try to add "multiple true" to the view's collection_select helper, the (now multiple) relation doesn't get saved anymore. Sample code:
(both Model and Controller unchanged)
View:
<% f.label :auteur %><br>
<% f.collection_select(:auteur_ids, #auteurs, :id, :name, {}, {:multiple => true}) %>
Posted Params:
{"utf8"=>"✓",
"authenticity_token"=>"mAXUm7MRDgJgCH00VPb9bpgC+y/iOfxBjXSazcthWYs=",
"livre"=>{"name"=>"sdfsdf",
"auteur_ids"=>["1",
"5",
"3"]},
"commit"=>"Create Livre"}
As you can see, the params for "auteur_ids" is now an array. That's the only difference.
What am I doing wrong?
Just to clarify: both piece of code are able to add a new record to the livres db table, but only the 1st code is able to add the appropriate record to the auteurs_livres db table. The second one simply does not insert anything into auteurs_livres.
(I run on ruby 1.9.3p194 and rails 4.0.1)
Thanks!
Answer
For the fine folks stuck with the same problem, here's the answer:
Edit your controller and change the permitted parameter from :auteur_ids to {:auteur_ids => []}
params.require(:livre).permit(:name, {:auteur_ids => []})
And it now works :)
For the fine folks stuck with the same problem, here's the answer:
Edit your controller and change the permitted parameter from :auteur_ids to {:auteur_ids => []}
params.require(:livre).permit(:name, {:auteur_ids => []})
And it now works :)
You worked out the solution because Rails now expects auteur_ids to be an array, rather than a single item. This means that instead of just passing a single entity to the model, it will package the params as [0][1][2] etc, which is how you can submit your HABTM records now
There is a more Rails way to do this, which is to use accepts_nested_attributes_for. This is going to seem like a lot more work, but it will dry up your system, and also ensure convention over configuration:
Model
class Livre < ActiveRecord::Base
has_and_belongs_to_many : auteurs
accepts_nested_attributes_for :auteurs
end
class Auteur < ActiveRecord::Base
has_and_belongs_to_many :livres
end
Controller
def new
#livre = Livre.new
#livre.auteurs.build
#auteurs = Auteur.all
end
def create
#livre = Livre.new(livre_params)
if #livre.save
redirect_to [:admin, #livre]
else
render 'new'
end
end
private
def livre_params
params.require(:livre).permit(:name, auteur_attributes: [:auteur_id])
end
Form
<%= form_for #livre do |f| %>
<%= f.text_field :your_current_vars %>
<%= f.fields_for :auteurs do |a| %>
<%= a.collection_select(:auteur_id, #auteurs, :id, :name, {}) %>
<% end %>
<% end %>
This will submit the auteur_id for you (and automatically associate the livre_id foreign key in the HABTM model. Currently, this will only submit the number of objects which have been built in the new action -- so in order to add more items, you'll have to build more

Rendering rails partial with dynamic variables

I'm trying to render a partial based on the taxon the user is inside. In my application.html.erb layout I have the following line of code:
<%= render 'spree/shared/women_subnav' if #enable_women %>
In the taxons controller, inside the show method, I have:
#taxon_id = params[:id].split('/').first
And in taxons#show I have:
<% if #taxon_id == params[:id].split('/').first %>
<%= "#enable_#{#taxon_id}" = true %>
<% end %>
When I run this I get a SyntaxError. But in taxons#show If I just enter:
<% if #taxon_id == params[:id].split('/').first %>
<%= "#enable_#{#taxon_id}" %>
<% end %>
without the '= true' then the page renders, outputting '#enable_women'. So I know it's getting the correct variable, I just need that variable to be set to true. What am I missing?
Thanks so much.
First of all I would like to give you some heads-up:
calling first on a user submittable input is not a great idea (what if I submit ?id=, it would return nil) also non utf-8 encoding will crash your app such as: ?id=Ж
Controllers are beast! I see you are setting the value of a true/false instance_variable in the view, please use controllers do define the logic before rendering its output. especially when parameter dependant.
so for a solution:
in your controller as params[:id] should suggest an INT(11) value:
def action
# returning a Taxon should be a good idea here
#taxon = Taxon.find(params[:id])
# as I would give a Taxon class an has_many relation to a User
#users = #taxon.users
end
and in your action's view
<%= render :partial => "taxons/users", collection: #users %>
of course you would have the great ability to scope the users returned and render the wanted partial accordingly.
if you want more info about "The Rails way" please read:
http://guides.rubyonrails.org/
Have fun!
use instance_variable_set
instance_variable_set "#enable_#{#taxon_id}", true
just a reminder that it's better to do these things inside a controller.

Render a rails partial based on the id of an action

I'm building a small ecommerce site that sells a variety of mens and womens clothing. i would like to render a partial based on which taxonomy the user is in. For example, if the user is at mysite.com/t/women/pants I would like to render _women.html.erb, or, if the user is at mysite.com/t/men/shirts I would like to render _men.html.erb.
I have a Taxonomy model that has_many taxons, and the Taxon model has_many products.
In taxons_controller.rb I have:
def show
#taxon = Taxon.find_by_permalink(params[:id])
return unless #taxon
#taxonomy = Spree::Taxonomy.all
#taxon_title = Spree::Taxon.all
#searcher = Spree::Config.searcher_class.new(params.merge(:taxon => #taxon.id))
#searcher.current_user = try_spree_current_user
#searcher.current_currency = current_currency
#products = #searcher.retrieve_products
respond_with(#taxon)
end
And in taxons#show I have: (which I know is wrong)
<% #taxon_title.each do |title| %>
<% #taxonomy.each do |taxonomy| %>
<% if title.name == taxonomy.name %>
<%= render "spree/shared/#{title.name.downcase}" %>
<% end %>
<% end %>
<% end %>
When I go to mysite.com/t/women/long-sleeve the rails debugger displays :
controller: spree/taxons
action: show
id: women/long-sleeve
How do I access the id of the action im inside, so that in the controller/view I can do something like:
'if id equals 'women' render "spree/shared/#{title.name.downcase}"'
where title is the name of the taxonomy?
I imagine I need to find(params[:something] in the show action of the controller, but I'm a little unclear about params.
*
*
*
#beck03076 That's a great trick. Thank you very much. But it's still not working.
In my controller I put:
#taxon_id = Spree::Taxon.find(params[:id])
Then in the action I put:
render 'spree/shared/women' if #taxon_id == params[:id]
And when I load the page it says 'the page you were looking for doesn't exist'. My partial is in the correct directory. Is my syntax correct?
My params are:
{"controller"=>"spree/taxons", "action"=>"show", "id"=>"women/long-sleeve"}
Thanks again for your help!
Whenever you are unclear about params, just put the lines below in the action and execute the action.
p "****************************"
p params
p "****************************"
Now, goto the terminal in which you started your server.
Locate those two "*******" and everything thats in between them are params.
params is basically a ruby hash.
example:
params look like this, {:controller => "hello",:action => "bye", :id => 7, :others => "OK"}
In your controller to access the id, use params[:id].(=7)
to access others, use params[:others].(="OK")

Rails and ActiveRecord: Save a parent object while using find-or-create on has-many children

I am trying make a complex form (like the railscast) with repeated-auto-complete (modified by Pat Shaughnessy) work for creating articles with many authors (has-many :through). I've got it working as long as I willing to always create new authors when I save an article. How can I get my associated author records to only be created when they don't already exist and just get a join table update for when they do?
I know you can you use find-or-create to get this result with the parent object but I need it for the associated objects that are saved when #article.save is called for the article.
in articles.rb
before_save :remove_blank_authors
after_update :save_authors
def remove_blank_authors
authors.delete authors.select{ |author| author.fullname.blank?}
end
def new_author_attributes=(author_attributes)
author_attributes.each do |attributes|
authors.build(attributes)
end
end
def existing_author_attributes=(author_attributes)
authors.reject(&:new_record?).each do |author|
attributes = author_attributes[author.id.to_s]
if attributes
author.attributes = attributes
else
author.delete(author)
end
end
end
def save_authors
authors.each do |author|
author.save(false)
end
end
and the authors partial of my view:
<div class="author">
<% fields_for_author author do |f| %>
<%= error_messages_for :author, :object => author %>
<%= f.label :fullname, "Author Fullname:" %>
<%= f.text_field_with_auto_complete :author, :fullname, {}, {:method => :get } %>
<%= link_to_function "remove", "$(this).up('.author').remove()" %>
<% end %>
</div>
I'm using Rails 2.2.2.
The problem is that I can't see where I could use the find-or-create. At the point where the attributes for the authors are being built - new_author_attributes - I have nothing to search on - that is just pre-building empty objects I think - and at the point where the authors are being saved they are already new objects. Or am I wrong?
It depends on what version of Rails you are using but you should be able to do:
#article.authors.find_or_create_by_article_id(#author)
or
#author.articles.find_or_create_by_author_id(#article)
then Rails "should" fill in the details for you...
In order to use find_or_create you need to have a condition to evaluate it by. That condition (the by_author_id part) can be changed to any column in the Article model.
This is one of the convention features that Rails includes which I haven't been able to find too much info on, so if this is way off, sorry ;)

Resources