How can I capture an instance generically? - ruby-on-rails

I'm using Rails 3.2.19 and Ruby 2.1.2. I've been googling around trying to figure this out, but perhaps I'm not searching for the right thing. Anyway, I'll try and be as concise as possible.
I have a few different models that all have a name attribute. In my views I want to somehow be able to access that name attribute regardless of the instance name passed into the view. Currently my various controllers create instances of their respective models. For instance:
class PagesController < ApplicationController
def show
#page = Page.find(params[:id])
respond_to do |format|
format.html
end
end
end
-
class ProductsController < ApplicationController
def show
#product = Product.find(params[:id])
respond_to do |format|
format.html
end
end
end
While I understand I could simply re-name the instances something generic, I was wondering if there was some way of accessing any/all instances while maintaining unambiguous instance names.
Basically something like this:
page.html.haml
%h1= resources[0].name #equates to #page.name
%h2= #page.some_other_attribute
or
product.html.haml
%h1= resources[0].name #equates to #product.name
%h2= #product.price
Where in each of the above resources[0] would be either #page or #product

You will have to define a route with an additional resource_type parameter to a generic controller or otherwise just include the resource_type into the url query parameter
/resources/product/17
or
/resources/17?resource_type=product
This will allow you to do the following in the controller
class ResourcesController < ApplicationController
def show
#resource = find_resource(params)
respond_to do |format|
format.html
end
end
private
def find_resource(params)
resource_klass = {
product: Product,
page: Page
}[params[:resource_type]]
resource_klass.find(params[:id])
end
end
Another Option would be to introduce another ResourceType Entity and define a polymorphic :has_one :belongs_to association to the actual resource entity (product, page). Then always search for ResourceTypes and load the polymorphic resource entity
class ResourceType < ActiveRecord::Base
belongs_to :resource, polymorphic: true
end
class Product < ActiveRecord::Base
has_one :resource_type, as: :resource
end
class Page < ActiveRecord::Base
has_one :resource_type, as: :resource
end
product_resource_type = ResourceType.create(...)
product = Product.create(resource_type: product_resource_type)
page_resource_type = ResourceType.create(...)
page = Page.create(resource_type: page_resource_type)
ResourceType.find(product_resource_type.id).resource
=> product
ResourceType.find(page_resource_type.id).resource
=> page

