Custom return value for new / create ActiveRecord model - ruby-on-rails-3

So I have a model (Photo), where when I call Photo.new #image => #image / Photo.create :image => #image, I want my model to find an existing photo with the same image hash OR create a new Photo from #image. Assume I can't use Photo.find_or_initialize_by_hash because I have a custom find function which finds close copies of images based on a soft image hash.
My first idea was to do
before_validation :check_duplicates, :on => :create
def check_duplicates
self = self.find_duplicate
end
Unfortunately, I realized you can't just redefine self in a model, so now I think the best approach is doing something along the lines of changing the return value from initialize to the duplicate.
Sort of like this, but it doesn't work (and I've heard horror stories about overriding initialize)
def initialize(*params)
super(*params)
return self.find_duplicate || self
end

From what I gather your model structure looks something like this?
class Photo < ActiveRecord::Base
has_one :image
end
class Image < ActiveRecord::Base
belongs_to :photo
end
If so, you can simply do this:
class Photo < ActiveRecord::Base
has_one :image, :uniq => true
end
Or if :image is just an attribute of Photo your first idea was on track:
class Photo < ActiveRecord::Base
before_create :check_duplicate
private
def check_duplicate
Photo.where(:image => self.image).count == 0 # will be false if Photo is found
end
end
which will cancel the Photo from being created if #check_duplicate returns false (http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html)
Or simply
class Photo < ActiveRecord::Base
validates_uniqueness_of :image
end

Related

Rails saving data from associated form

How can I get the data from an associated form and insert it to the associated table from the main model?
class Supplier < ActiveRecord::Base
has_one :account, foreign_key: "acc_sup_id", :autosave => true
self.primary_key = 'sup_id'
end
class Account < ActiveRecord::Base
belongs_to :supplier, foreign_key: "acc_sup_id"
self.primary_key = 'acc_id'
self.table_name = 'accounts'
end
I am having a combined form for Supplier and Account. When I submit I need to find a way to insert the corresponding values to Supplier and Account. The problem is Supplier values is inserting properly but not Account.
I have asked the same question in several forums, groups and even in stack but nobody seems to give a convincing answer.
The basic strategy is to first look at what params are being submitted when the form is submitted. You could add a line in the controller action such as raise params.inspect to see that. Make sure that those paras contain all the data you need; if not then there is some problem in the view that generates that form.
Once you have all the data getting to the controller action, then you need to change the controller action so that is properly interprets all the data and puts it into the correct models.
I cannot give any more specific advice unless you show the code for your view, the result from doing params.inspect, and the code for the controller action that takes the data.
Try this.
Let's assume that there are orders and customers tables and that you want to perform CRUD operations on customers from orders form.
Customer model is very simple
class Customer < ActiveRecord::Base
attr_accessible :name
end
Order model must provide virtual attributes for all customer's attributes (attr_accessor construct). CRUD for customers is provided through callbacks. Validations can be used as well.
class Order < ActiveRecord::Base
attr_accessor :customer_name
attr_accessible :description, :number, :customer_name
belongs_to :customer
validates_presence_of :number
validates_presence_of :description
validates_presence_of :customer_name
before_save :save_customer
after_find :find_customer
after_destroy :destroy_customer
protected
def save_customer
if self.customer
self.customer.name = self.customer_name
else
self.customer = Customer.create(name: self.customer_name)
end
self.customer.save
end
def find_customer
self.customer_name = self.customer.name
end
def destroy_customer
self.customer.destroy
end
end
Example grid for Order model.
class Orders < Netzke::Basepack::Grid
def configure(c)
super
c.model = 'Order'
c.items = [
:description,
:number,
:customer_name
]
c.enable_edit_inline = false
c.enable_add_inline = false
end
def preconfigure_record_window(c)
super
c.form_config.klass = OrderForm
end
end
Example form for Order model.
class OrderForm< Netzke::Basepack::Form
def configure(c)
super
c.model = 'Order'
c.items = [
:description,
:number,
:customer_name
]
end
end

How to restrict Sunspot search with nested models?

I want to filter the Sunspot search results with with(:is_available, true).
This is working with the User model, but I can't make it work with the Itinerary model.
app/controllers/search_controller.rb:
class SearchController < ApplicationController
before_filter :fulltext_actions
private
def fulltext_actions
#itineraries = do_fulltext_search(Itinerary)
#users = do_fulltext_search(User)
#itineraries_size = #itineraries.size
#users_size = #users.size
end
def do_fulltext_search(model)
Sunspot.search(model) do
with(:is_available, true)
fulltext params[:search]
end.results
end
end
app/models/user.rb:
class User < ActiveRecord::Base
has_many :itineraries, :dependent => :destroy
searchable do
text :first_name, :boost => 3
text :last_name, :boost => 3
text :status
boolean :is_available, :using => :available?
end
def available?
!self.suspended
end
end
app/models/itinerary.rb:
class Itinerary < ActiveRecord::Base
belongs_to :user
searchable do
text :title, :boost => 3
text :budget
text :description
boolean :is_available, :using => :available?
end
def available?
!self.user.suspended
end
end
Any ideas?
Thanks!
Well, my real problem was the indexation.
When I update the User model, I set a flag (like user_instance.update_index_flag = true) in my controller.
In the User model:
attr_accessor :update_index_flag
after_save :reindex_sunspot
def reindex_sunspot
if self.update_index_flag
Sunspot.index(self.itineraries.to_a)
end
end
That's it...

