I'm desperately don't success to make Carrierwave working with Tire (Elasticsearch gem).
I have a Question model which was an ActiveRecord one, but I migrated it on Elasticsearch with Tire. Until it was on ActiveRecord everything was working great. But not anymore.
What I want is to upload a remote file (from Facebook) to a S3 bucket. All config files are correct (as it was working under ActiveRecord model)
Here is my Question model:
class Question
include ActiveModel::MassAssignmentSecurity
include ActiveModel::Validations
extend CarrierWave::Mount
include Tire::Model::Callbacks
include Tire::Model::Persistence
# set fields for carrierwave uploader
mount_uploader :path, QuestionUploader
validates_presence_of :question
attr_accessible :path
attr_accessor :remote_path_url, :remove_path
property :difficulty
property :question
property :path
end
And then in my questions_controller:
class QuestionsController < ApplicationController
def create
#question = Question.new question: "How are you ?", difficulty: 3
#question.remote_path_url = "http://domain.com/file.jpg"
#question.save
render nothing: true
end
end
Elasticsearch record works, but no upload happens...
Someone has an idea ?
Cheers
After save callback should be explicitly called for non active record models, as mentioned here.
Like #question.store_image!
Also it may not be good idea to use path as file name, as it is confusing.
For example: #question.store_path!
I had a similar problem and ended up using Dragonfly for Models which integrated very well with a non-ActiveRecord model. I am using elasticsearch-persistence with a standalone persistence layer for models.
Related
I have a Rails model with:
has_many_attached :files
When uploading via Active Storage by default if you upload new files it deletes all the existing uploads and replaces them with the new ones.
I have a controller hack from this which is less than desirable for many reasons:
What is the correct way to update images with has_many_attached in Rails 6
Is there a way to configure Active Storage to keep the existing ones?
Looks like there is a configuration that does exactly that
config.active_storage.replace_on_assign_to_many = false
Unfortunately it is deprecated according to current rails source code and it will be removed in Rails 7.1
config.active_storage.replace_on_assign_to_many is deprecated and will be removed in Rails 7.1. Make sure that your code works well with config.active_storage.replace_on_assign_to_many set to true before upgrading.
To append new attachables to the Active Storage association, prefer using attach.
Using association setter would result in purging the existing attached attachments and replacing them with new ones.
It looks like explicite usage of attach will be the only way forward.
So one way is to set everything in the controller:
def update
...
if model.update(model_params)
model.files.attach(params[:model][:files]) if params.dig(:model, :files).present?
else
...
end
end
If you don't like to have this code in controller. You can for example override default setter for the model eg like this:
class Model < ApplicationModel
has_many_attached :files
def files=(attachables)
files.attach(attachables)
end
end
Not sure if I'd suggest this solution. I'd prefer to add new method just for appending files:
class Model < ApplicationModel
has_many_attached :files
def append_files=(attachables)
files.attach(attachables)
end
end
and in your form use
<%= f.file_field :append_files %>
It might need also a reader in the model and probably a better name, but it should demonstrate the concept.
The solution suggested for overwriting the writer by #edariedl DOES NOT WORK because it causes a stack level too deep
1st solution
Based on ActiveStorage source code at this line
You can override the writer for the has_many_attached like so:
class Model < ApplicationModel
has_many_attached :files
def files=(attachables)
attachables = Array(attachables).compact_blank
if attachables.any?
attachment_changes["files"] =
ActiveStorage::Attached::Changes::CreateMany.new("files", self, files.blobs + attachables)
end
end
end
Refactor / 2nd solution
You can create a model concern that will encapsulate all this logic and make it a bit more dynamic, by allowing you to specify the has_many_attached fields for which you want the old behaviour, while still maintaining the new behaviour for newer has_many_attached fields, should you add any after you enable the new behaviour.
in app/models/concerns/append_to_has_many_attached.rb
module AppendToHasManyAttached
def self.[](fields)
Module.new do
extend ActiveSupport::Concern
fields = Array(fields).compact_blank # will always return an array ( worst case is an empty array)
fields.each do |field|
field = field.to_s # We need the string version
define_method :"#{field}=" do |attachables|
attachables = Array(attachables).compact_blank
if attachables.any?
attachment_changes[field] =
ActiveStorage::Attached::Changes::CreateMany.new(field, self, public_send(field).public_send(:blobs) + attachables)
end
end
end
end
end
end
and in your model :
class Model < ApplicationModel
include AppendToHasManyAttached['files'] # you can include it before or after, order does not matter, explanation below
has_many_attached :files
end
NOTE: It does not matter if you prepend or include the module because the methods generated by ActiveStorage are added inside this generated module which is called very early when you inherit from ActiveRecord::Base here
==> So your writer will always take precedence.
Alternative/Last solution:
If you want something even more dynamic and robust, you can still create a model concern, but instead you loop inside the attachment_reflections of your model like so :
reflection_names = Model.reflect_on_all_attachments.filter { _1.macro == :has_many_attached }.map { _1.name.to_s } # we filter to exclude `has_one_attached` fields
# => returns ['files']
reflection_names.each do |name|
define_method :"#{name}=" do |attachables|
# ....
end
end
However I believe for this to work, you need to include this module after all the calls to your has_many_attached otherwise it won't work because the reflections array won't be fully populated ( each call to has_many_attached appends to that array)
I'm using the Frozen Record gem in Rails to upload a YAML file containing some fixed questions for my app. Once they're uploaded I want to use a SaleQualifier model to grab each question, associate it with an answer, and then use it as a state machine to move through a question tree.
I've got the Frozen Record gem working and the YAML file uploads just fine. When I tried associating the Model with a new Model (SaleQualifier) I got 'method_missing': undefined method 'belongs_to' for Question:Class (NoMethodError)
As a result I added the include ActiveRecord::Associations components in order to let me associate the new record with my SaleQualifier Question belongs_to :sale_qualifier- but this then throws:
'method_missing': undefined method 'dangerous_attribute_method?' for Question:Class (NoMethodError)
According to my search, this error is thrown when I've already declared the method beforehand. I don't know where this is defined, but from the Frozen_Record gem files I can see they set up a FrozenRecord with the following:
module FrozenRecord
class Base
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::AttributeMethods
include ActiveModel::Serializers::JSON
include ActiveModel::Serializers::Xml
end
end
I'm starting to think using this gem might be overkill, and perhaps I should just load my questions into a normal ActiveRecord::Base model, but I like the FrozenRecord idea, as that's exactly what I'm trying to achieve.
My code:
class Question < FrozenRecord::Base
include ActiveModel::Validations
include ActiveRecord::Associations
validates :next_question_id_yes, :question_text, :answer_type, presence: true
belongs_to :sale_qualifier
self.base_path = 'config/initializers/'
end
SaleQualifier:
class SaleQualifier < ActiveRecord::Base
has_many :questions
end
Can anyone help me unpick the mess I seem to have dug myself into? Maybe I ought to just dig out the YAML upload functions from FrozenRecord and dump them into my Question model without using the gem.
So in the end, Frozen_Record wasn't really adding much to my app it seems. Given the files are loaded from YAML and there's no Controller, I presume there's no way for the Question records to be updated, unless someone has already compromised my DB and inserted their own questions.yml file.
As such I just changed the Question model as follows to load the YAML and insert it into the Database as new Question records:
class Question < ActiveRecord::Base
validates :next_question_id_yes, :question_text, :answer_type, presence: true
belongs_to :sale_qualifier
File.open("#{Rails.root}/config/initializers/questions.yml", 'r') do |file|
YAML::load(file).each do |record|
Question.create(record)
end
end
end
I am using rails-api gem in my project for json api, and for that purpose I used active model serializer gem for serializing my objects but some how the objects are not being serialized using active model serializer.
I have a MessageSerializer inside of my serializers folder
class MessageSerializer < ActiveModel::Serializer
attributes :id, :sender_id, :recipient_id, :sender_type, :subj, :body, :status, :sender
def sender
object.user.try('username')
end
end
And my messages controller is as follows
class Api::MessagesController < Api::BaseController
def index
#messages = current_user.incoming_messages
render json: #messages, serializer: MessageSerializer
end
end
But the problem is that the serialized object thrown to client contains all the fields in message model ie; it contains created_at, updated_at fields too.
Seems like its not using serializer.
What might have gone wrong?
I searched a lot about it but didn't found any post that helped me.
Thanks
In your BaseController, did you add the include bellow ?
include ActionController::Serialization
What version of AMS are you using?
I had this same issue and was able to fix it by changing the AMS version from 0.9.X to 0.8.X. This can be done by adding a version number to your Gemfile.
gem 'active_model_serializers', '~> 0.8.0'
There are notes about this on the AMS GitHub repo.
https://github.com/rails-api/active_model_serializers#maintenance-please-read
That's because the serialization is not loaded by default in rails-api.
You have to do this:
class ApplicationController < ActionController::API
include ::ActionController::Serialization
end
I didn't downgrade, I spent some time trying different things and at the end I get to a pattern like this:
def sender
if object.sender
serializer = SenderSerializer.new(object.sender)
ActiveModel::Serializer::Adapter::JsonApi.new(serializer).as_json[:senders]
end
end
it's ugly but it did the trick for me.
For a has_many relation, you can do something like this:
def attachments
attachments = object.attachments.to_a
return [] if attachments.empty?
serializer = ActiveModel::Serializer::ArraySerializer.new(attachments, each_serializer:AttachmentSerializer)
ActiveModel::Serializer::Adapter::JsonApi.new(serializer).as_json[:attachments]
end
I have a project using Paperclip gem for attachments and Globalize3 for attribute translation. Records need to have a different attachment for each locale.
I though about moving Paperclip attributes to translation table, and that might work, but I don't think that would work when Paperclip needs to delete attachments.
What's the best way to achieve something like that?
UPDATE: to be clear, I want this because my client wants to upload different images for each locale.
Unfortunately I didn't find a way to do this using Globalize3. In theory, I could have added a separate model for image and add image_id to list of translated columns (to have something like MainModel -> Translation -> Image), but it seems that Globalize has some migration issues with non-string columns.
Instead of using Globalize3, I did this with a separate Image model with locale attribute and main model which accepts nested attributes for it. Something along the lines of:
class MainModel < ActiveRecord::Base
has_many :main_model_images
accepts_nested_attributes_for :main_model_images
# return image for locale or any other as a fallback
def localized_image(locale)
promo_box_images.where(:locale => locale).first || promo_box_images.first
end
end
class MainModelImage < ActiveRecord::Base
belongs_to :main_model
has_attached_file :image
validates :locale,
:presence => true,
:uniqueness => { :scope => :main_model_id }
end
Tricky part was getting form to accept nested attributes only for one image, instead of all images in has_many relation.
=f.fields_for :main_model_images, #main_model.image_for_locale(I18n.locale) do |f_image|
=f_image.hidden_field :locale
=f_image.label :image
You could also try the paperclip-globalize3 gem, it should handle the case you describe. https://github.com/emjot/paperclip-globalize3
Ok since you asked me to share my solution to this problem even though I am using Carrierwave as a library for uploading here is it:
Ok so I would have a model setup like this:
class MyModel < ActiveRecord::Base
# ...
translates :attr_one, :attr_two, :uploaded_file
Now what I need for CarrierWave to work is place to attach the uploader to and that can be done on the Translation model
Translation.mount_uploader :uploaded_file, FileUploader
end
Now for your question about deleting, I think though I haven't needed to do it but it should work as the README says it should but on the translation model. https://github.com/jnicklas/carrierwave#removing-uploaded-files
MyModel.first.translation.remove_uploaded_file!
I haven't taken a look at paperclip for a good 2 years and if this is not applicable knowledge I suggest you try out carrierwave.
I want to create a Rails (2.1 and 2.2) model with ActiveRecord validations, but without a database table. What is the most widely used approach? I've found some plugins that claim to offer this functionality, but many of them don't appear to be widely used or maintained. What does the community recommend I do? Right now I am leaning toward coming up with my own solution based on this blog post.
There is a better way to do this in Rails 3: http://railscasts.com/episodes/219-active-model
This is an approach I have used in the past:
In app/models/tableless.rb
class Tableless < ActiveRecord::Base
def self.columns
#columns ||= [];
end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default,
sql_type.to_s, null)
end
# Override the save method to prevent exceptions.
def save(validate = true)
validate ? valid? : true
end
end
In app/models/foo.rb
class Foo < Tableless
column :bar, :string
validates_presence_of :bar
end
In script/console
Loading development environment (Rails 2.2.2)
>> foo = Foo.new
=> #<Foo bar: nil>
>> foo.valid?
=> false
>> foo.errors
=> #<ActiveRecord::Errors:0x235b270 #errors={"bar"=>["can't be blank"]}, #base=#<Foo bar: nil>>
There is easier way now:
class Model
include ActiveModel::Model
attr_accessor :var
validates :var, presence: true
end
ActiveModel::Model code:
module ActiveModel
module Model
def self.included(base)
base.class_eval do
extend ActiveModel::Naming
extend ActiveModel::Translation
include ActiveModel::Validations
include ActiveModel::Conversion
end
end
def initialize(params={})
params.each do |attr, value|
self.public_send("#{attr}=", value)
end if params
end
def persisted?
false
end
end
end
http://api.rubyonrails.org/classes/ActiveModel/Model.html
I think the blog post you are linking is the best way to go. I would only suggest moving the stubbed out methods into a module not to pollute your code.
just create a new file ending in ".rb" following the conventions you're used to (singular for file name and class name, underscored for file name, camel case for class name) on your "models/" directory. The key here is to not inherit your model from ActiveRecord (because it is AR that gives you the database functionality).
e.g.: for a new model for cars, create a file called "car.rb" in your models/ directory and inside your model:
class Car
# here goes all your model's stuff
end
edit: btw, if you want attributes on your class, you can use here everything you use on ruby, just add a couple lines using "attr_accessor":
class Car
attr_accessor :wheels # this will create for you the reader and writer for this attribute
attr_accessor :doors # ya, this will do the same
# here goes all your model's stuff
end
edit #2: after reading Mike's comment, I'd tell you to go his way if you want all of the ActiveRecord's functionality but no table on the database. If you just want an ordinary Ruby class, maybe you'll find this solution better ;)
For the sake of completeness:
Rails now (at V5) has a handy module you can include:
include ActiveModel::Model
This allows you to initialise with a hash, and use validations amongst other things.
Full documentation is here.
There's a screencast about non-Active Record model, made up by Ryan Bates. A good place to start from.
Just in case you did not already watch it.
I have built a quick Mixin to handle this, as per John Topley's suggestion.
http://github.com/willrjmarshall/Tableless
What about marking the class as abstract?
class Car < ActiveRecord::Base
self.abstract = true
end
this will tell rails that the Car class has no corresponding table.
[edit]
this won't really help you if you'll need to do something like:
my_car = Car.new
Use the Validatable gem. As you say, there are AR-based solutions, but they tend to be brittle.
http://validatable.rubyforge.org/
Anybody has ever tried to include ActiveRecord::Validations and ActiveRecord::Validations::ClassMethods in a non-Active Record class and see what happens when trying to setup validators ?
I'm sure there are plenty of dependencies between the validation framework and ActiveRecord itself. But you may succeed in getting rid of those dependencies by forking your own validation framework from the AR validation framework.
Just an idea.
Update: oopps, this is more or less what's suggested in the post linked with your question. Sorry for the disturbance.
Do like Tiago Pinto said and just don't have your model inherit from ActiveRecord::Base. It'll just be a regular Ruby class that you stick in a file in your app/models/ directory. If none of your models have tables and you're not using a database or ActiveRecord at all in your app, be sure to modify your environment.rb file to have the following line:
config.frameworks -= [:active_record]
This should be within the Rails::Initializer.run do |config| block.
You ought to checkout the PassiveRecord plugin. It gives you an ActiveRecord-like interface for non-database models. It's simple, and less hassle than fighting ActiveRecord.
We're using PassiveRecord in combination with the Validatable gem to get the OP's desired behaviour.