Rails: activeadmin overriding create action - ruby-on-rails

I have an activeadmin resource which has a belongs_to :user relationship.
When I create a new Instance of the model in active admin, I want to associate the currently logged in user as the user who created the instance (pretty standard stuff I'd imagine).
So... I got it working with:
controller do
def create
#item = Item.new(params[:item])
#item.user = current_curator
super
end
end
However ;) I'm just wondering how this works? I just hoped that assigning the #item variable the user and then calling super would work (and it does). I also started looking through the gem but couldn't see how it was actually working.
Any pointers would be great. I'm assuming this is something that InheritedResources gives you?
Thanks!

I ran into a similar situation where I didn't really need to completely override the create method. I really only wanted to inject properties before save, and only on create; very similar to your example. After reading through the ActiveAdmin source, I determined that I could use before_create to do what I needed:
ActiveAdmin.register Product do
before_create do |product|
product.creator = current_user
end
end

Another option:
def create
params[:item].merge!({ user_id: current_curator.id })
create!
end

You are right active admin use InheritedResources, all other tools you can see on the end of the page.

As per the AA source code this worked for me:
controller do
def call_before_create(offer)
end
end

Related

Strong parameters issue with custom controller?

I'm using acts_as_taggable_on plugin in conjunction with my User model.
acts_as_taggable_on :skills
Now, I have a custom controller called SkillsController to add skills via ajax.
class SkillController < ApplicationController
def add
current_user.skill_list.add(params[:skill])
current_user.save # Not saving!
end
end
and in routes.rb
get 'skill/:skill', to: 'skill#add'
I guess it has to do something with Strong Parameters, but I don't know how to solve it as it stands.
The current_user.save isn't working, how to solve it.
P.S current_user.errors shows #message is "too short" as per my validations. But how do I just save the skill_list without having to modify other attributes or running validations on them?
If you want to save current_user without validation check you can do just like as:
current_user.save(:validate => false)
This will work for you :)

using .dup with multiple records

I am creating a Stripe payment engine for the Catarse project. I have three records I need to copy from my User, who is a project owner to my User's project. But because I'm a beginner, the code looks like S&%#te!
#project_controller.rb
def create
...
check_for_stripe_keys
....
end
def show
...
check_for_stripe_keys
....
end
....
def check_for_stripe_keys
if #project.stripe_userid.nil?
#project.reload
#project.stripe_access_token = #project.user.stripe_access_token.dup
#project.stripe_key = #project.user.stripe_key.dup
#project.stripe_userid = #project.user.stripe_userid.dup
elsif #project.stripe_userid != #project.user.userid
#project.stripe_access_token = #project.user.stripe_access_token.dup
#project.stripe_key = #project.user.stripe_key.dup
#project.stripe_userid = #project.user.stripe_userid.dup
end
#project.save
end
....
I only need those three records because my stripe code is an engine. Three things:
1) Initially I thought to user update_attributes but I don't know if its possible to use .dup in that method.
2) Is it possible to put this in a helper located in the engine thats accessible to the main_app so users don't have to edit the main_app project_controller code?
3) Is there a cleaner way to show the above .dup code? 'Cause it's fugly!
Thanks for your help!
This is a more compact way to write what you already have:
[:stripe_access_token, :stripe_key, :stripe_userid].each do |field|
#project.send("#{field.to_s}=", #project.user.send(field).dup)
end
Maybe something like this:
Gemfile:
gem 'deep_cloneable'
Code:
#user.dup :include => [:stripe_userid, :stripe_access_token, :stripe_key]
I didn't quite follow your relationships. But take a look at this answer: What is the easiest way to duplicate an activerecord record?
Here is deep_cloneable:
https://github.com/moiristo/deep_cloneable

Can I make Rails update_attributes with nested form find existing records and add to collections instead of creating new ones?