Rails 3: Find parent of polymorphic model in controller?

I'm trying to find an elegant (standard) way to pass the parent of a polymorphic model on to the view. For example:
class Picture < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
end
class Employee < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
class Product < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
The following way (find_imageable) works, but it seems "hackish".
#PictureController (updated to include full listing)
class PictureController < ApplicationController
#/employees/:id/picture/new
#/products/:id/picture/new
def new
#picture = imageable.pictures.new
respond_with [imageable, #picture]
end
private
def imageable
#imageable ||= find_imageable
end
def find_imageable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
end
Is there a better way?
EDIT
I'm doing a new action. The path takes the form of parent_model/:id/picture/new and params include the parent id (employee_id or product_id).
I'm not sure exactly what you're trying to do but if you're trying to find the object that 'owns' the picture you should be able to use the imageable_type field to get the class name. You don't even need a helper method for this, just
def show
#picture = Picture.find(params[:id])
#parent = #picture.imagable
#=> so on and so forth
end
Update
For an index action you could do
def index
#pictures = Picture.includes(:imagable).all
end
That will instantiate all 'imagables' for you.
Update II: The Wrath of Poly
For your new method you could just pass the id to your constructor, but if you want to instantiate the parent you could get it from the url like
def parent
#parent ||= %w(employee product).find {|p| request.path.split('/').include? p }
end
def parent_class
parent.classify.constantize
end
def imageable
#imageable ||= parent_class.find(params["#{parent}_id"])
end
You could of course define a constant in your controller that contained the possible parents and use that instead of listing them in the method explicitly. Using the request path object feels a little more 'Rails-y' to me.
I just ran into this same problem.
The way I 'sort of' solved it is defining a find_parent method in each model with polymorphic associations.
class Polymorphic1 < ActiveRecord::Base
belongs_to :parent1, :polymorphic => true
def find_parent
self.parent1
end
end
class Polymorphic2 < ActiveRecord::Base
belongs_to :parent2, :polymorphic => true
def find_parent
self.parent2
end
end
Unfortunately, I can not think of a better way. Hope this helps a bit for you.
This is the way I did it for multiple nested resources, where the last param is the polymorphic model we are dealing with: (only slightly different from your own)
def find_noteable
#possibilities = []
params.each do |name, value|
if name =~ /(.+)_id$/
#possibilities.push $1.classify.constantize.find(value)
end
end
return #possibilities.last
end
Then in the view, something like this:
<% # Don't think this was needed: #possibilities << picture %>
<%= link_to polymorphic_path(#possibilities.map {|p| p}) do %>
The reason for returning the last of that array is to allow finding the child/poly records in question i.e. #employee.pictures or #product.pictures

best way to save dependent objects in a has_and_belongs_to_many relation?

Hi I am new to rails and I would like to know what is the best way who save dependent objects in an HBTM relation.
Specifically, I have two classes Post and Tag
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts
end
I have a migration to create the joining table
class AddPostsTagsJoinTable < ActiveRecord::Migration
def self.up
create_table :posts_tags, :id => false do |t|
t.integer :post_id
t.integer :tag_id
end
end
def self.down
drop_table :postss_tags
end
end
All is good up to here
So I have a PostsController from which I handle the creation, updates and deletes for the posts, and I want to encapsulate the Tags so that the creation is via the PostsController... like so:
class PostsController < ApplicationController
#... code removed for brevity
def create
#post = current_user.posts.build(params[:post])
if #post.save
tag_names = params[:post][:tags].strip.split(' ')
tag_names.each do |t|
#see if the tag already exists
tag = Tag.find_by_name(t);
if tag.nil?
#post.tags.create!(:name => t)
else
#post.tags << tag #just create the association
end
end
flash[:success] = "Post created."
redirect_to(user_posts_path(current_user.username))
else
#user = current_user
render 'new'
end
end
end
I am not sure how I should handle the creation of my Tag(s) because if I just call
#post.tags.create!(:name => t)
this will create duplicate records in the Tags table (even when :uniq => true is specified in the model).
So to avoid the duplication I see if a tag is already present and then add it like this
tag = Tag.find_by_name(t);
if tag.nil?
#post.tags.create!(:name => t)
else
#post.tags << tag #just create the association
end
Is this the way it's supposed to be done?
This seems expensive (especially 'cause it's in a loop) so I am wondering if there is another "cleaner" way to do this? (pls forget the DRY'ing up of the action and so on)
Is there a clean way to create my Tags without having to manually check for duplicates?
thank you in advance for your help!
You can save tags attribute of post if automatically by adding accepts_nested_attributes_for to Post model
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags
accepts_nested_attributes_for :tags
end
The next step is to output tags fields inside post form.

Using after_create

I have a model, Category. And I want to create an new default sub_category when ever the category is created. But I'm not sure how to do it. Here is what I have.
class Category < ActiveRecord::Base
attr_accessible :title, :position
has_many :sub_categories
after_create :make_default_sub
def make_default_sub
#Sub_Categories.new( :title=>' ');
end
end
Why not to use ancestry gem? In the future if you will have more subcategories, it will be easier to manage them.
For example in your case:
class Category < ActiveRecord::Base
attr_accessible :title, :position
has_ancestry
after_create :create_default_subcategory
def make_default_sub
children = self.children.new
children.title = ''
children.position = 1 # or autogenerated
children.save!
end
end
But can you explain, why do you need such a strange default behaviour?
Thanks