I am trying to output the value of a method on my Item model (current_user is defined in application_controller). I currently have as my rabl template:
object #item
attributes :id, :name
code :is_liked do |this_item|
if current_user
this_item.is_liked current_user
else
false
end
end
and in my model:
class Item < ActiveRecord::Base
...
def is_liked user
if user
if user.liked_item_ids.include?(self.id)
return true
else
return false
end
end
end
....
end
but it isn't working. I'm not sure what a proper way of outputting this would be. Any idea how to get this to work correctly?
edit 1
Here's the error that I'm getting:
Failure/Error: Unable to find matching line from backtrace
ActionView::Template::Error:
stack level too deep
Your rabl seems to be fine, however, when your find yourself adding some logic in your views (rabl can be compared to a view) you might want to consider refactor the logic in a presenter.
More information about presenters with rabl here
Regarding your error, like #apneadiving just said, there is a recursion issue in your codebase somewhere. Just by curiosity, have you try to rename the code block into something else than your method's name ? Depending on which version on rabl you are using, this could be the issue.
Finaly, you should consider refactoring your is_liked method:
def is_liked user
return user.liked_item_ids.include?(id) if user
false
end
Try:
node(:is_liked) {|this_item| this_item.is_liked(current_user) }
You already have the method, you can simply invoke here within a node instead of recreating logic.
Related
I have this function in rails controller:
def validate_params(*props)
props.each do |prop|
unless params[prop].start_with?('abc')
# return error
end
end
end
im thinking if I have params[:name] and params[:bio] and I want to validate name & bio with this function (not every attribute I might want to validate), I will call it with validate_params(:name, :bio). But, for nested param it won't work like params[:user][:name]. Is there anything I can do to pass this nested property to my function or is there a completely different approach? Thanks
Rails Validations generally belong in the model. You should post some additional info about what you're trying to do. For example, if you wanted to run the validation in the controller because these validations should only run in a certain context (i.e., only when this resource is interacted with from this specific endpoint), use on: to define custom contexts.
If you don't want to do things the rails way (which you should, imo), then don't call params in the method body. i.e.
def validate_params(*args)
args.each do |arg|
unless arg.start_with?('abc')
# return error
end
end
end
and call with validate_params(params[:user], params[:user][:name]
but yeah... just do it the rails way, you'll thank yourself later.
I want to disable all link of users at a time after deactivating users. So, for that I wrote a code like this
def link_to(*user)
if user_link_disabled?(user.id)
return nil
else
super
end
end
def user_link_disabled?(user_id)
User.where(activation: false).pluck(:name).include?(user_id)
end
But I am getting this error
undefined method `id' for #<Array:0x007efee4667d00>
Could anyone please help me on this?
I would add a column to your users model:
deactivated => type boolean
user.deactivated? #will return true or false
In your view you can then use link_to_unless
link_to_unless(user.deactivated, name, options = {}, html_options = {}, &block)
I don't know the scope because you didnt display anymore info but it could just be user_id in your if statement or try id[params[:id] instead of user.id but I'm not sure with out more context.
IN following method
def user_link_disabled?(user_id)
User.where(activation: false).pluck(:name).include?(user_id)
end
You are going to pluck name from user table records but you are checking include? for user.id, I think you should pluck id instead of name.
First of all, I am not gonna comment on your preferred code/method for overriding the the link_to helper. There is not much context available for that.
But to solve the particular error you are getting:
Your are defining method like this def link_to(*user) .
Here *user means it is expecting an Array as argument to the method and using the Ruby splat(*), it is converting it to normal arguments.
So if you call this as link_to [1,2,3], it will be same as calling a method with 3 arguments. That is link_to (1,2,3) but the argument user will be an Array.
So in here if user_link_disabled?(user.id), you are calling a id on a Array data type. That's why you are getting an error.
Depending on your use, either remove the * from method definition,
or
Use looping, if you are going to pass multiple users data to method, like:
def link_to(*user)
user.each do |u|
if user_link_disabled?(u.id)
return nil
else
super
end
end
end
As I mentioned in beginning, I don't know much about the context. So can not comment about the right way but if I may suggest, then I would suggest to use a custom helper for all user routes. like below pseudo code:
def link_to_user(user)
deactivated = user.deactivated?
if deactivated
# render some disabled link
else
# render link
end
end
I'm trying to monkey patch ActiveRecord::FinderMethods in order to use hashed ids for my models. So for example User.find(1) becomes User.find("FEW"). Sadly my overwritten method doesn't get called. Any ideas how to overwrite the find_one method?
module ActiveRecord
module FinderMethods
alias_method :orig_find_one, :find_one
def find_one(id)
if id.is_a?(String)
orig_find_one decrypt_id(id)
else
orig_find_one(id)
end
end
end
end
Here's an article that discusses how to actually do what you want by overriding the User.primary_key method like:
class User
self.primary_key = 'hashed_id'
end
Which would allow you to call User.find and pass it the "hashed_id":
http://ruby-journal.com/how-to-override-default-primary-key-id-in-rails/
So, it's possible.
That said, I would recommend against doing that, and instead using something like User.find_by_hashed_id. The only difference is that this method will return nil when a result is not found instead of throwing an ActiveRecord::RecordNotFound exception. You could throw this manually in your controller:
def show
#user = User.find_by_hashed_id(hashed_id)
raise ActiveRecord::RecordNotFound.new if #user.nil?
... continue processing ...
end
Finally, one other note to make this easier on you -- Rails also has a method you can override in your model, to_param, to tell it what property to use when generating routes. By default, of course, it users the id, but you would probably want to use the hashed_id.
class User
def to_param
self.hashed_id
end
end
Now, in your controller, params[:id] will contain the hashed_id instead of the id.
def show
#user = User.find_by_hashed_id(params[:id])
raise ActiveRecord::RecordNotFound.new if #user.nil?
... continue processing ...
end
I agree that you should be careful when doing this, but it is possible.
If you have a method decode_id that converts a hashed ID back to the original id, then the following will work:
In User.rb
# Extend AR find method to allow finding records by an encoded string id:
def self.find(*ids)
return super if ids.length > 1
# Note the short-circuiting || to fall-back to default behavior
find_by(id: decode_id(ids[0])) || super
end
Just make sure that decode_id returns nil if it's passed an invalid hash. This way you can find by Hashed ID and standard ID, so if you had a user with id 12345, then the following:
User.find(12345)
User.find("12345")
User.find(encode_id(12345))
Should all return the same user.
This is probably one of the things that all new users find out about Rails sooner or later. I just realized that rails is updating all fields with the serialize keyword, without checking if anything really changed inside. In a way that is the sensible thing to do for the generic framework.
But is there a way to override this behavior? If I can keep track of whether the values in a serialized fields have changed or not, is there a way to prevent it from being pushed in the update statement? I tried using "update_attributes" and limiting the hash to the fields of interest, but rails still updates all the serialized fields.
Suggestions?
Here is a similar solution for Rails 3.1.3.
From: https://sites.google.com/site/wangsnotes/ruby/ror/z00---topics/fail-to-partial-update-with-serialized-data
Put the following code in config/initializers/
ActiveRecord::Base.class_eval do
class_attribute :no_serialize_update
self.no_serialize_update = false
end
ActiveRecord::AttributeMethods::Dirty.class_eval do
def update(*)
if partial_updates?
if self.no_serialize_update
super(changed)
else
super(changed | (attributes.keys & self.class.serialized_attributes.keys))
end
else
super
end
end
end
Yes, that was bugging me too. This is what I did for Rails 2.3.14 (or lower):
# config/initializers/nopupdateserialize.rb
module ActiveRecord
class Base
class_attribute :no_serialize_update
self.no_serialize_update = false
end
end
module ActiveRecord2
module Dirty
def self.included(receiver)
receiver.alias_method_chain :update, :dirty2
end
private
def update_with_dirty2
if partial_updates?
if self.no_serialize_update
update_without_dirty(changed)
else
update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
end
else
update_without_dirty
end
end
end
end
ActiveRecord::Base.send :include, ActiveRecord2::Dirty
Then in your controller use:
model_item.no_serialize_update = true
model_item.update_attributes(params[:model_item])
model_item.increment!(:hits)
model_item.update_attribute(:nonserializedfield => "update me")
etc.
Or define it in your model if you do not expect any changes to the serialized field once created (but update_attribute(:serialized_field => "update me" still works!)
class Model < ActiveRecord::Base
serialize :serialized_field
def no_serialize_update
true
end
end
I ran into this problem today and ended up hacking my own serializer together with a getter and setter. First I renamed the field to #{column}_raw and then used the following code in the model (for the media attribute in my case).
require 'json'
...
def media=(media)
self.media_raw = JSON.dump(media)
end
def media
JSON.parse(media_raw) if media_raw.present?
end
Now partial updates work great for me, and the field is only updated when the data is actually changed.
The problem with Joris' answer is that it hooks into the alias_method_chain chain, disabling all the chains done after (like update_with_callbacks which accounts for the problems of triggers not being called). I'll try to make a diagram to make it easier to understand.
You may start with a chain like this
update -> update_with_foo -> update_with_bar -> update_with_baz
Notice that update_without_foo points to update_with_bar and update_without_bar to update_with_baz
Since you can't directly modify update_with_bar per the inner workings of alias_method_chain you might try to hook into the chain by adding a new link (bar2) and calling update_without_bar, so:
alias_method_chain :update, :bar2
Unfortunately, this will get you the following chain:
update -> update_with_bar2 -> update_with_baz
So update_with_foo is gone!
So, knowing that alias_method_chain won't let you redefine _with methods my solution so far has been to redefine update_without_dirty and do the attribute selection there.
Not quite a solution but a good workaround in many cases for me was simply to move the serialized column(s) to an associated model - often this actually was a good fit semantically anyway.
There is also discussions in https://github.com/rails/rails/issues/8328.
Is it possible to send variables in the the transition? i.e.
#car.crash!(:crashed_by => current_user)
I have callbacks in my model but I need to send them the user who instigated the transition
after_crash do |car, transition|
# Log the car crashers name
end
I can't access current_user because I'm in the Model and not the Controller/View.
And before you say it... I know I know.
Don't try to access session variables in the model
I get it.
However, whenever you wish to create a callback that logs or audits something then it's quite likely you're going to want to know who caused it? Ordinarily I'd have something in my controller that did something like...
#foo.some_method(current_user)
and my Foo model would be expecting some user to instigate some_method but how do I do this with a transition with the StateMachine gem?
If you are referring to the state_machine gem - https://github.com/pluginaweek/state_machine - then it supports arguments to events
after_crash do |car, transition|
Log.crash(car: car, crashed_by: transition.args.first)
end
I was having trouble with all of the other answers, and then I found that you can simply override the event in the class.
class Car
state_machine do
...
event :crash do
transition any => :crashed
end
end
def crash(current_driver)
logger.debug(current_driver)
super
end
end
Just make sure to call "super" in your custom method
I don't think you can pass params to events with that gem, so maybe you could try storing the current_user on #car (temporarily) so that your audit callback can access it.
In controller
#car.driver = current_user
In callback
after_crash do |car, transition|
create_audit_log car.driver, transition
end
Or something along those lines.. :)
I used transactions, instead of updating the object and changing the state in one call. For example, in update action,
ActiveRecord::Base.transaction do
if #car.update_attribute!(:crashed_by => current_user)
if #car.crash!()
format.html { redirect_to #car }
else
raise ActiveRecord::Rollback
else
raise ActiveRecord::Rollback
end
end
Another common pattern (see the state_machine docs) that saves you from having to pass variables between the controller and model is to dynamically define a state-checking method within the callback method. This wouldn't be very elegant in the example given above, but might be preferable in cases where the model needs to handle the same variable(s) in different states. For example, if you have 'crashed', 'stolen', and 'borrowed' states in your Car model, all of which can be associated with a responsible Person, you could have:
state :crashed, :stolen, :borrowed do
def blameable?
true
end
state all - [:crashed, :stolen, :borrowed] do
def blameable?
false
end
Then in the controller, you can do something like:
car.blame_person(person) if car.blameable?