2

How can I set a model to be read-only every time that it is accessed if an attribute within the same model is set to true?

I have looked everywhere and the model read only seems to have very little documentation and even web results.

Edit (Additional Info): I have two methods in my Model (application.rb) - not in private

  def lock()
    self.locked = true
    save(validate: false)
  end

  def unlock()
    self.locked = false
    save(validate: false)
  end

I call them from my applications controller on update with:

if params[:application][:locked] == false
  @application.unlock
  return
elsif params[:application][:locked] == true
  @application.lock
  return
end

and in the Model (application.rb) I have - not in private:

  def readonly?
    locked == true
  end
  • Is it a duplicate of https://stackoverflow.com/questions/53432372/how-to-make-a-rails-model-read-only-except-for-specified-attributes?noredirect=1#comment93829700_53432372 Isn't it? – iGian Nov 26 '18 at 11:59
  • @iGian - It is somewhat a duplicate. I made this because I felt that this topic is a bit off topic from my main question of that post, so I wanted to ensure they existed separately to allow both questions to be answered separately (and hopefully help someone else with these questions). This is about making the model read only based on attributes within the model, and the other is about only allowing specific attributes to be read only. – notADevAccount Nov 26 '18 at 12:59
  • @notADevAccount When you say "read-only on a rails model" , were you intending to set read-only to the entire model (all of the records)? or to only set read-only for an instance of that model (dynamically resolved per instance)? – Jay-Ar Polidario Nov 26 '18 at 13:18
  • @Jay-ArPolidario - Yes, I need to set it by instance, because each record will have an attribute named 'locked' that should determine if that specific record is read-only. if true, then it is read-only and should not allow any writes. – notADevAccount Nov 26 '18 at 13:38
  • @notADevAccount I see, then I think my answer is what you were looking for? :) – Jay-Ar Polidario Nov 26 '18 at 13:39
  • @Jay-ArPolidario - Yes, I think so, but I have a question: When I place the `def readonly?` method in my model (public), I have `locked == true` as the condition, but it seems to make the model readonly regardless of the attribute – notADevAccount Nov 26 '18 at 13:43
  • @notADevAccount If it's the same code as your https://stackoverflow.com/questions/53432372/how-to-make-a-rails-model-read-only-except-for-specified-attributes?noredirect=1#comment93829700_53432372 where is this `locked` set (like where does `true` or `false` value gets evaluated from)? Also, can you update your question as well to include this particular `locked` code? just so other SO users wouldn't need to navigate to your other question. – Jay-Ar Polidario Nov 26 '18 at 13:46
  • @notADevAccount Is `locked` an attribute of your model? If so, then your `if locked; def readonly?; true; end; end` is incorrect, because outside `def readonly? ... end`, `self == Model` and not `self` instance of `Model`. I'll expand on this further after you confirm first if my assumptions/guesses are right. – Jay-Ar Polidario Nov 26 '18 at 13:49
  • @Jay-ArPolidario - I updated the question with my code – notADevAccount Nov 26 '18 at 13:53
  • @notADevAccount I updated my answer, let me know if this is what you intended to do – Jay-Ar Polidario Nov 26 '18 at 14:06

1 Answers1

2

Updated:

# app/models/application.rb

# I highly suggest renaming `Application` into something else because, Rails
# already has a same defined constant name `Application` which is defined in your
# app/config/application.rb

class Application < ApplicationRecord
  def lock!
    # depending on your use-case I'll do an `update` below instead
    # self.lock = true
    update!(locked: true)
  end

  def unlock!
    # self.lock = false
    update!(locked: false)
  end
end

# app/models/user.rb
class User < ApplicationRecord
  belongs_to :application

  def readonly?
    # this is still subject to race-condition even though already `reloaded`
    application.reload.locked || some_user_attribute == 'HELLO WORLD!'
  end
end

# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :application

  def readonly?
    # this is still subject to race-condition even though already `reloaded`
    application.reload.locked || some_comment_attribute_like_is_disabled?
  end
end

Notice that I added a belongs_to association there because you'll most likely need this because your Application as you said is actually already a normal model anyway. If you do not have this association, and are setting the locked internally as a class instance variable of your Application class (i.e. you have @locked class instance variable), then (depending on your requirements), you'll have problems with 1) persistency because each request (per different process/server) will default to locked = nil (which might or might not be a problem to you), and also 2) concurrency because threads share the value of this class instance variable, which means that simultaneous requests would need this @locked value be evaluated independently; which becomes potentially dangerous if @locked is set to true in one thread, while on another @locked is overidden and is set to false. But if these are not a problem, I can still update my answer to not use belongs_to :application; let me know.

Jay-Ar Polidario
  • 6,463
  • 14
  • 28
  • So I am still having difficulty understanding what that means. Does setting the read only in user then make it check if the application is read only per instance? My application model is: `class Application < ApplicationRecord` – notADevAccount Nov 26 '18 at 14:54
  • @notADevAccount ohh i see when you said `application.rb`, I thought you were referring to the "conventionally-named" `app/config/application.rb` class. Regardless though what class you are setting `self.locked = true or false`, to ensure thread-safetiness you'll still need something like `Thread.current`. – Jay-Ar Polidario Nov 26 '18 at 14:57
  • @notADevAccount To answer your question though, yes `def readonly?` is an `instance` method, because a `class` method would be like `def self.readonly?` instead. Because it is an `instance` method, then yes that method is evaluated per instance of a Model (which is as expected of how Rails correctly implemented this `def readonly` method), and I verified that that method is called/invoked whenever an instance of that Model has been created/updated/destroyed. – Jay-Ar Polidario Nov 26 '18 at 15:00
  • @notADevAccount I'm updating my code now to reflect incorrect "application.rb" code that I assumed – Jay-Ar Polidario Nov 26 '18 at 15:07
  • @notADevAccount oh I understand now, `Application` is actually a normal model, and not an abstract subclass of `ApplicationRecord`? and that it has an attribute called `locked`? Then yeah I don't think you'll need to worry about thread-safetiness here then. I updated my answer. – Jay-Ar Polidario Nov 26 '18 at 15:38
  • Okay, that makes more sense! Thanks a lot! I have marked as answer... I up voted as well, but it will apparently not be shown publicly due to my low rep. I appreciate all of your help! – notADevAccount Nov 26 '18 at 18:52
  • @notADevAccount no worries! Glad to help :) – Jay-Ar Polidario Nov 26 '18 at 18:57