I am using Rails 4.
I have two resources: articles and subarticles. subarticles are nested into articles.
I currently have a random button which takes the user to a random article. However, I would like it to only take them to an article page in which the subarticle is present.
What is the best way to go about this? I'm having trouble finding documentation.
Here is my random method in the articles_controller:
#items = Article.all
#randitem = #items[rand(#items.count)]
and in the view:
<%= link_to "Random Page", article_path(#randitem) %>
Did you setup a counter_cache? If not, I would recommend you to do so as it will allow you to do what you want in a more elegant way (with less code and less database query as well) : http://guides.rubyonrails.org/association_basics.html
class Article < ActiveRecord::Base
# The article table needs then to have a `subarticles_count` field
has_many :subarticles
end
class Subarticle < ActiveRecord::Base
belongs_to :article, counter_cache: true
end
Then in your controller, you can query the articles that have subarticles :
class ArticlesController < ApplicationController
def index
#items = Article.includes(:subarticles).where('subarticles_count > 0')
#randitem = #items[rand(#items.count)]
end
end
By the way, it's cleaner to use the Ruby sample method to get a random item from a collection :
class ArticlesController < ApplicationController
def index
#items = Article.includes(:subarticles).where('subarticles_count > 0')
#randitem = #items.sample
end
end
Related
I'm working with the acts_as_votable gem for a project that will allow users to 'like' their favorite courses and their favorite guides. The favorited guides will then show up on one page and the favorited courses on another. I'm having trouble with retrieving model specific results in my controller below is code that works but is not scoping to a specific controller.
class FavoritesController < ApplicationController
def guides
end
def courses
user = current_user
#courses = user.find_up_voted_items
end
end
This is the only code I've gotten to work, I realize there is nothing in the controller currently to narrow down the results to a specific model but I wasn't able to get anything I tried to work.
From the acts_as_votable docs:
Members of an individual model that a user has voted for can also be
displayed. The result is an ActiveRecord Relation.
#user.get_voted Comment
#user.get_up_voted Comment
#user.get_down_voted Comment
https://github.com/ryanto/acts_as_votable
So in your case I would use:
class FavoritesController < ApplicationController
before_action :get_user, only: %i[guides courses]
def guides
#guides = #user.get_up_voted Guide
end
def courses
#courses = #user.get_up_voted Course
end
private
def get_user
#user = current_user
end
end
Trying to figure our how to set up associations in form.
I have 3 models:
class Request < ActiveRecord::Base
has many :answers
has many :users, through: :answers
end
class Answer < ActiveRecord::Base
belongs to :user
belongs to :request
end
class User < ActiveRecord::Base
has many :answers
has many :requests, through: :answers
end
I am trying to figure out: how to have a User link to Answer#new from Request#Show, and then create an Answer record passing in the Request#Show request_id from the previous page - creating an association between the User's Answer and the Request he was viewing.
My method of doing this now is: I flash the request_id value on Request#Show, and then when a User links to Answer#new, it passes the flashed value into a hidden form tag on Answer#new. This does not seem like the best way to do this.
Any thoughts?
Kudos for the creative approach using flash, however your right there is an easy way. You can pass parameters much between controllers just like passing parameters between methods using the route names.
I didn't quite follow what it was you were trying to achieve in this case but it looks like this blog entry here should get you started..
https://agilewarrior.wordpress.com/2013/08/31/how-to-pass-parameters-as-part-of-the-url-in-rails/
Good luck!
User link to Answer#new from Request#Show
This can be achieved with either sessions or nested resources (or both!). Let me explain:
I would definitely add a nested resource to your requests routes:
#config/routes.rb
resources :requests do
resources :answers, only: [:new, :create] #-> url.com/requests/:request_id/answers [POST]
end
This gives you the ability to call a "nested" route (IE one which sends data to a child controller, and requires "parent" data to be appended to the request).
In your case, you want to create an answer for a request. The most efficient way is to use a routing structure as above; this will allow you to use the following controller method:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def new
#request = Request.find params[:request_id]
#answer = #request.answers.new
end
def create
#request = Request.find params[:request_id]
#answer = #request.answers.new answer_params
#answer.save
end
private
def answer_params
params.require(:answer).permit(:title, :body)
end
end
The above gives you the ability to create an answer by passing the request_id to the nested route. You must note the corresponding route will require a POST method in order to work.
You don't need the new method. If you wanted it, it can easily be handled with the above structure.
Passing the user is a little more tricky.
You can either use the routes, or set a session.
I would personally set a session (it's cleaner):
#app/controllers/requests_controller.rb
class RequestsController < ApplicationController
def show
session[:user_id] = #user.id #-> I don't know how you populate #user
end
end
This will give you the ability to access this session here:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def new
user = User.find session[:user_id]
end
end
#app/views/requests/show.html.erb
<%= link_to "New Answer", request_new_answer_path(request) %>
--
If you're using Devise, the user object should be available in the current_user object (which means you don't have to set session[:user_id]):
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def new
## current_user available here if using devise
end
end
To assign a #user to the new answer record, just do this in answers#create:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
...
def create
#request = Request.find params[:request_id]
#answer = #request.answers.new answer_params
#answer.user = current_user
#answer.save
end
end
Something like this worked for me:
I have two models (Formula and FormulaMaterial)
Formula has_many FormulaMaterials, which belongs to Formula
My Formula controller sets #formula like so:
#formula = Formula.find(params[:id])
I list my Formula Materials in my Formula show.html.erb by declaring it in my Formula controller like so:
#formula_materials = FormulaMaterial.where(:formula_id => #formula)
When I want to add a new FormulaMaterial to my Formula, the "New Formula Material" button in my show.html.erb file looks like this:
<%= link_to 'Add Material To Formula', new_formula_material_path(:formula_id => #formula), class: "btn btn-success" %>
In the "new_..._path" I set the associated id to the #formula variable. When it passes through to the new.html.erb for my FormulaMaterial, my URL looks like so:
http://localhost:3000/formula_materials/new?formula_id=2
In my FormulaMaterial new.html.erb file, I created a hidden_field that sets the value of the association by using "params" to access the formula_id in the URL like so:
params[:formula_id] %>
I am not sure if this is the best way to do this, but this way has allowed me to pass through the view id from the previous page as a hidden, associated and set field in the form every time.
Hope this helps!
Trying to learn rails, building very simple app.
by going to
<%= link_to 'Show Orders', orders_path %>)
I get list of orders for one customer. There are some attributes stored for customer, including first name and second name.
I try to create header with text "Listing orders for John":
<h1>Listing Orders for <%= #customer.try(:name) %></h1>
Result is:
Listing Orders for
What do i do wrong?
UPD:
Well, customer and order actually "nicknames" for Feature and Assets (relations are the same), for easier understanding. Sorry for confusing.
So, Assets controller show method:
class AssetsController < ApplicationController
before_action :set_asset, only: [:show, :edit, :update, :destroy]
def show
end
private
def set_asset
#asset = Asset.find(params[:id])
end
Feature model:
class Feature < ActiveRecord::Base
has_many :assets, dependent: :destroy
end
Asset model:
class Asset < ActiveRecord::Base
belongs_to :feature
end
try returns nil instead of raising exception.So that customer.name is nil.I am sure it wont be coming for all customers.remove try and code will raise exception as name is nil
Ok, finally managed it. A bit different approach - i now show assets on features pages.
class FeaturesController < ApplicationControll
def show
#assets = Asset.where(feature_id: #feature.id)
end
And in features/show.html.erb
<h2>Assets</h2>
<%= render #feature.assets %>
Seems like total magic to me, but still works just like needed.
This is closed now.
Many presenters, have something of the form of
class MyClassPresenter < SimpleDelegator
def something_extra
end
end
view_obj = MyClassPresenter.new my_class_instance
I want to transverse the instance:
view_obj.nested_obj.nested_obj.value
This means creating multiple presentation objects, which in effect start just copying the models.
class MyClassPresenter < SimpleDelegator
def something_extra
end
def nest_obj
AnotherPresenter.new(__get_obj__.nest_obj)
end
end
To demonstrate a real world example a bit better
class UserPresenter < SimpleDelegator
def home_page_url
"/home/#{__get_obj__.id}"
end
end
class MyController < ApplicationController
def show
#user = UserPresenter.new(current_user) # devise's current_user
end
end
/my/show.html.slim
/! Show profile comments
- for comment in #user.profile.comments
| Comment on:
= comment.created_at
= comment.content
The main object being passed in is #user, however, the presenter doesn't cover that far. I could create #comments, but I would like the code to be more flexible to however the front-end engineers wanted to take it.
How have other people handled multiple layers for a presenter?
Would the code look something like this? (ugh)
/! Show profile comments
- for comment in #user.profile.comments
- display_comment = CommentPresenter.new(comment)
| Comment on:
= display_comment.comment.created_at
= display_comment.content
-daniel
I'm having an issue coming up with a good way to do the following. I have a very generic Org model and User model. Org has_many :users, and User belongs_to :org.
I am trying to find a way of showing a list of users that is not restricted by Org, but also show a list of User's that is restricted by Org. I know I could nest the routes, and just have two different routes like
map.resources :users
map.resources :orgs, :has_many => :users
The problem is that they both go back to the same actions in the User controller. The controller code starts to get very messy because I am having to check for the existence of an :org_id param. Then I have to decide whether to return the normal results of a find call on User, or a find that is scoped to an Org. I'm not sure what the best solution is here, or what the best practice is. If someone with some knowledge on this could please enlighten me, it would be great.
Another way of doing this without a plugin would be to use named_scope. You can create a named scope in User that filters by org_id if it's not empty.
class User < ActiveRecord::Base
belongs_to :org
named_scope :by_org, lambda{|org| org.blank? ? {} : { :conditions => ['org_id = ?', org] }}
end
And in the controller just use your named scope. That way if you eventually supply more filter options in the controller you don't need to duplicate them:
class UsersController < ApplicationController
def index
#users = User.by_org(params[:org_id]).all
...
end
end
Not really the answer you were probably looking for on a technical level but I use a plugin for my projects that takes care of that mess for me. Take a look at make_resourceful.
make_resourceful do
actions :all
belongs_to :org
end
It will figure out the rest for you, no need to define your standard crud action. It will even detect scoping and scope it for you. (unless that's an other plugin i'm using I forgot about)
I use resource_controller plugin for most cases. With it, you just put:
class UsersController < ApplicationController
resource_controller
belongs_to :org
end
It works with nested and not-nested resources.
If you don't want to use additional plugin, your controller still won't be very complicated.
class UsersController < ApplicationController
def index
#org = Org.find(params[:org_id]) unless params[:org_id].blank?
#users = params[:org_id].blank? ? User.all : #org.users
...
end
end
What I usually do is this:
class UsersController < ApplicationController
def index
root = Org.find(params[:org_id]) if params[:org_id]
root = User if root.nil?
#users = root.all(:conditions => {...}, :order => "...")
end
end
I'm basically doing a tree-walk. As conditions are added, I simply change the root of the #find call. When I'm done evaluating conditions, I call the final #find / #first / #all method and I'm done.
This also works if you have multiple named scopes:
class UsersController < ApplicationController
def index
root = Org.find(params[:org_id]) if params[:org_id]
root = User if root.nil?
root = root.named(params[:name]) if params[:name]
root = root.registered_after(params[:registered_at]) if params[:registered_at]
# more conditions, as required
#users = root.all(:conditions => {...}, :order => "...")
end
end