Scenario: I have a has_many association (Post has many Authors), and I have a nested Post form to accept attributes for Authors.
What I found is that when I call post.update_attributes(params[:post]) where params[:post] is a hash with post and all author attributes to add, there doesn't seem to be a way to ask Rails to only create Authors if certain criteria is met, e.g. the username for the Author already exists. What Rails would do is just failing and rollback update_attributes routine if username has uniqueness validation in the model. If not, then Rails would add a new record Author if one that does not have an id is in the hash.
Now my code for the update action in the Post controller becomes this:
def update
#post = Post.find(params[:id])
# custom code to work around by inspecting the author attributes
# and pre-inserting the association of existing authors into the testrun's author
# collection
params[:post][:authors_attributes].values.each do |author_attribute|
if author_attribute[:id].nil? and author_attribute[:username].present?
existing_author = Author.find_by_username(author_attribute[:username])
if existing_author.present?
author_attribute[:id] = existing_author.id
#testrun.authors << existing_author
end
end
end
if #post.update_attributes(params[:post])
flash[:success] = 'great!'
else
flash[:error] = 'Urgg!'
end
redirect_to ...
end
Are there better ways to handle this that I missed?
EDIT: Thanks for #Robd'Apice who lead me to look into overriding the default authors_attributes= function that accepts_nested_attributes_for inserts into the model on my behalf, I was able to come up with something that is better:
def authors_attributes=(authors_attributes)
authors_attributes.values.each do |author_attributes|
if author_attributes[:id].nil? and author_attributes[:username].present?
author = Radar.find_by_username(radar_attributes[:username])
if author.present?
author_attributes[:id] = author.id
self.authors << author
end
end
end
assign_nested_attributes_for_collection_association(:authors, authors_attributes, mass_assignment_options)
end
But I'm not completely satisfied with it, for one, I'm still mucking the attribute hashes from the caller directly which requires understanding of how the logic works for these hashes (:id set or not set, for instance), and two, I'm calling a function that is not trivial to fit here. It would be nice if there are ways to tell 'accepts_nested_attributes_for' to only create new record when certain condition is not met. The one-to-one association has a :update_only flag that does something similar but this is lacking for one-to-many relationship.
Are there better solutions out there?
This kind of logic probably belongs in your model, not your controller. I'd consider re-writing the author_attributes= method that is created by default for your association.
def authors_attributes=(authors_attributes)
authors_attributes.values.each do |author_attributes|
author_to_update = Author.find_by_id(author_attributes[:id]) || Author.find_by_username(author_attributes[:username]) || self.authors.build
author_to_update.update_attributes(author_attributes)
end
end
I haven't tested that code, but I think that should work.
EDIT: To retain the other functionality of accepts_nested_Attributes_for, you could use super:
def authors_attributes=(authors_attributes)
authors_attributes.each do |key, author_attributes|
authors_attributes[key][:id] = Author.find_by_username(author_attributes[:username]).id if author_attributes[:username] && !author_attributes[:username].present?
end
super(authors_attributes)
end
If that implementation with super doesn't work, you probably have two options: continue with the 'processing' of the attributes hash in the controller (but turn it into a private method of your controller to clean it up a bit), or continue with my first solution by adding in the functionality you've lost from :destroy => true and reject_if with your own code (which wouldn't be too hard to do). I'd probably go with the first option.
I'd suggest using a form object instead of trying to get accepts_nested_attributes to work. I find that form object are often much cleaner and much more flexible. Check out this railscast

Is there a way to prevent serialized attributes in rails from getting updated even if there are not changes?

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.

Can I use Struct.new as a Rails model? Or: How to create anonymous structured non-db backed sessions?

Given the following example:
class AnonymousSession << Struct.new(:location, :preferences)
def valid?
...
end
def new_record?
...
end
end
While this interface is sufficient to create resourceful form and so on, it fails as soon as I want to save my form data to the session:
if session[:user] = AnonymousSession.create(params[:anonymous_session])
#--> fails with "unknown key(s): location..."
...
end
The error message is about "unknown keys". Any clue how to make it work? I just need anonymous sessions without database backend. They are completely disposable due to their short live nature.
Maybe my approach is wrong anyway and there's already an elegant solution to using anonymous sessions? I had a look at AuthLogic but any example I found always comes with an ActiveRecord model (and thus bound to a database).
I have used this solution, where my model derives from Tableless. I think it will work in your case as well.
Ryan Bates has a couple Railscast episodes that might help you out: Session Based Model and Super Simple Authentication.
You'd have to explain more as to what you're trying to accomplish. Why couldn't you just create an AnonymousSession class in /app/models?
class AnonymousSession
attr_accessor :location, :preferences
def new_record?
# ...
end
def valid?
# ...
end
end

Resources