I figured this out after discovering instance_variables and instance_variables_get
Those methods will return all instance variables being passed into the view. From there I discovered that the :#_assigns instance variable contained the instances that I was looking for. So I iterated over them to find if any had the name attribute.
- instance_variable_get(:#_assigns).each do |var|
- if var[1].respond_to?("name")
%h1= var[1].name
There is probably a better way of accomplishing this, so if anyone has any opinions, they are welcome.

Related

Rails how to associate 2 models on create?

I would like to associate Order object wit Dispute Object on create of Dispute but when i go create the object in the log shows:
ActiveRecord::RecordNotFound (Couldn't find Order without an ID)
should i not try to find the order in the method?
Someone know how to associate the objects in the creation?
the Dispute Controller is:
class DisputesController < ApplicationController
def new
if current_user.address.blank?
redirect_to edit_user_path
flash[:error] = 'fill the address'
else
#dispute = Dispute.new
end
end
def create
#order = Order.find(params[:id])
if current_user == #order.buyer
dispute = #order.dispute.nil? ? Dispute.new : #order.dispute
dispute.attributes = params[:dispute]
dispute.user = #order.buyer
dispute.buyer_name = #order.buyer_name
dispute.seller_name = #order.seller_name
if dispute.save
flash[:success] = 'Dispute Created'
end
end
The order model
class Order < ActiveRecord::Base
has_one :dispute
end
the dispute model
class Dispute < ActiveRecord::Base
belongs_to :order
end
Without adding parameters or a nested route the request won't know what order is being referenced. You can use nested routes like orders/:order_id/dispute (http://guides.rubyonrails.org/routing.html#nested-resources) and then you can use #order.build_dispute (http://guides.rubyonrails.org/association_basics.html#methods-added-by-belongs-to)
My first thought based on the error you are getting is to check what params you have available upon the form's submission, because it seems it is not finding an Order based on the param you're passing into the find call.
Also check out strong params for security: http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html

Rails undefined method

Why is this undefined? Does it have something to do with the #current_user?
I'm trying to create tasks for my challenges. And the created task should get /achievements. However, I get a GET 500 error.
This is the error I get:
NoMethodError at /achievements
==============================
> undefined method `achievements' for #<User:0x00000105140dd8>
app/controllers/achievements_controller.rb, line 5
--------------------------------------------------
``` ruby
1 class AchievementsController < ApplicationController
2
3
4 def index
> 5 #achievements = #current_user.achievements
6 render :json => #achievements
7 end
8
9 def new 10 #achievement = Achievement.new
This is my code in my controller
class AchievementsController < ApplicationController
def index
#achievements = #current_user.achievements
render :json => #achievements
end
def new
#achievement = Achievement.new
render :json => #achievement
end
#create a new achievment and add it to the current user
#check then set the acheivments pub challenge id to the current pub challenge
def create
#achievement = Achievement.new achievement_params
#achievement.user = #current_user.id
#achievement.pub_challenge = params[:id]
if #achievement.save
# render :json => #achievement #{ status: 'ok'}
else
render :json => {:errors => #achievement.errors}
end
end
def show
#achievement = Achievement.find params[:id]
render :json => #achievement
end
def destroy
#achievement = Achievement.find params[:id]
#achievement.destroy
end
private
def achievement_params
params.require(:achievement).permit(:pub_challenges)
end
end
You are missing the has_many :achievements relation in your User model.
You'll need to create the ActiveRecord associations you require:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :achievements
end
#app/models/achievement.rb
class Achievement < ActiveRecord::Base
belongs_to :user
end
This will give you the ability to call the achievements method on any User objects you have.
Error
The error you have is described as such:
undefined method `achievements' for #<User:0x00000105140dd8>
This basically means that you're trying to call an undefined method on a User object. Might sound simple, but really, most people don't understand it.
To explain properly, you have to remember that Rails, by virtue of being built on Ruby is object orientated. This means that everything you do in Rails should be structured around objects - which are defined in your Models:
This means that each time you call an object, you're actually above to invoke a series of "methods" which will give you the ability to either manipulate the object itself, or any of the associated functionality it has.
The problem you have is that your User object doesn't have the achievements method. Whilst you could simply do the following to fix the issue, because it's Rails, you'll need to populate the record with associative data:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :achievements #-> what you need
def achievements
#this will also fix the error you see, although it's fundamentally incorrect
end
end
Something that helped me with this type of error was that the database table was missing the relevant column. Adding the required column to the database fixed the issue.

Rails STI not saving "type" field

I'm trying to set up a single table inheritance for Questions table. I've followed some advices adding a route this way :
resources :vfquestions, :controller => 'questions', :type =>
'Vfquestion'
And the model :
class Vfquestion < Question
end
It works, saving the question in the database, but the type field stays empty.
Here is my controller :
class QuestionsController < ApplicationController
before_action :authenticate_user!
def index
#user = current_user
#category = Category.find(params[:category_id])
#questions = #user.questions.where(:type => params[:type])
end
def new
#category = Category.find(params[:category_id])
#question = #category.questions.new
#question.type = params[:type]
end
def show
#user = current_user
#category = #user.categories.find(params[:category_id])
#question = #category.questions.find(params[:id])
end
def create
#user = current_user
#category = Category.find(params[:category_id])
#question = #category.questions.new(question_params)
#question.user_id = current_user.id
#question.save
end
private
def question_params
params.require(:question).permit(:title, :body)
end
end
Am I missing something to save this param ?
As far as I know, type is not saved for the base class. It also can't be overridden via params, as that would mean X.new would potentially yield an instance of a class other than X.
What you need to do is create the correct type on the way in:
#question =
case (params[:question][:type])
when 'Vfquestion'
Vfquestion.new(params[:question])
else
Question.new(params[:question])
end
#category.questions << #question
The relationship is also defined in terms of a singular base class, so all objects built in that scope will default to the base class.
I'm guessing in your Category model you have a line:
has_many :questions
The problem is that this relationship is pointing to the parent Question model; it has no idea you want to create or find any of its subtypes (Remember that Rails operates on convention over configuration; in this case, rails is locating your Question model because of the convention for naming has_many relationships).
One way to solve this is add the appropriate subtypes like so:
has_many :vfquestions
has_many :some_other_question_subtype
And then to create, for example, a new VFQuestion for a particular category, you would simply do:
#question = #category.vfquestions.new(question_params)
Side Note
Part of the problem in your situation is, in your create method, you have no way of distinguishing between a VFquestion, or some other question sub type when you go to create it. You'll have to figure out the best way to handle this for your particular domain, but possibly the simplest way to handle this is to pass a type parameter from the form. So, for example, if you have some kind of radio button that flops between the different question types, make sure it is named appropriately to it is sent when the form is submitted. Then simply check that piece of data in the params and either invoke .vfquestions, or some other question sub type.

rails create page from

I have Categories and Products. A product has a relation
belongs_to :category
In the categories show page I have a button to add a new product. This button goes to a page where I create the new product, but I need to give the category to the new product.
How can I pass the id from the category page where I was to the new Product? So, if I am in the category Electronic I click 'Add product' and this product automaticaly is associated with Eletronic category.
Hope you can understand what I want.
Thanks
You need to pass the category_id in your link, e.g. new_product_path(category_id: #category.id).
You will also need to have a field in your product form to save the category's ID, e.g <%= f.hidden_field :category_id, params[:category_id] %>
First, I would decide whether each product is contained within a category, or whether it's simply associated with a category. Hints it is contained would be:
You expect each product to have exactly one 'parent' category.
You expect each product will always appear in the context of its parent category.
If and only if you believe this to be the case, I would be tempted to nest the product resource within the category.
# routes.rb
resources :categories do
resources :products
end
# products_controller.rb (SIMPLIFIED!)
class ProductController < ApplicationController
before_filter :get_category
def new
#product = #category.products.build
end
def create
#product = #category.products.build(params[:product])
if #product.save
redirect_to #product
else
render template: "new"
end
end
def get_category
#category = Category.find(params[:category_id])
end
end
If you do this, rails will ensure your product is associated with the right category. The magic happens in #category.products.build, which automatically sets the category_id based on the relationship.
If you'd rather keep categories and products as simple associations, I'd just use a query parameter as per Eric Andres answer, although I'd be tempted to handle it in a slightly different way:
# link:
new_product_path(category_id: #category.id) # So far, so similar.
# products_controller.rb
class ProductsController < ApplicationController
def new
#product = Product.new
#product.category_id = params[:category_id].to_i if params[:category_id]
end
end
# new.erb
<%= f.hidden_field :category_id %>
This is mostly just a stylistic difference. Eric's answer will work too - I just prefer to set the value on the model itself rather than have the view worry about parameters etc.

Rails 3: alias_method_chain to set specific attribute first

When user's create a post I'd like to set the user_id attribute first. I'm trying to do this using alias_method_chain on the arrtibutes method. But I'm not sure if this is right as the problem I thought this would fix is still occurring. Is this correct?
Edit:
When my users create a post they assign 'artist(s)' to belong to each post, using a virtual attribute called 'artist_tokens'. I store the relationships in an artist model and a joined table of artist_ids and post_ids called artisanships.
I'd like to to also store the user_id of whomever created the artist that belongs to their post (and I want it inside the artist model itself), so I have a user_id column on the artist model.
The problem is when I create the artist for each post and try to insert the user_id of the post creator, the user_id keeps showing as NULL. Which is highly likely because the post's user_id attribute hasn't been set yet.
I figured to get around this I needed to set the user_id attribute of the post first, then let the rest of the attributes be set as they normally are. This is where I found alias_method_chain.
post.rb
attr_reader :artist_tokens
def artist_tokens=(ids)
ids.gsub!(/CREATE_(.+?)_END/) do
Artist.create!(:name => $1, :user_id => self.user_id).id
end
self.artist_ids = ids.split(",")
end
def attributes_with_user_id_first=(attributes = {})
if attributes.include?(:user_id)
self.user_id = attributes.delete(:user_id)
end
self.attributes_without_user_id_first = attributes
end
alias_method_chain :attributes=, :user_id_first
EDIT:
class ArtistsController < ApplicationController
def index
#artists = Artist.where("name like ?", "%#{params[:q]}%")
results = #artists.map(&:attributes)
results << {:name => "Add: #{params[:q]}", :id => "CREATE_#{params[:q]}_END"}
respond_to do |format|
format.html
format.json { render :json => results }
end
end
In your controller, why not just do this:
def create
#post = Post.new :user_id => params[:post][:user_id]
#post.update_attributes params[:post]
...
end
But it seems to me that it would be much better to create the artist records after you've done validation on the post rather than when you first assign the attribute.
EDIT
I would change this to a callback like this:
class Post < ActiveRecord::Base
attr_accessor :author_tokens
def artist_tokens=(tokens)
#artist_tokens = tokens.split(',')
end
after_save :create_artists
def create_artists
#artist_tokens.each do |token|
...
end
end
end

Resources