So I have a few different models in my Rails 4 app that have image uploads. Rather than adding identical code to each of the models I've created a module that I can include into all of them.
Here it is:
module WithImage
extend ActiveSupport::Concern
included do
attr_accessor :photo
has_one :medium, as: :imageable
after_save :find_or_create_medium, if: :photo?
def photo?
self.photo.present?
end
def find_or_create_medium
medium = Medium.find_or_initialize_by_imageable_id_and_imageable_type(self.id, self.class.to_s)
medium.attachment = photo
medium.save
end
end
def photo_url
medium.attachment if medium.present?
end
end
class ActiveRecord::Base
include WithImage
end
A Medium (singular of media) in this case is a polymorphic model that has paperclip on it. The attr_accessor is a f.file_field :photo that I have on the various forms.
Here's my PurchaseType Model (that uses this mixin):
class PurchaseType < ActiveRecord::Base
include WithImage
validates_presence_of :name, :type, :price
end
So here's the thing, the after_save works great here. However, when I go to the console and do PurchaseType.last.photo_url I get the following error:
ActiveRecord::ActiveRecordError: ActiveRecord::Base doesn't belong in a hierarchy descending from ActiveRecord
I haven't the faintest clue what this means or why it is happening. Anyone have any insight?
Thanks!
It turns out I was trying to do things I had seen in various examples of modules. It was simple to get it working:
module WithImage
extend ActiveSupport::Concern
included do
attr_accessor :photo
has_one :medium, as: :imageable
after_save :find_or_create_medium, if: :photo?
def photo?
self.photo.present?
end
def find_or_create_medium
medium = Medium.find_or_initialize_by_imageable_id_and_imageable_type(self.id, self.class.to_s)
medium.attachment = photo
medium.save
end
def photo_url
medium.attachment.url if medium.present?
end
end
end
Related
I am trying to validate emails given from a CSV list of emails. So I have created the invite_list virtual attribute where when given a list of emails, it will loop and create a new record in the invited_only_emails model.
Now the thing is, this works fine, but how can I catch the validation error thrown by InvitedOnlyEmail while looping in Users model so I'll be able to use that error in the controller?
This is my main model:
class Users < ActiveRecord::Base
attr_accessor :invite_list
attr_accessible :invite_list
has_many :invited_only_emails
def invite_list=(list)
list.split(",").each do |address|
self.invited_only_emails.create! :email => address
end
end
def invite_list
self.invited_only_emails.map {|email| email.email}.join(',')
end
end
And this is the invited_only_emails model:
class InvitedOnlyEmail < ActiveRecord::Base
attr_accessible :email
belongs_to :users
validates_format_of :email, :with => /^([\w\.%\+\-]+)#([\w\-]+\.)+([\w]{2,})$/i
end
Thanks!
I think you could use validates_associated method:
class User < ActiveRecord::Base
validates_associated :invited_only_emails
# ...
end
http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_associated
Have you looked at validates_associated?
http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_associated
I am building a public activity stream which contains a stream of the following:
User posted 3 minutes ago
User starred a post
I am using the public_activity gem for achieving this.
My question is whether there is a way to use the includes for a polymorpic function.
The code that i am running currently is as follows:
#app/models/post.rb
class Post < ActiveRecord::Base
include PublicActivity::Common
attr_accessible :content, :visibility
validates_presence_of :content
belongs_to :postable, :polymorphic => true
has_many :stars
end
#app/models/star.rb
class Star < ActiveRecord::Base
include PublicActivity::Common
validate :duplicate_star
belongs_to :user
belongs_to :post
private
def duplicate_star
errors.add(:base, "Post already starred") if Star.exists?(:user_id => self.user, :post_id => self.post)
end
end
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def index
#post = Post.new
#activities = PublicActivity::Activity.order("id desc").all.includes(:trackable, :owner)
end
end
The trackable can be a post or a star.
I have render functions for displaying both.
The problem is, if I try to output something like {user} starred {postcontent}, it does it this way:
activity.trackable.post.content
So this results in multiple queries, each time it finds a post.
How do I tackle this problem/situation?
Thanks in advance.
Are you not able to use the standard eager loading syntax?
#activities = PublicActivity::Activity.order("id desc").includes(:owner, :trackable => {:post => :content})
I want to create tableless model which doesn't need datebase. At example:
class Post < ActiveRecord::Base
attr_accessible :body, :title, :language_id
belong_to :language
end
class Language
has_many :post
...
end
Will be 2 or 3 language. I don't want to load DB, is it possible to create languges in model by hand?
It might help to read this article: http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/.
In general, your models need not inherit from ActiveRecord, because you can include ActiveModel instead.
On the other hand, you can keep it simple like so:
class Langauge
attr_accessor :posts
def initialize
#posts = []
end
def add_post(post)
#posts << post
end
end
lang = Language.new
lang.add_post(Post.new)
Can someone please assist with regards to CarriveWave's store_dir.
How does one have mulitple image models that stores files based on the associated belong_to model's permalink?
# Garage model
class Garage < ActiveRecord:Base
attr_accessible: :avatar, :permalink, :car_image_attributes,
:motorcycle_image_attributes
has_many :car_image
has_many :motorcycle_image
mount_uploader :avatar, AvatarUploader
def set_permalink
self.permalink = permalink.parameterize
end
def to_param
permalink.parameterize
end
end
This is what my Image Models that links with CarrierWave
# Car Image model
CarImage < ActiveRecord:Base
belongs_to :garage
attr_accessible: :garage_id, :image
mount_uploader :car_image, CarUploader
end
# Motocycle Image model
MotocycleImage < ActiveRecord:Base
belongs_to :garage
attr_accessible: :garage_id, :image
mount_uploader :motorcycle_image, MotocycleUploader
end
This is what my CarrierWave uploaders look like.
# CarrierWave avatar uploader
avatar_uploader.rb
# This uploader directly relates to the Garage model table
# column avatar:string.
def store_dir
# This works for the avatar because it calls on the Garage permalink
# but it fails for the other image models because it's a model relation
# has_many, belongs_to and the model.permalink will be based on the
# uploader's respective model and not the Garage model
# eg. car_uploader.rb = model.permalink = CarImage.permalink
# I would like it to refer to to Garage.permalink at all times.
"garage/#{model.permalink}/#{mounted_as}/"
end
end
# CarrierWave car and motorcycle uploaders
car_uploader.rb
# Fails to upload because it doesn't know what permalink is
end
motorcycle_uploader.rb
# Fails to upload because it doesn't know what permalink is
end
Apologies if I wants so clear but a big thanks for any insight given.
probably the easiest way would be to delegate the permalink to the parent on the model
CarImage < ActiveRecord:Base
belongs_to :garage
delegate : permalink, :to => :garage
attr_accessible: :garage_id, :image
mount_uploader :car_image, CarUploader
end
Let's say I have two models; Post & Comment
class Post < ActiveRecord::Base
has_many :comments
accepts_nested_attributes_for :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
before_save :do_something
def do_something
# Please, let me do something!
end
end
I have a form for Post, with fields for comments. Everything works as expected, except for the filter. With the above configuration, before_save filter on Comment isn't triggered.
Could you explain why, and how I can fix this?
Rails doesn't instantiate and save the comments individually in this case. You would be better off adding a callback in your Post model to handle this for nested comments:
class Post < AR::Base
before_save :do_something_on_comments
def do_something_on_comments
comments.map &:do_something
end
end
According to Bryan Helmkamp, it's better to use the form object pattern than it is to use accepts_nested_attributes_for. Take a look at 7 Patterns to Refactor Fat ActiveRecord Models
Maybe you could do something like this?
class NewPost
include Virtus
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
attr_reader :post
attr_reader :comment
# Forms are never themselves persisted
def persisted?
false
end
def save
if valid?
persist!
true
else
false
end
end
private
def persist!
#post = Post.create!
#comment = #post.comment.create!
end
end
do_something would get called when you create the comment.