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
Related
Given a model Orderstatus with attributes private_status:string, and private_status_history:json(I'm using Postgresql's json). I would like to record each status transition, together with the user who made the change.
Ideally it would be something like:
class Orderstatus < ActiveRecord::Base
after_save :track_changes
def track_changes
changes = self.changes
if self.private_status_changed?
self.private_status_history_will_change!
self.private_status_history.append({
type: changes[:private_status],
user: current_user.id
})
end
end
end
class OrderstatusController <ApplicationController
def update
if #status.update_attributes(white_params)
# Good response
else
# Bad response
end
end
end
#Desired behaviour (process not run with console)
status = Orderstatus.new(private_status:'one')
status.private_status #=> 'one'
status.private_status_history #=> []
status.update_attributes({:private_status=>'two'}) #=>true
status.private_status #=> 'two'
status.private_status_history #=> [{type:['one','two'],user:32]
What would be the recommended practice to achieve this? Apart from the usual one using Thread. Or maybe, any suggestion to refactor the structure of the app?
So, I finally settled for this option ( I hope it's not alarming to anyone :S)
class Orderstatus < ActiveRecord::Base
after_save :track_changes
attr_accessor :modifying_user
def track_changes
changes = self.changes
if self.private_status_changed?
newchange = {type:changes[:private_status],user: modifying_user.id}
self.update_column(:private_status_history,
self.private_status_history.append(newchange))
end
end
end
class OrderstatusController <ApplicationController
def update
#status.modifying_user = current_user # <---- HERE!
if #status.update_attributes(white_params)
# Good response
else
# Bad response
end
end
end
Notes:
- I pass the from the Controller to the Model through an instance attribute modifying_user of the class Orderstatus. That attribute is ofc not saved to the db.
- Change of method to append new changes to the history field. I.e. attr_will_change! + save to update_column + append
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 base_table, and I have a extended_table which has extra properties to further extend my base_table. (I would have different extended_tables, to add different properties to my base_table, but that's non-related to the question I'm asking here).
The model definition for my base_table is like:
class BaseTable < ActiveRecord::Base
module BaseTableInclude
def self.included(base)
base.belongs_to :base_table, autosave:true, dependent: :destroy
# do something more when this module is included
end
end
end
And the model definition for my extended_table is like:
class TennisQuestionaire < ActiveRecord::Base
include BaseTable::BaseTableInclude
end
Now I what I want is the code below:
params = {base_table: {name:"Songyy",age:19},tennis_ball_num:3}
t = TennisQuestionaire.new(params)
When I created my t, I want the base_table to be instantiated as well.
One fix I can come up with, is to parse the params to create the base_table object, before TennisQuestionaire.new was called upon the params. It's something like having a "before_new" filter here. But I cannot find such kind of filter when I was reading the documentation.
Additionally, I think another way is to override the 'new' method. But this is not so clean.
NOTE: There's one method called accepts_nested_attributes_for, seems to do what I want, but it doesn't work upon a belongs_to relation.
Any suggestions would be appreciated. Thanks :)
After some trails&error, the solution is something like this:
class BaseTable < ActiveRecord::Base
module BaseTableInclude
def initialize(*args,&block)
handle_res = handle_param_args(args) { |params| params[:base_table] = BaseTable.new(params[:base_table]) }
super(*args,&block)
end
private
def handle_param_args(args)
return unless block_given?
if args.length > 0
params = args[0]
if (params.is_a? Hash) and params[:base_table].is_a? Hash
yield params
end
end
end
end
end
How can I parse the params from a custom URL?
Lets say I have a User class that implements a to_param and a from_param method and I have a backend admin where a "customer" can insert an URL from a user (profile) page (e.g. http://localhost:3000/users/JohnDoe-123security456-123 where 123 is the ID).
Is it possible to generate a custom params object or something similar to parse the id from the url?
My goal is to reuse the existing logic instead of creating another regex.
I know I could do something like: "http://localhost:3000/users/JohnDoe-123security456-123/custom_action?abc=def".gsub(/^.*\/users\//, '').gsub(/\/.*$/,'') (or something more suffisticated) to get the id.
Here is the pseudocode of what I try to achieve.
class User < ActiveRecord::Base
def to_param
"#{name}-#{security-token}-#{id}"
end
def self.id_from_param(param_id)
param_id.to_s.gsub(/.*-/,'')
end
end
class AdminUserController < ActionController::Base
def search
url = params[:url]
parsed_params = some_method_that_extracts_the_params_from_url(url) # (1)
user_id = User.id_from_param(parsed_params[:id])
end
end
This is the weirdest thing ever happened to me with ruby/rails.
I have a model, Store, which has_many Balances. And I have a method that gives me the default balance based on the store's currency.
Store model.
class Store < ActiveRecord::Base
has_many :balances, as: :balanceable, dependent: :destroy
def default_balance
#puts self.inspect <- weird part.
balances.where(currency: self.currency)[0]
end
...
end
Balance model.
class Balance < ActiveRecord::Base
belongs_to :balanceable, :polymorphic => true
...
end
Ok, so then in the Balance controller I have the show action, that will give me a specific balance or the default one.
Balance controller.
class Api::Stores::BalancesController < Api::Stores::BaseController
before_filter :load_store
# Returns a specific alert
# +URL+:: GET /api/stores/:store_id/balances/:id
def show
#puts #store.inspect <- weird part.
#balance = (params[:id] == "default") ? #store.default_balance : Balance.find(params[:id])
respond_with #balance, :api_template => :default
end
...
private
# Provides a shortcut to access the current store
def load_store
#store = Store.find(params[:store_id])
authorize! :manage, #store
end
end
Now here is where the weird part comes...
If I make a call to the show action; for example:
GET /api/stores/148/balances/default
It returns null (because the currency was set as null, and there is no Balance with null currency), and the SQL query generated is:
SELECT `balances`.* FROM `balances` WHERE `balances`.`balanceable_id` = 148 AND `balances`.`balanceable_type` = 'Store' AND `balances`.`currency` IS NULL
So I DON'T know why... it is setting the currency as NULL. BUT if in any where in that process I put
puts #store.inspect
or inside the default_balance method:
puts self.inspect
it magically works!!!.
So I don't know why is that happening?... It seems like the store object is not getting loaded until I "inspect" it or something like that.
Thanks
Sam and Adrien are on the right path.
ActiveRecord overrides method_missing to add a whole bunch of dynamic methods including the accessors for the column-backed attributes like Store#currency. While I'm glossing over a lot, suffice it to say that when the logic is invoked then the dynamic class/instance methods are added to the Store class/instances so that subsequent calls no longer require the method_missing hook.
When YOU overrode method_missing without calling super, you effectively disabled this functionality. Fortunately, this functionality can be invoked by other means, one of which you tripped upon when you called store#inspect.
By adding the call to super, you simply assured that ActiveRecord's dynamic methods are always added to the class when they're needed.
OK finally after a lot of debugging, I found the reason...
In the Store model I have a method_missing method and I had it like this:
def method_missing method_name, *args
if method_name =~ /^(\w+)_togo$/
send($1, *args).where(togo: true)
elsif method_name =~ /^(\w+)_tostay$/
send($1, *args).where(tostay: true)
end
end
So when I was calling self.currency it went first to the method_missing and then returned null. What I was missing here was the super call.
def method_missing method_name, *args
if method_name =~ /^(\w+)_togo$/
send($1, *args).where(togo: true)
elsif method_name =~ /^(\w+)_tostay$/
send($1, *args).where(tostay: true)
else
super
end
end
But I continue wondering why after I had called puts #store.inspect or puts self.inspect it worked well?. I mean, why in that case that super call wasn't needed?