In my model I have a method that marks a record as pending by changing its status to 2. After which it calls another method in another controller to create a notification containing details of the record that was changed.
e.g.
class Page < ActiveRecord::Base
def pend_page
self.update(status: 2)
Notification.create_notification("#{link_to self.title, pages_path(:status => 2)} marked as pending", #current_user)
end
end
However it seems Rails doesn't pass helpers for link_to and the routes to the models... as I get the error: undefined method 'pages_path' for #<Page:0x007fd15c996c88>.
How can I make it so that the link_to and pages_path work?
I'm using Rails 4.2.5.1
edit: here is what create_notification looks like:
class Notification < ActiveRecord::Base
belongs_to :user
def self.create_notification(content, user)
notification = Notification.new
notification.content = content
notification.user_id = user.id
notification.status = 0
notification.save
end
end
This should go in either a service object or in a PORO (plain old ruby object). A model's concerns should begin and end with database related functionality, anything else is in the wrong place.
Related
I have a 3 controllers with the method show namely
class CarController < ApplicationController
def show
end
end
class MotorcycleController < ApplicationController
def show
end
end
class TravelController < ApplicationController
def show
end
end
In my routes, i want the show method of all tree to follow the same url structure /:company_id/:id
So given an example that my home page url is https://localhost:3000, company id is 1 and id is 2, if i go to the show method of car controller, my url should be http://localhost:3000/1/2
At the moment, i did this in my routes
get '/:company_id/:id' => 'travel_controller#show', as: 'travel_insurance_product'
get '/:company_id/:id' => 'car_controller#show', as: 'car_insurance_product'
get '/:company_id/:id' => 'motorcycle_controller#show', as: 'motorcycle_insurance_product'
But when i trigger the car show method, it goes to the travel controller method
Is this possible to be done in ruby?
No, it's not possible. How is the router supposed to know if the id is for a Car, Motorcycle, or Travel?
BTW, the convention is for the controller to be in the plural form (such as MotorcyclesController).
Also BTW, routing is a Rails phenomenon, not a Ruby phenomenon.
Let's say I have two models: Client and Product
The "username" and "email" of Client should be "unique index", as "serialnumber" of Product
When the user is typing on the form field that is unique index, I have an onblur function that sends a request to the controller with the attribute name and attribute value. If a value exists, the user is immediately informed.
In ClientController, I wrote a function that checks if it's unique or not and returns -2 for error, -1 for not exists or a positive number (the id) if exists.
def unique
if params[:attrName].blank? or params[:attrValue].blank?
id = "-2"
else
cli = Client.where("#{params[:attrName]} = '#{params[:attrValue]}'").first
if cli != nil
id = cli["id"]
else
id = "-1"
end
end
render :json => {
:id => id
}
end
This is not good for many reasons (SQL Injection vulnerability, violation of DRY, as each controller would have basically the same method.
I'm thinking of writing the "unique" function inside ApplicationController, but as you saw above, I should be able to call "Client.where" if it's a client, or "Product.where" if it's a product. How can I build this function the most "generically" possible and the most securily? I'm thinking of raw SQL but I think this is a naive approach.
It would be wise to avoid raw SQL for this.
Would this work?
class ApplicationController < ActionController::Base
def unique
id = if params[:attrName].blank? || params[:attrValue].blank?
-2
elsif found = model_name.where(params[:attrName] => params[:attrValue]).take
found.id
else
-1
end
render json: { id: id }
end
end
You could put that in application_controller.rb then in both of your ClientsController and ProductsController you would define the model_name method:
class ClientsController < ApplicationController
def model_name
Client
end
end
class ProductsController < ApplicationController
def model_name
Product
end
end
This will work but might not be ideal. You might want to let Rails do more of the work by using find to raise if a model exists or not and strong params for validating that the params you need are present.
You can move this to a module and make it return an ActiveRecord relation. the advantage is later you can chain this with other ActiveRecord relations if you wish to, Something like (and note I have used ? in my sql condition, instead of directing giving the param )
#module
module UniqueRecord
module ClassMethods
def unique(params)
where(params)
end
end
def self.included(receiver)
receiver.extend ClassMethods
end
end
and use it in your class
#client.rb
class Client < ActiveRecord::Base
include UniqueRecord
end
#product.rb
class Product < ActiveRecord::Base
include UniqueRecord
end
So now both of your classes has the method unique available.
you can create a hash from the keys and values you get, Ex: you could dynamically create a hash to search email like
hash = {email: 'same#email.com'}
and then call the method
Client.unique(hash)
and if you want to , you can call it by the class name string
'Client'.constantize.unique(hash)
one more thing, it better to return an array of objects (if found) or blank array (if not found) instead of -1, -2. that will make your api consistent. like
Client.unique(hash).to_json
I have a rails model called "Post", it represents user's posts on a website.
Each "Post" has a method called "score" which takes its count of views and calculates a score. Score is not stored in the db, it's just a method:
class Post < ActiveRecord::Base
has_many :votes
def score
self.votes * 2
end
# This should return an object with all the posts, ordered by score.
def get_all_posts_sorted_by_rank
...
end
Error in Rails console:
2.0.0-p451 :001 > Post.get_all_posts_sorted_by_rank
NoMethodError: undefined method `get_all_posts_sorted_by_rank' for Post (call 'Post.connection' to establish a connection):Class
If you want it to be a class method, you should use:
def self.get_all_posts_sorted_by_rank
...
end
It's probably what you want.
Otherwise, if you want an instance method, you should use the way you are, but you have to instantiate your object before:
#post = Post.new #or find(params[:id]) or some other way
#post.get_all_posts_sorted_by_rank #use it
But the first case seems more probable.
How can I pass an argument to a model method using grape entity ?
I'd like to check wheter the current_user likes an item when presenting the item, so I built a model user_likes? method:
class Item
include Mongoid::Document
#some attributes here...
has_and_belongs_to_many :likers
def user_likes?(user)
likers.include?(user)
end
end
but I don't know how to send current_user to the grape-entity model :
module FancyApp
module Entities
class Item < Grape::Entity
expose :name #easy
expose :user_likes # <= How can I send an argument to this guy ?
end
end
end
in the grape api:
get :id do
item = Item.find(.....)
present item, with: FancyApp::Entities::Item # I should probably send current_user here, but how ?
end
I feel like the current_user should probably be sent from this last piece of code, but I can't figure how to do it :(
Any thoughts ?
Thanks !
Well, I found I could pass current as a parameters, and use it in a block. So :
present item, with: FancyApp::Entities::Item, :current_user => current_user
and in the entity definition:
expose :user_likes do |item,options|
item.user_likes?(options[:current_user])
end
#aherve, for some reason your syntax won't work in my case. The syntax in Grape Entity docs is a little different
your example, the syntax should be:
expose(:user_likes) { |item, options| item.user_likes?(options[:current_user]) }
Another way would be to store the current user in the item temporarily by defining an attribute accessor:
class Item
include Mongoid::Document
#some attributes here...
has_and_belongs_to_many :likers
attr_accessor :current_user
def user_likes
likers.include?(current_user)
end
end
and in the grape api set the current user:
get :id do
item = Item.find(.....)
item.current_user = current_user
present item, with: FancyApp::Entities::Item
end
no changes to the grape-entity model is needed.
There is no database field current_user_id or so. No writes to database.
I have a model OutcomeData with controller OutcomeDatas.
In OutcomeData, I have a method as_cleaned_hash that right now, doesn't do a damn thing. Let's just pretend it returns 'hello'
class OutcomeData < ActiveRecord::Base
attr_accessible :key, :outcome_uid, :task_id, :value
belongs_to :task
belongs_to :outcome
def as_cleaned_hash
'hello i am alive'
end
This is the method that as_cleaned_hash is supposed to follow, if it matters:
#outcome_data = OutcomeData.find_all_by_outcome_uid(params[:outcome_uid])
hash = Hash.new
#outcome_data.each do |p|
unless p[:value].blank? || p[:key] == 'raw'
hash[p[:key]] = p[:value]
end
end
This works fine -- right now I'm throwing it into my controller actions, but since it needs to be used throughout my app, I can't let this happen.
So, for whatever reason, I get an undefined method error.
I called OutcomeData.methods to see if the method was even there, and, nope. (see list here: http://pastebin.com/B3y1r2w7)
OutcomeData.respond_to?('as_cleaned_hash') returns false.
There's nothing fancy going on either, so I'm not quite sure what's happening.
Rails 3.2.12 with Ruby 2.0.0-p195
To define a class method, the syntax is
def self.foo
end
You have defined an instance method.